cb18f3c381
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>
181 lines
6.6 KiB
TypeScript
181 lines
6.6 KiB
TypeScript
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
|
||
}
|