feat: F021 market intelligence & signal discovery
Signal domain types, mock data (8 signals), provider interface + MockupMarketIntelligenceProvider, marketIntelligenceService, TanStack Query hooks, 10 ops components (SignalInbox, MarketSignalCard, MarketSignalFilterBar, MarketSignalSkeleton, MarketSignalEmptyState, SignalConfidenceBadge, SourceReliabilityBadge, SensitivityWarningPanel, SignalConversionPanel, SignalEvidenceList), MarketIntelligence page wired into AppShell nav and App router. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
import type { AssetType, SignalType, SensitivityLevel, FreshnessStatus } from './enums'
|
||||
|
||||
// ── Source Categories ─────────────────────────────────────────────────────────
|
||||
|
||||
export const MarketSignalSourceCategory = {
|
||||
PUBLIC_LISTING_PLATFORM: 'PUBLIC_LISTING_PLATFORM',
|
||||
BUILDING_PERMIT_REGISTER: 'BUILDING_PERMIT_REGISTER',
|
||||
COMPANY_NEWS: 'COMPANY_NEWS',
|
||||
JOB_GROWTH_SIGNAL: 'JOB_GROWTH_SIGNAL',
|
||||
COMMERCIAL_REGISTER: 'COMMERCIAL_REGISTER',
|
||||
INFRASTRUCTURE_PROJECT: 'INFRASTRUCTURE_PROJECT',
|
||||
PORTFOLIO_IMPORT: 'PORTFOLIO_IMPORT',
|
||||
LEASE_EXPIRY_DATA: 'LEASE_EXPIRY_DATA',
|
||||
USER_DEMAND_SIGNAL: 'USER_DEMAND_SIGNAL',
|
||||
MANUAL_ANALYST_SIGNAL: 'MANUAL_ANALYST_SIGNAL',
|
||||
} as const
|
||||
export type MarketSignalSourceCategory = typeof MarketSignalSourceCategory[keyof typeof MarketSignalSourceCategory]
|
||||
|
||||
export const MARKET_SIGNAL_SOURCE_LABELS: Record<MarketSignalSourceCategory, string> = {
|
||||
PUBLIC_LISTING_PLATFORM: 'Listing-Plattform',
|
||||
BUILDING_PERMIT_REGISTER: 'Baugesuch-Register',
|
||||
COMPANY_NEWS: 'Unternehmensnachrichten',
|
||||
JOB_GROWTH_SIGNAL: 'Stellenwachstum',
|
||||
COMMERCIAL_REGISTER: 'Handelsregister',
|
||||
INFRASTRUCTURE_PROJECT: 'Infrastrukturprojekt',
|
||||
PORTFOLIO_IMPORT: 'Portfolio-Import',
|
||||
LEASE_EXPIRY_DATA: 'Vertragslaufdaten',
|
||||
USER_DEMAND_SIGNAL: 'Nutzernachfrage',
|
||||
MANUAL_ANALYST_SIGNAL: 'Analyst-Signal',
|
||||
}
|
||||
|
||||
// LEASE_EXPIRY_DATA and PORTFOLIO_IMPORT are sensitive — never expose raw to Demand Users
|
||||
export const SENSITIVE_SOURCE_CATEGORIES: MarketSignalSourceCategory[] = [
|
||||
MarketSignalSourceCategory.LEASE_EXPIRY_DATA,
|
||||
MarketSignalSourceCategory.PORTFOLIO_IMPORT,
|
||||
]
|
||||
|
||||
// ── Processing Status ─────────────────────────────────────────────────────────
|
||||
|
||||
export const SignalProcessingStatus = {
|
||||
DETECTED: 'DETECTED',
|
||||
NORMALIZED: 'NORMALIZED',
|
||||
ENRICHED: 'ENRICHED',
|
||||
NEEDS_REVIEW: 'NEEDS_REVIEW',
|
||||
APPROVED_AS_SIGNAL: 'APPROVED_AS_SIGNAL',
|
||||
REJECTED: 'REJECTED',
|
||||
CONVERTED_TO_FUTURE_AVAILABILITY: 'CONVERTED_TO_FUTURE_AVAILABILITY',
|
||||
ARCHIVED: 'ARCHIVED',
|
||||
} as const
|
||||
export type SignalProcessingStatus = typeof SignalProcessingStatus[keyof typeof SignalProcessingStatus]
|
||||
|
||||
export const SIGNAL_PROCESSING_STATUS_LABELS: Record<SignalProcessingStatus, string> = {
|
||||
DETECTED: 'Erkannt',
|
||||
NORMALIZED: 'Normalisiert',
|
||||
ENRICHED: 'Angereichert',
|
||||
NEEDS_REVIEW: 'Prüfung erforderlich',
|
||||
APPROVED_AS_SIGNAL: 'Genehmigt',
|
||||
REJECTED: 'Abgelehnt',
|
||||
CONVERTED_TO_FUTURE_AVAILABILITY: 'Konvertiert',
|
||||
ARCHIVED: 'Archiviert',
|
||||
}
|
||||
|
||||
export const SIGNAL_PROCESSING_STATUS_COLORS: Record<SignalProcessingStatus, { bg: string; fg: string }> = {
|
||||
DETECTED: { bg: 'rgba(148,163,184,0.15)', fg: '#64748b' },
|
||||
NORMALIZED: { bg: 'rgba(59,130,246,0.12)', fg: '#2563eb' },
|
||||
ENRICHED: { bg: 'rgba(99,102,241,0.12)', fg: '#4f46e5' },
|
||||
NEEDS_REVIEW: { bg: 'rgba(245,158,11,0.12)', fg: '#d97706' },
|
||||
APPROVED_AS_SIGNAL: { bg: 'rgba(34,197,94,0.12)', fg: '#16a34a' },
|
||||
REJECTED: { bg: 'rgba(239,68,68,0.12)', fg: '#dc2626' },
|
||||
CONVERTED_TO_FUTURE_AVAILABILITY: { bg: 'rgba(139,92,246,0.12)', fg: '#7c3aed' },
|
||||
ARCHIVED: { bg: 'rgba(148,163,184,0.10)', fg: '#94a3b8' },
|
||||
}
|
||||
|
||||
// ── Evidence & Entities ───────────────────────────────────────────────────────
|
||||
|
||||
export const ExtractedEntityType = {
|
||||
COMPANY: 'COMPANY',
|
||||
PERSON: 'PERSON',
|
||||
LOCATION: 'LOCATION',
|
||||
ASSET: 'ASSET',
|
||||
DATE: 'DATE',
|
||||
} as const
|
||||
export type ExtractedEntityType = typeof ExtractedEntityType[keyof typeof ExtractedEntityType]
|
||||
|
||||
export interface ExtractedEntity {
|
||||
type: ExtractedEntityType
|
||||
value: string
|
||||
confidence: number
|
||||
}
|
||||
|
||||
export const EvidenceType = {
|
||||
TEXT_EXCERPT: 'TEXT_EXCERPT',
|
||||
URL_REFERENCE: 'URL_REFERENCE',
|
||||
ANALYST_NOTE: 'ANALYST_NOTE',
|
||||
DOCUMENT: 'DOCUMENT',
|
||||
} as const
|
||||
export type EvidenceType = typeof EvidenceType[keyof typeof EvidenceType]
|
||||
|
||||
export interface SignalEvidence {
|
||||
id: string
|
||||
signalId: string
|
||||
evidenceType: EvidenceType
|
||||
content: string
|
||||
sourceUrl?: string
|
||||
retrievedAt: string
|
||||
confidence: number
|
||||
}
|
||||
|
||||
// ── Core Signal ───────────────────────────────────────────────────────────────
|
||||
|
||||
export interface MarketSignal {
|
||||
id: string
|
||||
title: string
|
||||
summary: string
|
||||
sourceCategory: MarketSignalSourceCategory
|
||||
sourceLabel: string
|
||||
sourceUrl?: string
|
||||
detectedAt: string
|
||||
location: string
|
||||
affectedAssetTypes: AssetType[]
|
||||
signalType: SignalType
|
||||
rawEvidenceSummary: string
|
||||
extractedEntities: ExtractedEntity[]
|
||||
evidence: SignalEvidence[]
|
||||
sourceReliabilityScore: number // 0–1
|
||||
confidenceScore: number // 0–1
|
||||
sensitivityLevel: SensitivityLevel
|
||||
freshnessStatus: FreshnessStatus
|
||||
processingStatus: SignalProcessingStatus
|
||||
linkedPropertyId?: string
|
||||
linkedNeedId?: string
|
||||
possibleFutureSignalId?: string
|
||||
analystNotes: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
// ── Intelligence Aggregates ───────────────────────────────────────────────────
|
||||
|
||||
export interface MarketInsight {
|
||||
id: string
|
||||
title: string
|
||||
summary: string
|
||||
signalIds: string[]
|
||||
location: string
|
||||
affectedAssetTypes: AssetType[]
|
||||
confidenceScore: number
|
||||
createdAt: string
|
||||
analystId: string
|
||||
}
|
||||
|
||||
export interface IntelligenceRun {
|
||||
id: string
|
||||
triggeredAt: string
|
||||
completedAt?: string
|
||||
signalsDetected: number
|
||||
signalsProcessed: number
|
||||
status: 'RUNNING' | 'COMPLETED' | 'FAILED'
|
||||
sourceCategories: MarketSignalSourceCategory[]
|
||||
}
|
||||
|
||||
export interface SignalConversionCandidate {
|
||||
signalId: string
|
||||
proposedTitle: string
|
||||
proposedSummary: string
|
||||
estimatedAvailabilityDate?: string
|
||||
proposedConfidence: number
|
||||
conversionRationale: string
|
||||
requiresReview: boolean
|
||||
}
|
||||
|
||||
// ── Filters ───────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface MarketSignalFilters {
|
||||
sourceCategory?: MarketSignalSourceCategory
|
||||
processingStatus?: SignalProcessingStatus
|
||||
sensitivityLevel?: SensitivityLevel
|
||||
signalType?: SignalType
|
||||
search?: string
|
||||
}
|
||||
Reference in New Issue
Block a user