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:
Benjamin Sutter
2026-05-15 17:07:58 +02:00
parent 3cd2e83b25
commit cb18f3c381
17 changed files with 1389 additions and 4 deletions
+180
View File
@@ -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 // 01
confidenceScore: number // 01
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
}