feat: remove Administration workspace — keep only Verwaltung + Suche
- Delete all ops page components (ReviewQueue, AIMonitoring, Governance, SourceMonitoring, ActivityTimeline, SignalPipeline) - Remove OPERATIONS workspace from AppShell config, nav order, path detection - Remove all /ops/* routes from App.tsx - Remove WorkspaceType.OPERATIONS from allowedWorkspaces in authService, sessionStore, permissions - Keep MarketIntelligence page (already moved to /supply/market-intelligence) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
export const ActivityAction = {
|
||||
CREATED: 'CREATED',
|
||||
UPDATED: 'UPDATED',
|
||||
DELETED: 'DELETED',
|
||||
APPROVED: 'APPROVED',
|
||||
REJECTED: 'REJECTED',
|
||||
SHORTLISTED: 'SHORTLISTED',
|
||||
REVIEWED: 'REVIEWED',
|
||||
VERIFIED: 'VERIFIED',
|
||||
EXPORTED: 'EXPORTED',
|
||||
SHARED: 'SHARED',
|
||||
} as const
|
||||
export type ActivityAction = typeof ActivityAction[keyof typeof ActivityAction]
|
||||
|
||||
export const ActivityEntityType = {
|
||||
PROPERTY: 'PROPERTY',
|
||||
NEED: 'NEED',
|
||||
MATCH: 'MATCH',
|
||||
FUTURE_SIGNAL: 'FUTURE_SIGNAL',
|
||||
AI_OUTPUT: 'AI_OUTPUT',
|
||||
SHORTLIST: 'SHORTLIST',
|
||||
USER: 'USER',
|
||||
} as const
|
||||
export type ActivityEntityType = typeof ActivityEntityType[keyof typeof ActivityEntityType]
|
||||
|
||||
export interface ActivityEvent {
|
||||
id: string
|
||||
actorId: string // user or system ID that triggered the event
|
||||
action: ActivityAction
|
||||
entityType: ActivityEntityType
|
||||
entityId: string
|
||||
timestamp: string
|
||||
metadata?: Record<string, unknown>
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { ReviewStatus } from './enums'
|
||||
|
||||
export const AIOutputType = {
|
||||
NEED_PARSE: 'NEED_PARSE',
|
||||
FOLLOW_UP_QUESTIONS: 'FOLLOW_UP_QUESTIONS',
|
||||
MATCH_EXPLANATION: 'MATCH_EXPLANATION',
|
||||
COMPARE_SUMMARY: 'COMPARE_SUMMARY',
|
||||
DECISION_BRIEF: 'DECISION_BRIEF',
|
||||
DATA_QUALITY_SUMMARY: 'DATA_QUALITY_SUMMARY',
|
||||
} as const
|
||||
export type AIOutputType = typeof AIOutputType[keyof typeof AIOutputType]
|
||||
|
||||
export const AIErrorType = {
|
||||
SCHEMA_VALIDATION: 'SCHEMA_VALIDATION',
|
||||
PROVIDER_TIMEOUT: 'PROVIDER_TIMEOUT',
|
||||
INVALID_JSON: 'INVALID_JSON',
|
||||
EMPTY_RESPONSE: 'EMPTY_RESPONSE',
|
||||
RATE_LIMIT: 'RATE_LIMIT',
|
||||
} as const
|
||||
export type AIErrorType = typeof AIErrorType[keyof typeof AIErrorType]
|
||||
|
||||
export interface AIOutputError {
|
||||
type: AIErrorType
|
||||
message: string
|
||||
recoverable: boolean
|
||||
}
|
||||
|
||||
export interface AIOutput {
|
||||
id: string
|
||||
type: AIOutputType
|
||||
provider: string
|
||||
model: string
|
||||
promptVersion: string
|
||||
schemaVersion: string
|
||||
inputHash: string
|
||||
outputPreview: string
|
||||
createdAt: string
|
||||
latencyMs?: number
|
||||
costEstimate?: number
|
||||
reviewStatus: ReviewStatus
|
||||
relatedEntityType: string
|
||||
relatedEntityId: string
|
||||
error?: AIOutputError
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { WorkspaceType } from './enums'
|
||||
|
||||
export interface AssistantContext {
|
||||
currentRoute: string
|
||||
workspace: WorkspaceType | null
|
||||
selectedEntityType?: string
|
||||
selectedEntityId?: string
|
||||
visibleScores?: Record<string, number>
|
||||
visibleRisks?: string[]
|
||||
visibleMissingData?: string[]
|
||||
availableActions?: string[]
|
||||
userRole: string
|
||||
organizationId: string
|
||||
}
|
||||
|
||||
export interface AssistantAction {
|
||||
id: string
|
||||
label: string
|
||||
description: string
|
||||
actionType: 'NAVIGATE' | 'OPEN_REVIEW' | 'ADD_TO_SHORTLIST' | 'REQUEST_DATA' | 'SEND_TO_REVIEW'
|
||||
payload?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface AssistantMessage {
|
||||
id: string
|
||||
role: 'user' | 'assistant'
|
||||
content: string
|
||||
createdAt: string
|
||||
confidence?: number
|
||||
sources?: string[]
|
||||
actions?: AssistantAction[]
|
||||
}
|
||||
|
||||
export interface SuggestedQuestion {
|
||||
id: string
|
||||
question: string
|
||||
category: string
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
export interface KpiCardData {
|
||||
id: string
|
||||
label: string
|
||||
value: number | string
|
||||
trend?: 'up' | 'down' | 'neutral'
|
||||
trendLabel?: string
|
||||
accent?: string
|
||||
tooltip?: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
export interface StrongMatchItem {
|
||||
matchId: string
|
||||
propertyId: string
|
||||
propertyTitle: string
|
||||
propertyAddress: string
|
||||
needSummary: string
|
||||
matchScore: number
|
||||
topReason: string
|
||||
missingDataCount: number
|
||||
nextBestAction: string
|
||||
}
|
||||
|
||||
export interface TimeHorizonDistribution {
|
||||
short: number // 0–6 months
|
||||
medium: number // 6–12 months
|
||||
long: number // 12–24 months
|
||||
}
|
||||
|
||||
export interface FutureSignalSummary {
|
||||
total: number
|
||||
highConfidence: number
|
||||
restricted: number
|
||||
needsReview: number
|
||||
avgTimeHorizonMonths: number
|
||||
timeHorizonDistribution: TimeHorizonDistribution
|
||||
}
|
||||
|
||||
export interface DataQualitySummary {
|
||||
avgScore: number
|
||||
critical: number
|
||||
propertiesWithMissingCritical: number
|
||||
topMissingFields: string[]
|
||||
}
|
||||
|
||||
export interface DashboardReviewTask {
|
||||
id: string
|
||||
title: string
|
||||
priority: 'HIGH' | 'MEDIUM' | 'LOW'
|
||||
status: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface DashboardData {
|
||||
totalProperties: number
|
||||
activeProperties: number
|
||||
strongMatchCount: number
|
||||
avgDataQuality: number
|
||||
futureSignals: FutureSignalSummary | null
|
||||
dataQuality: DataQualitySummary | null
|
||||
reviewTasks: DashboardReviewTask[] | null
|
||||
strongMatches: StrongMatchItem[] | null
|
||||
lastUpdated: string
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import type { FreshnessStatus } from './enums'
|
||||
|
||||
// ── Connector / Source Type ───────────────────────────────────────────────────
|
||||
export const DataSourceType = {
|
||||
API_CONNECTOR: 'API_CONNECTOR',
|
||||
CSV_IMPORT: 'CSV_IMPORT',
|
||||
MANUAL_UPLOAD: 'MANUAL_UPLOAD',
|
||||
PUBLIC_WEB_SOURCE: 'PUBLIC_WEB_SOURCE',
|
||||
PARTNER_FEED: 'PARTNER_FEED',
|
||||
INTERNAL_PORTFOLIO_EXPORT: 'INTERNAL_PORTFOLIO_EXPORT',
|
||||
CONTRACT_METADATA_IMPORT: 'CONTRACT_METADATA_IMPORT',
|
||||
ANALYST_ENTRY: 'ANALYST_ENTRY',
|
||||
FUTURE_CRAWLER_STUB: 'FUTURE_CRAWLER_STUB',
|
||||
} as const
|
||||
export type DataSourceType = typeof DataSourceType[keyof typeof DataSourceType]
|
||||
|
||||
export const DATA_SOURCE_TYPE_LABELS: Record<DataSourceType, string> = {
|
||||
API_CONNECTOR: 'API-Connector',
|
||||
CSV_IMPORT: 'CSV-Import',
|
||||
MANUAL_UPLOAD: 'Manueller Upload',
|
||||
PUBLIC_WEB_SOURCE: 'Öffentliche Web-Quelle',
|
||||
PARTNER_FEED: 'Partner-Feed',
|
||||
INTERNAL_PORTFOLIO_EXPORT: 'Portfolio-Export',
|
||||
CONTRACT_METADATA_IMPORT: 'Vertrags-Import',
|
||||
ANALYST_ENTRY: 'Analysten-Eingabe',
|
||||
FUTURE_CRAWLER_STUB: 'Crawler (geplant)',
|
||||
}
|
||||
|
||||
// ── Source Status ─────────────────────────────────────────────────────────────
|
||||
export const SourceStatus = {
|
||||
ACTIVE: 'ACTIVE',
|
||||
PAUSED: 'PAUSED',
|
||||
ERROR: 'ERROR',
|
||||
PENDING_REVIEW: 'PENDING_REVIEW',
|
||||
DISABLED: 'DISABLED',
|
||||
} as const
|
||||
export type SourceStatus = typeof SourceStatus[keyof typeof SourceStatus]
|
||||
|
||||
export const SOURCE_STATUS_LABELS: Record<SourceStatus, string> = {
|
||||
ACTIVE: 'Aktiv',
|
||||
PAUSED: 'Pausiert',
|
||||
ERROR: 'Fehler',
|
||||
PENDING_REVIEW: 'Prüfung ausstehend',
|
||||
DISABLED: 'Deaktiviert',
|
||||
}
|
||||
|
||||
export const SOURCE_STATUS_COLORS: Record<SourceStatus, { bg: string; fg: string }> = {
|
||||
ACTIVE: { bg: 'rgba(22,163,74,0.1)', fg: '#15803d' },
|
||||
PAUSED: { bg: 'rgba(234,179,8,0.1)', fg: '#a16207' },
|
||||
ERROR: { bg: 'rgba(239,68,68,0.1)', fg: '#dc2626' },
|
||||
PENDING_REVIEW: { bg: 'rgba(59,130,246,0.1)', fg: '#1d4ed8' },
|
||||
DISABLED: { bg: 'rgba(148,163,184,0.1)', fg: '#64748b' },
|
||||
}
|
||||
|
||||
// ── Terms / Legal Status ──────────────────────────────────────────────────────
|
||||
export const TermsStatus = {
|
||||
APPROVED: 'APPROVED',
|
||||
NEEDS_LEGAL_REVIEW: 'NEEDS_LEGAL_REVIEW',
|
||||
RESTRICTED: 'RESTRICTED',
|
||||
BLOCKED: 'BLOCKED',
|
||||
UNKNOWN: 'UNKNOWN',
|
||||
} as const
|
||||
export type TermsStatus = typeof TermsStatus[keyof typeof TermsStatus]
|
||||
|
||||
export const TERMS_STATUS_LABELS: Record<TermsStatus, string> = {
|
||||
APPROVED: 'Genehmigt',
|
||||
NEEDS_LEGAL_REVIEW: 'Rechtliche Prüfung',
|
||||
RESTRICTED: 'Eingeschränkt',
|
||||
BLOCKED: 'Gesperrt',
|
||||
UNKNOWN: 'Unbekannt',
|
||||
}
|
||||
|
||||
export const TERMS_STATUS_COLORS: Record<TermsStatus, { bg: string; fg: string; border: string }> = {
|
||||
APPROVED: { bg: 'rgba(22,163,74,0.08)', fg: '#15803d', border: 'rgba(22,163,74,0.3)' },
|
||||
NEEDS_LEGAL_REVIEW: { bg: 'rgba(234,179,8,0.08)', fg: '#a16207', border: 'rgba(234,179,8,0.3)' },
|
||||
RESTRICTED: { bg: 'rgba(249,115,22,0.08)', fg: '#c2410c', border: 'rgba(249,115,22,0.3)' },
|
||||
BLOCKED: { bg: 'rgba(239,68,68,0.08)', fg: '#dc2626', border: 'rgba(239,68,68,0.3)' },
|
||||
UNKNOWN: { bg: 'rgba(148,163,184,0.08)', fg: '#64748b', border: 'rgba(148,163,184,0.3)' },
|
||||
}
|
||||
|
||||
// ── Connector Run Status ──────────────────────────────────────────────────────
|
||||
export const ConnectorRunStatus = {
|
||||
RUNNING: 'RUNNING',
|
||||
COMPLETED: 'COMPLETED',
|
||||
FAILED: 'FAILED',
|
||||
PARTIAL: 'PARTIAL',
|
||||
CANCELLED: 'CANCELLED',
|
||||
} as const
|
||||
export type ConnectorRunStatus = typeof ConnectorRunStatus[keyof typeof ConnectorRunStatus]
|
||||
|
||||
export const CONNECTOR_RUN_STATUS_LABELS: Record<ConnectorRunStatus, string> = {
|
||||
RUNNING: 'Läuft',
|
||||
COMPLETED: 'Abgeschlossen',
|
||||
FAILED: 'Fehlgeschlagen',
|
||||
PARTIAL: 'Teilweise',
|
||||
CANCELLED: 'Abgebrochen',
|
||||
}
|
||||
|
||||
export const CONNECTOR_RUN_STATUS_COLORS: Record<ConnectorRunStatus, { bg: string; fg: string }> = {
|
||||
RUNNING: { bg: 'rgba(99,102,241,0.1)', fg: '#4f46e5' },
|
||||
COMPLETED: { bg: 'rgba(22,163,74,0.1)', fg: '#15803d' },
|
||||
FAILED: { bg: 'rgba(239,68,68,0.1)', fg: '#dc2626' },
|
||||
PARTIAL: { bg: 'rgba(234,179,8,0.1)', fg: '#a16207' },
|
||||
CANCELLED: { bg: 'rgba(148,163,184,0.1)', fg: '#64748b' },
|
||||
}
|
||||
|
||||
// ── Interfaces ────────────────────────────────────────────────────────────────
|
||||
export interface DataSource {
|
||||
id: string
|
||||
name: string
|
||||
sourceType: DataSourceType
|
||||
ownerOrganizationId?: string
|
||||
legalBasis: string
|
||||
termsStatus: TermsStatus
|
||||
dataCategories: string[]
|
||||
supportedAssetTypes: string[]
|
||||
regionCoverage: string[]
|
||||
reliabilityScore: number
|
||||
freshnessStatus: FreshnessStatus
|
||||
lastRunAt?: string
|
||||
nextRunAt?: string
|
||||
status: SourceStatus
|
||||
errorState?: string
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export interface ConnectorRun {
|
||||
id: string
|
||||
sourceId: string
|
||||
startedAt: string
|
||||
finishedAt?: string
|
||||
status: ConnectorRunStatus
|
||||
itemsDetected: number
|
||||
itemsNormalized: number
|
||||
itemsRejected: number
|
||||
signalsCreated: number
|
||||
errors: string[]
|
||||
warnings: string[]
|
||||
runSummary: string
|
||||
}
|
||||
|
||||
export interface SourceFilters {
|
||||
search?: string
|
||||
sourceType?: DataSourceType
|
||||
status?: SourceStatus
|
||||
termsStatus?: TermsStatus
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// ── Asset Types ───────────────────────────────────────────────────────────────
|
||||
export const AssetType = {
|
||||
OFFICE: 'OFFICE',
|
||||
RETAIL: 'RETAIL',
|
||||
GASTRO: 'GASTRO', // legacy – kept for mock-data compatibility
|
||||
LIGHT_INDUSTRIAL: 'LIGHT_INDUSTRIAL',
|
||||
LOGISTICS: 'LOGISTICS',
|
||||
PRODUCTION: 'PRODUCTION',
|
||||
MIXED: 'MIXED',
|
||||
UNKNOWN: 'UNKNOWN',
|
||||
} as const
|
||||
export type AssetType = typeof AssetType[keyof typeof AssetType]
|
||||
|
||||
// ── Result Types ──────────────────────────────────────────────────────────────
|
||||
export const ResultType = {
|
||||
VERIFIED_PORTFOLIO: 'VERIFIED_PORTFOLIO',
|
||||
EXTERNAL_MARKET: 'EXTERNAL_MARKET',
|
||||
FUTURE_AVAILABILITY: 'FUTURE_AVAILABILITY',
|
||||
} as const
|
||||
export type ResultType = typeof ResultType[keyof typeof ResultType]
|
||||
|
||||
// ── Match Strength ─────────────────────────────────────────────────────────────
|
||||
export const MatchStrength = {
|
||||
STRONG: 'STRONG',
|
||||
MODERATE: 'MODERATE',
|
||||
WEAK: 'WEAK',
|
||||
} as const
|
||||
export type MatchStrength = typeof MatchStrength[keyof typeof MatchStrength]
|
||||
|
||||
// ── Match Status ──────────────────────────────────────────────────────────────
|
||||
export const MatchStatus = {
|
||||
PENDING_REVIEW: 'PENDING_REVIEW',
|
||||
APPROVED: 'APPROVED',
|
||||
REJECTED: 'REJECTED',
|
||||
SHORTLISTED: 'SHORTLISTED',
|
||||
ARCHIVED: 'ARCHIVED',
|
||||
} as const
|
||||
export type MatchStatus = typeof MatchStatus[keyof typeof MatchStatus]
|
||||
|
||||
// ── Risk Level ────────────────────────────────────────────────────────────────
|
||||
export const RiskLevel = {
|
||||
LOW: 'LOW',
|
||||
MEDIUM: 'MEDIUM',
|
||||
HIGH: 'HIGH',
|
||||
CRITICAL: 'CRITICAL',
|
||||
} as const
|
||||
export type RiskLevel = typeof RiskLevel[keyof typeof RiskLevel]
|
||||
|
||||
// ── Confidence Level (qualitative) ───────────────────────────────────────────
|
||||
export const ConfidenceLevel = {
|
||||
VERY_HIGH: 'VERY_HIGH', // >= 0.90
|
||||
HIGH: 'HIGH', // >= 0.75
|
||||
MEDIUM: 'MEDIUM', // >= 0.55
|
||||
LOW: 'LOW', // >= 0.35
|
||||
VERY_LOW: 'VERY_LOW', // < 0.35
|
||||
} as const
|
||||
export type ConfidenceLevel = typeof ConfidenceLevel[keyof typeof ConfidenceLevel]
|
||||
|
||||
// ── Data Quality Level (qualitative) ─────────────────────────────────────────
|
||||
export const DataQualityLevel = {
|
||||
HIGH: 'HIGH', // >= 0.80
|
||||
MEDIUM: 'MEDIUM', // >= 0.60
|
||||
LOW: 'LOW', // < 0.60
|
||||
INCOMPLETE: 'INCOMPLETE', // missing critical fields
|
||||
} as const
|
||||
export type DataQualityLevel = typeof DataQualityLevel[keyof typeof DataQualityLevel]
|
||||
|
||||
// ── Availability Status ───────────────────────────────────────────────────────
|
||||
export const AvailabilityStatus = {
|
||||
AVAILABLE_NOW: 'AVAILABLE_NOW',
|
||||
AVAILABLE_SOON: 'AVAILABLE_SOON',
|
||||
FUTURE_SIGNAL: 'FUTURE_SIGNAL',
|
||||
OCCUPIED: 'OCCUPIED',
|
||||
UNKNOWN: 'UNKNOWN',
|
||||
} as const
|
||||
export type AvailabilityStatus = typeof AvailabilityStatus[keyof typeof AvailabilityStatus]
|
||||
|
||||
// ── Availability Type (structural distinction) ────────────────────────────────
|
||||
export const AvailabilityType = {
|
||||
CONFIRMED: 'CONFIRMED', // verified, date known
|
||||
INDICATIVE: 'INDICATIVE', // external listing, unconfirmed
|
||||
PROBABILISTIC: 'PROBABILISTIC', // AI signal, no confirmed date
|
||||
} as const
|
||||
export type AvailabilityType = typeof AvailabilityType[keyof typeof AvailabilityType]
|
||||
|
||||
// ── Source Type ───────────────────────────────────────────────────────────────
|
||||
export const SourceType = {
|
||||
ERP_IMPORT: 'ERP_IMPORT',
|
||||
MANUAL_ENTRY: 'MANUAL_ENTRY',
|
||||
IMMOSCOUT_SCRAPE: 'IMMOSCOUT_SCRAPE',
|
||||
HOMEGATE_SCRAPE: 'HOMEGATE_SCRAPE',
|
||||
NEWHOME_SCRAPE: 'NEWHOME_SCRAPE',
|
||||
MATCHOFFICE_SCRAPE: 'MATCHOFFICE_SCRAPE',
|
||||
MAISON_WORK_SCRAPE: 'MAISON_WORK_SCRAPE',
|
||||
AI_SIGNAL: 'AI_SIGNAL',
|
||||
PARTNER_FEED: 'PARTNER_FEED',
|
||||
UNKNOWN: 'UNKNOWN',
|
||||
} as const
|
||||
export type SourceType = typeof SourceType[keyof typeof SourceType]
|
||||
|
||||
// ── Freshness Status ──────────────────────────────────────────────────────────
|
||||
export const FreshnessStatus = {
|
||||
FRESH: 'FRESH', // updated within 48h
|
||||
STALE: 'STALE', // 2–14 days old
|
||||
OUTDATED: 'OUTDATED', // > 14 days old
|
||||
} as const
|
||||
export type FreshnessStatus = typeof FreshnessStatus[keyof typeof FreshnessStatus]
|
||||
|
||||
/** @deprecated Use FreshnessStatus — kept for mock-data backward compatibility */
|
||||
export const DataFreshness = FreshnessStatus
|
||||
export type DataFreshness = FreshnessStatus
|
||||
|
||||
// ── Review Status ─────────────────────────────────────────────────────────────
|
||||
export const ReviewStatus = {
|
||||
UNREVIEWED: 'UNREVIEWED',
|
||||
IN_REVIEW: 'IN_REVIEW',
|
||||
APPROVED: 'APPROVED',
|
||||
REJECTED: 'REJECTED',
|
||||
FLAGGED: 'FLAGGED',
|
||||
} as const
|
||||
export type ReviewStatus = typeof ReviewStatus[keyof typeof ReviewStatus]
|
||||
|
||||
// ── Sensitivity Level ─────────────────────────────────────────────────────────
|
||||
export const SensitivityLevel = {
|
||||
PUBLIC: 'PUBLIC',
|
||||
INTERNAL: 'INTERNAL',
|
||||
CONFIDENTIAL: 'CONFIDENTIAL',
|
||||
RESTRICTED: 'RESTRICTED',
|
||||
} as const
|
||||
export type SensitivityLevel = typeof SensitivityLevel[keyof typeof SensitivityLevel]
|
||||
|
||||
// ── Shortlist Status ──────────────────────────────────────────────────────────
|
||||
export const ShortlistStatus = {
|
||||
DRAFT: 'DRAFT',
|
||||
REVIEW_READY: 'REVIEW_READY',
|
||||
FINALIZED: 'FINALIZED',
|
||||
ACTIVE: 'ACTIVE',
|
||||
SHARED: 'SHARED',
|
||||
ARCHIVED: 'ARCHIVED',
|
||||
CONVERTED: 'CONVERTED',
|
||||
} as const
|
||||
export type ShortlistStatus = typeof ShortlistStatus[keyof typeof ShortlistStatus]
|
||||
|
||||
// ── User Role ─────────────────────────────────────────────────────────────────
|
||||
export const UserRole = {
|
||||
SUPER_ADMIN: 'SUPER_ADMIN',
|
||||
ORGANIZATION_ADMIN: 'ORGANIZATION_ADMIN',
|
||||
PROPERTY_MANAGER: 'PROPERTY_MANAGER',
|
||||
REVIEWER: 'REVIEWER',
|
||||
OWNER_VIEWER: 'OWNER_VIEWER',
|
||||
DEMAND_USER: 'DEMAND_USER',
|
||||
} as const
|
||||
export type UserRole = typeof UserRole[keyof typeof UserRole]
|
||||
|
||||
// ── Signal Type ───────────────────────────────────────────────────────────────
|
||||
export const SignalType = {
|
||||
EXPANSION: 'EXPANSION',
|
||||
POSSIBLE_MOVE_OUT: 'POSSIBLE_MOVE_OUT',
|
||||
CONSTRUCTION_PROJECT: 'CONSTRUCTION_PROJECT',
|
||||
RESTRUCTURING: 'RESTRUCTURING',
|
||||
PROJECT_DEVELOPMENT: 'PROJECT_DEVELOPMENT',
|
||||
SPACE_CONSOLIDATION: 'SPACE_CONSOLIDATION',
|
||||
} as const
|
||||
export type SignalType = typeof SignalType[keyof typeof SignalType]
|
||||
|
||||
// ── Workspace ─────────────────────────────────────────────────────────────────
|
||||
export const WorkspaceType = {
|
||||
SUPPLY: 'SUPPLY',
|
||||
DEMAND: 'DEMAND',
|
||||
OPERATIONS: 'OPERATIONS',
|
||||
} as const
|
||||
export type WorkspaceType = typeof WorkspaceType[keyof typeof WorkspaceType]
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { SignalType, RiskLevel, ReviewStatus } from './enums'
|
||||
|
||||
export interface SignalSource {
|
||||
type: 'PRESS' | 'CONSTRUCTION_PERMIT' | 'JOB_POSTING' | 'COMPANY_REPORT' | 'MARKET_DATA' | 'MANUAL'
|
||||
url?: string
|
||||
publishedAt?: string
|
||||
credibility: 'LOW' | 'MEDIUM' | 'HIGH'
|
||||
}
|
||||
|
||||
export interface SignalEvidence {
|
||||
summary: string
|
||||
sourceUrls?: string[]
|
||||
extractedAt?: string
|
||||
}
|
||||
|
||||
export interface FutureSignal {
|
||||
id: string
|
||||
signalType: SignalType
|
||||
companyName?: string
|
||||
propertyId?: string
|
||||
locationHint: string
|
||||
areaSqmEstimate?: number
|
||||
probability: number
|
||||
confidenceScore: number
|
||||
timeHorizonMonths: number
|
||||
source: SignalSource
|
||||
sensitivityLevel: 'PUBLIC' | 'INTERNAL' | 'CONFIDENTIAL'
|
||||
disclaimer: string
|
||||
riskLevel: RiskLevel
|
||||
marketIndicator?: string
|
||||
relevanceScore?: number
|
||||
isVerified: boolean
|
||||
verifiedBy?: string
|
||||
verifiedAt?: string
|
||||
expiresAt?: string
|
||||
organizationId?: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
|
||||
// F004 additions
|
||||
title?: string // short human-readable headline
|
||||
evidence?: SignalEvidence // structured evidence block
|
||||
matchabilityScore?: number // 0–100: how well this signal can be matched to needs
|
||||
reviewStatus?: ReviewStatus
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
export * from './enums'
|
||||
export * from './property'
|
||||
export * from './need'
|
||||
export * from './match'
|
||||
export * from './futureSignal'
|
||||
export * from './aiOutput'
|
||||
export * from './activityEvent'
|
||||
export * from './unifiedResult'
|
||||
export * from './shortlist'
|
||||
export * from './review'
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import type { MatchStrength, MatchStatus, RiskLevel, ResultType, ConfidenceLevel } from './enums'
|
||||
|
||||
// ── Score Building Blocks ─────────────────────────────────────────────────────
|
||||
|
||||
export interface ScoreBreakdown {
|
||||
hardMatchScore: number // 0–100 hard criteria score
|
||||
softFactorScore: number // 0–100 soft factors score
|
||||
confidenceModifier: number // -20 to +5 adjustment
|
||||
dataQualityModifier: number // -15 to 0 adjustment
|
||||
totalScore: number // final 0–100
|
||||
}
|
||||
|
||||
export interface ScoreFactor {
|
||||
criterion: string
|
||||
weight: number // 0–1 relative weight
|
||||
score: number // 0–100
|
||||
contribution: number // weighted points added to total
|
||||
explanation: string
|
||||
}
|
||||
|
||||
// ── Explainability Types ──────────────────────────────────────────────────────
|
||||
|
||||
/** Canonical tradeoff type (F004) */
|
||||
export interface TradeOff {
|
||||
criterion: string
|
||||
concern: string
|
||||
severity: 'LOW' | 'MEDIUM' | 'HIGH'
|
||||
mitigation?: string
|
||||
impactOnScore?: number
|
||||
}
|
||||
|
||||
/** @deprecated Use TradeOff — kept for backward compatibility */
|
||||
export type Tradeoff = TradeOff
|
||||
|
||||
export interface Risk {
|
||||
category: string // e.g. "Datenverfügbarkeit", "Standortrisiko"
|
||||
description: string
|
||||
level: RiskLevel
|
||||
mitigation?: string
|
||||
}
|
||||
|
||||
export interface MissingDataItem {
|
||||
field: string
|
||||
importance: 'CRITICAL' | 'HIGH' | 'MEDIUM' | 'LOW'
|
||||
description: string
|
||||
impact: string // how it affects the match score
|
||||
}
|
||||
|
||||
export interface NextBestAction {
|
||||
label: string // e.g. "Besichtigung anfragen"
|
||||
description?: string
|
||||
priority: 'HIGH' | 'MEDIUM' | 'LOW'
|
||||
actionType: 'CONTACT' | 'VERIFY' | 'REVIEW' | 'SHORTLIST' | 'COMPARE' | 'SCHEDULE'
|
||||
externalUrl?: string
|
||||
}
|
||||
|
||||
export interface AlternativeStrategy {
|
||||
title: string
|
||||
description: string
|
||||
expectedScore?: number
|
||||
reasoning?: string
|
||||
}
|
||||
|
||||
// ── Match (core entity) ───────────────────────────────────────────────────────
|
||||
|
||||
export interface Match {
|
||||
id: string
|
||||
needId: string
|
||||
|
||||
// Result reference — works for all three result types
|
||||
resultId?: string // preferred: generic result ID
|
||||
resultType?: ResultType
|
||||
propertyId: string // legacy alias for resultId (VERIFIED_PORTFOLIO)
|
||||
|
||||
// Scoring
|
||||
matchScore: number // 0–100
|
||||
matchStrength: MatchStrength
|
||||
scoreBreakdown: ScoreBreakdown
|
||||
confidenceLevel: number // 0–1 numeric
|
||||
confidenceLevelLabel?: ConfidenceLevel
|
||||
dataConfidenceScore?: number // 0–1 data quality contribution
|
||||
|
||||
// Explainability
|
||||
positiveFactors: ScoreFactor[]
|
||||
negativeFactors: ScoreFactor[]
|
||||
tradeoffs: TradeOff[]
|
||||
tradeOffs?: TradeOff[] // alias for F004 naming convention
|
||||
risks?: Risk[]
|
||||
missingData?: MissingDataItem[]
|
||||
nextBestActions?: NextBestAction[]
|
||||
explainabilitySummary: string
|
||||
explanation?: string // alias / longer form
|
||||
|
||||
// Risk
|
||||
riskLevel: RiskLevel
|
||||
uncertaintyIndicators: string[]
|
||||
|
||||
// Alternatives
|
||||
alternativeStrategies?: AlternativeStrategy[]
|
||||
|
||||
// Review / lifecycle
|
||||
status?: MatchStatus
|
||||
isApproved?: boolean // legacy — prefer status
|
||||
reviewedBy?: string
|
||||
reviewedAt?: string
|
||||
organizationId?: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import type { AssetType, MatchStatus } from './enums'
|
||||
|
||||
export interface AreaRange {
|
||||
min: number
|
||||
max: number
|
||||
}
|
||||
|
||||
export interface BudgetRange {
|
||||
minPerSqm?: number
|
||||
maxPerSqm: number
|
||||
maxMonthlyTotal?: number
|
||||
currency: string
|
||||
}
|
||||
|
||||
export interface Timing {
|
||||
earliestMoveIn: string
|
||||
latestMoveIn: string
|
||||
contractDurationMonths?: number
|
||||
flexibleTiming: boolean
|
||||
}
|
||||
|
||||
export interface WeightingProfile {
|
||||
area: number
|
||||
location: number
|
||||
budget: number
|
||||
timing: number
|
||||
prestige: number
|
||||
accessibility: number
|
||||
expansionPotential: number
|
||||
flexibility: number
|
||||
[key: string]: number
|
||||
}
|
||||
|
||||
export interface SoftFactorPreferences {
|
||||
minPrestige?: number
|
||||
minAccessibility?: number
|
||||
requireParking?: boolean
|
||||
maxPublicTransportMinutes?: number
|
||||
preferredEsgRating?: string
|
||||
requireHighVisibility?: boolean
|
||||
}
|
||||
|
||||
export interface SizeRange {
|
||||
minSqm: number
|
||||
maxSqm: number
|
||||
}
|
||||
|
||||
export interface MustHaveCriterion {
|
||||
criterion: string
|
||||
description?: string
|
||||
weight?: number // 0–1, how much a miss hurts the score
|
||||
}
|
||||
|
||||
export interface WeightedPreference {
|
||||
criterion: string
|
||||
weight: number // 0–1
|
||||
idealValue?: string | number
|
||||
description?: string
|
||||
}
|
||||
|
||||
export interface Need {
|
||||
id: string
|
||||
companyName: string
|
||||
contactName?: string
|
||||
assetType: AssetType
|
||||
requiredArea: AreaRange
|
||||
|
||||
// F004 additions
|
||||
desiredLocation?: string[] // preferred city/district list
|
||||
sizeRange?: SizeRange // structured alias for requiredArea
|
||||
mustHaveCriteria?: MustHaveCriterion[]
|
||||
weightedPreferences?: WeightedPreference[]
|
||||
status?: MatchStatus | 'ACTIVE' | 'INACTIVE' | 'DRAFT' | 'CLOSED'
|
||||
|
||||
preferredLocations: string[]
|
||||
excludedLocations?: string[]
|
||||
budgetRange: BudgetRange
|
||||
timing: Timing
|
||||
mustCriteriaText?: string[] // legacy — prefer mustHaveCriteria
|
||||
softFactors?: SoftFactorPreferences
|
||||
weightingProfile: WeightingProfile
|
||||
confidenceInCriteria: number
|
||||
extractedFromText?: string
|
||||
notes?: string
|
||||
organizationId?: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export type CreateNeedInput = Omit<Need, 'id' | 'createdAt' | 'updatedAt'>
|
||||
export type UpdateNeedInput = Partial<CreateNeedInput>
|
||||
@@ -0,0 +1,69 @@
|
||||
import type { AssetType } from './enums'
|
||||
|
||||
export interface ParsedNeedCriteria {
|
||||
assetType?: AssetType
|
||||
areaRange?: { min: number; max: number }
|
||||
preferredLocations?: string[]
|
||||
budgetRange?: { maxPerSqm: number; maxMonthlyTotal?: number; currency: string }
|
||||
timing?: { earliestMoveIn: string; latestMoveIn?: string; contractDurationMonths?: number; flexibleTiming: boolean }
|
||||
mustHaveCriteria?: string[]
|
||||
softFactors?: { minPrestige?: number; requireParking?: boolean; maxPublicTransportMinutes?: number; requireHighVisibility?: boolean }
|
||||
infrastructureRequirements?: string[]
|
||||
accessibilityRequirements?: string[]
|
||||
prestigeImportance?: 'LOW' | 'MEDIUM' | 'HIGH'
|
||||
flexibilityNeed?: 'LOW' | 'MEDIUM' | 'HIGH'
|
||||
expansionPotential?: boolean
|
||||
parkingNeed?: boolean
|
||||
visibilityNeed?: 'LOW' | 'MEDIUM' | 'HIGH'
|
||||
footfallNeed?: 'LOW' | 'MEDIUM' | 'HIGH'
|
||||
companyName?: string
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export interface FollowUpQuestion {
|
||||
id: string
|
||||
questionText: string
|
||||
targetField: string
|
||||
reason: string
|
||||
suggestedAnswerOptions?: string[]
|
||||
importance: 'required' | 'recommended' | 'optional'
|
||||
}
|
||||
|
||||
export interface ParseNeedResult {
|
||||
extractedCriteria: ParsedNeedCriteria
|
||||
confidenceByField: Record<string, number>
|
||||
missingFields: string[]
|
||||
assumptions: string[]
|
||||
suggestedWeights: Record<string, number>
|
||||
followUpQuestionCandidates: FollowUpQuestion[]
|
||||
rawSummary: string
|
||||
promptVersion: string
|
||||
schemaVersion: string
|
||||
}
|
||||
|
||||
export const NeedBuilderStep = {
|
||||
IDLE: 'idle',
|
||||
PARSING: 'parsing',
|
||||
PARSED_REQUIRES_REVIEW: 'parsed_requires_review',
|
||||
CLARIFICATION_REQUIRED: 'clarification_required',
|
||||
WEIGHTING_REVIEW: 'weighting_review',
|
||||
READY_TO_SAVE: 'ready_to_save',
|
||||
SAVING: 'saving',
|
||||
SAVED: 'saved',
|
||||
ERROR: 'error',
|
||||
} as const
|
||||
export type NeedBuilderStep = typeof NeedBuilderStep[keyof typeof NeedBuilderStep]
|
||||
|
||||
export const WEIGHTING_KEYS = ['area', 'location', 'budget', 'timing', 'prestige', 'accessibility', 'expansionPotential', 'flexibility'] as const
|
||||
export type WeightingKey = typeof WEIGHTING_KEYS[number]
|
||||
|
||||
export const WEIGHTING_LABELS: Record<WeightingKey, string> = {
|
||||
area: 'Fläche',
|
||||
location: 'Standort',
|
||||
budget: 'Budget',
|
||||
timing: 'Verfügbarkeit',
|
||||
prestige: 'Prestige',
|
||||
accessibility: 'Erreichbarkeit',
|
||||
expansionPotential: 'Expansionspotenzial',
|
||||
flexibility: 'Flexibilität',
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import type {
|
||||
AssetType, ResultType, AvailabilityStatus, AvailabilityType,
|
||||
FreshnessStatus, RiskLevel, SourceType, DataQualityLevel,
|
||||
} from './enums'
|
||||
|
||||
// ── Location / Address ────────────────────────────────────────────────────────
|
||||
|
||||
export interface Location {
|
||||
city: string
|
||||
district?: string
|
||||
canton?: string
|
||||
region?: string
|
||||
country: string
|
||||
coordinates?: { lat: number; lng: number }
|
||||
}
|
||||
|
||||
export interface Address {
|
||||
street: string
|
||||
houseNumber: string
|
||||
postalCode: string
|
||||
city: string
|
||||
country: string
|
||||
}
|
||||
|
||||
// ── Source Metadata ───────────────────────────────────────────────────────────
|
||||
|
||||
export interface SourceMeta {
|
||||
sourceType: SourceType | string
|
||||
sourceLabel?: string
|
||||
sourceUrl?: string
|
||||
sourceUpdatedAt?: string
|
||||
externalId?: string
|
||||
}
|
||||
|
||||
// ── Data Quality ──────────────────────────────────────────────────────────────
|
||||
|
||||
export interface DataQuality {
|
||||
score: number
|
||||
qualityLevel?: DataQualityLevel
|
||||
missingCriticalFields: string[]
|
||||
missingOptionalFields: string[]
|
||||
lastVerifiedAt?: string
|
||||
freshness: FreshnessStatus
|
||||
warnings: string[]
|
||||
}
|
||||
|
||||
// ── Hard Facts ────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface PropertyHardFacts {
|
||||
floor?: number
|
||||
fitOut?: 'SHELL' | 'BASIC' | 'FULL' | 'PREMIUM'
|
||||
parking?: number
|
||||
publicTransportScore?: number
|
||||
usageType?: string
|
||||
ceilingHeightM?: number
|
||||
loadingDocksCount?: number
|
||||
powerSupplyKva?: number
|
||||
hasServerRoom?: boolean
|
||||
isBarrierFree?: boolean
|
||||
}
|
||||
|
||||
// ── Soft Factors ──────────────────────────────────────────────────────────────
|
||||
|
||||
export interface SoftFactors {
|
||||
prestigeScore?: number
|
||||
visibilityScore?: number
|
||||
footfallScore?: number
|
||||
commuterAccessScore?: number
|
||||
talentAccessScore?: number
|
||||
esgScore?: number
|
||||
flexibilityScore?: number
|
||||
expansionPotentialScore?: number
|
||||
taxEnvironmentScore?: number
|
||||
// Legacy aliases kept for mock-data backward compatibility
|
||||
prestige?: number
|
||||
accessibility?: number
|
||||
talentAccess?: number
|
||||
esgRating?: string
|
||||
passerbyFrequency?: 'LOW' | 'MEDIUM' | 'HIGH' | 'VERY_HIGH'
|
||||
parkingSpots?: number
|
||||
publicTransportMinutes?: number
|
||||
infrastructureNotes?: string
|
||||
}
|
||||
|
||||
// ── Property ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface Property {
|
||||
id: string
|
||||
organizationId?: string
|
||||
title: string
|
||||
|
||||
assetType: AssetType
|
||||
resultType: ResultType
|
||||
|
||||
location: Location
|
||||
address: Address
|
||||
|
||||
areaSqm: number
|
||||
areaSqmMin?: number
|
||||
areaSqmMax?: number
|
||||
|
||||
rentPricePerSqm: number
|
||||
rentChfSqmYear?: number
|
||||
totalRentMonthly?: number
|
||||
ancillaryCosts?: number
|
||||
|
||||
availabilityDate: string
|
||||
availabilityStatus: AvailabilityStatus
|
||||
availabilityType?: AvailabilityType
|
||||
|
||||
sourceType: string
|
||||
sourceLabel?: string
|
||||
sourceUrl?: string
|
||||
sourceUpdatedAt?: string
|
||||
sourceMeta?: SourceMeta
|
||||
|
||||
confidenceScore: number
|
||||
dataQuality: DataQuality
|
||||
|
||||
softFactors?: SoftFactors
|
||||
hardFacts?: PropertyHardFacts
|
||||
|
||||
// Legacy fields — kept for backward compat
|
||||
floorLevel?: number
|
||||
expansionPotentialSqm?: number
|
||||
|
||||
contractDurationMonths?: number
|
||||
riskLevel?: RiskLevel
|
||||
description?: string
|
||||
images?: string[]
|
||||
|
||||
status?: 'ACTIVE' | 'INACTIVE' | 'DRAFT' | 'ARCHIVED'
|
||||
lastReviewedAt?: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export type CreatePropertyInput = Omit<Property, 'id' | 'createdAt' | 'updatedAt'>
|
||||
export type UpdatePropertyInput = Partial<CreatePropertyInput>
|
||||
@@ -0,0 +1,79 @@
|
||||
// ── Entity Types ──────────────────────────────────────────────────────────────
|
||||
|
||||
export const ReviewEntityType = {
|
||||
FUTURE_SIGNAL: 'FUTURE_SIGNAL',
|
||||
MATCH_EXPLANATION: 'MATCH_EXPLANATION',
|
||||
LOW_CONFIDENCE_MATCH:'LOW_CONFIDENCE_MATCH',
|
||||
CONTACT_RELEASE: 'CONTACT_RELEASE',
|
||||
AI_OUTPUT: 'AI_OUTPUT',
|
||||
PROPERTY_DATA_ISSUE: 'PROPERTY_DATA_ISSUE',
|
||||
} as const
|
||||
export type ReviewEntityType = typeof ReviewEntityType[keyof typeof ReviewEntityType]
|
||||
|
||||
// ── Task Status ───────────────────────────────────────────────────────────────
|
||||
|
||||
export const ReviewTaskStatus = {
|
||||
PENDING: 'PENDING',
|
||||
IN_REVIEW: 'IN_REVIEW',
|
||||
APPROVED: 'APPROVED',
|
||||
REJECTED: 'REJECTED',
|
||||
NEEDS_MORE_DATA: 'NEEDS_MORE_DATA',
|
||||
ESCALATED: 'ESCALATED',
|
||||
} as const
|
||||
export type ReviewTaskStatus = typeof ReviewTaskStatus[keyof typeof ReviewTaskStatus]
|
||||
|
||||
// ── Priority ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export const ReviewPriority = {
|
||||
LOW: 'LOW',
|
||||
MEDIUM: 'MEDIUM',
|
||||
HIGH: 'HIGH',
|
||||
CRITICAL: 'CRITICAL',
|
||||
} as const
|
||||
export type ReviewPriority = typeof ReviewPriority[keyof typeof ReviewPriority]
|
||||
|
||||
// ── Note ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface ReviewNote {
|
||||
id: string
|
||||
content: string
|
||||
createdBy: string
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
// ── Task ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface ReviewTask {
|
||||
id: string
|
||||
entityType: ReviewEntityType
|
||||
entityId: string
|
||||
title: string
|
||||
description?: string
|
||||
priority: ReviewPriority
|
||||
status: ReviewTaskStatus
|
||||
assignedTo?: string
|
||||
createdBy: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
dueDate?: string
|
||||
reviewNotes: ReviewNote[]
|
||||
relatedOrganizationId?: string
|
||||
// Risk / confidence context
|
||||
confidenceScore?: number
|
||||
riskLevel?: string
|
||||
// AI-output context
|
||||
promptVersion?: string
|
||||
// Legacy compat (match-based items)
|
||||
matchId?: string
|
||||
needId?: string
|
||||
propertyId?: string
|
||||
matchScore?: number
|
||||
}
|
||||
|
||||
// ── Backward-compat aliases ───────────────────────────────────────────────────
|
||||
|
||||
export type ReviewQueueItem = ReviewTask
|
||||
export type ReviewQueueStatus = ReviewTaskStatus
|
||||
|
||||
/** @deprecated use ReviewTaskStatus */
|
||||
export const ReviewQueueStatus = ReviewTaskStatus
|
||||
@@ -0,0 +1,143 @@
|
||||
import type { ScoreFactor, TradeOff, Risk, MissingDataItem, NextBestAction } from './match'
|
||||
|
||||
// ── Hard Filter Thresholds ────────────────────────────────────────────────────
|
||||
|
||||
export const HARD_FILTER = {
|
||||
AREA_MIN_TOLERANCE: 0.85, // exclude if property < 85% of need's min area
|
||||
AREA_MAX_RATIO: 2.50, // severe penalty if property > 2.5× need's max area
|
||||
BUDGET_EXCLUSION_RATIO: 1.50, // exclude if rent > 150% of max budget/m²
|
||||
BUDGET_SEVERE_RATIO: 1.25, // severe penalty if 125–150% over budget
|
||||
BUDGET_MODERATE_RATIO: 1.10, // mild penalty if 110–125% over budget
|
||||
TIMING_GRACE_DAYS: 90, // allow ±90 days window flexibility
|
||||
} as const
|
||||
|
||||
// ── Score Architecture ────────────────────────────────────────────────────────
|
||||
|
||||
// Within each group, scores are weighted and normalized to 0–100.
|
||||
// Final = baseScore + dataQualityModifier + confidenceModifier, clamped 0–100.
|
||||
export const SCORE_SPLIT = {
|
||||
HARD_CRITERIA: 0.60, // expected contribution from hard criteria group
|
||||
SOFT_FACTORS: 0.40, // expected contribution from soft factors group
|
||||
} as const
|
||||
|
||||
export const HARD_CRITERION_KEYS = ['area', 'location', 'budget', 'timing'] as const
|
||||
export type HardCriterionKey = typeof HARD_CRITERION_KEYS[number]
|
||||
|
||||
export const SOFT_FACTOR_KEYS = [
|
||||
'prestige', 'accessibility', 'expansionPotential', 'flexibility',
|
||||
'visibility', 'footfall', 'talentAccess', 'esg', 'taxEnvironment',
|
||||
] as const
|
||||
export type SoftFactorKey = typeof SOFT_FACTOR_KEYS[number]
|
||||
|
||||
// ── Modifier Tables ───────────────────────────────────────────────────────────
|
||||
|
||||
export const DATA_QUALITY_MODIFIER = {
|
||||
EXCELLENT: +5, // dataQuality.score >= 0.85
|
||||
GOOD: 0, // >= 0.70
|
||||
FAIR: -5, // >= 0.55
|
||||
POOR: -10, // >= 0.40
|
||||
CRITICAL: -15, // < 0.40
|
||||
} as const
|
||||
|
||||
export const CONFIDENCE_MODIFIER = {
|
||||
VERIFIED_HIGH: +3, // VERIFIED_PORTFOLIO + confidenceScore >= 0.80
|
||||
VERIFIED_MEDIUM: 0, // VERIFIED_PORTFOLIO + confidenceScore < 0.80
|
||||
EXTERNAL_MARKET: -3, // EXTERNAL_MARKET result type
|
||||
FUTURE_AVAILABILITY: -15, // FUTURE_AVAILABILITY — never treat as confirmed availability
|
||||
LOW_CONFIDENCE: -10, // confidenceScore < 0.50 (stacks with above)
|
||||
} as const
|
||||
|
||||
// ── Scoring Weight Profile ────────────────────────────────────────────────────
|
||||
|
||||
export interface ScoringWeightProfile {
|
||||
// Hard criteria
|
||||
area: number
|
||||
location: number
|
||||
budget: number
|
||||
timing: number
|
||||
// Soft factors
|
||||
prestige: number
|
||||
accessibility: number
|
||||
expansionPotential: number
|
||||
flexibility: number
|
||||
visibility: number
|
||||
footfall: number
|
||||
talentAccess: number
|
||||
esg: number
|
||||
taxEnvironment: number
|
||||
[key: string]: number
|
||||
}
|
||||
|
||||
// ── Default Profiles per Asset Type ──────────────────────────────────────────
|
||||
// Each profile sums to 1.00. No magic numbers — weights reflect domain logic.
|
||||
|
||||
export const DEFAULT_SCORING_PROFILES: Record<string, ScoringWeightProfile> = {
|
||||
// Büro: ÖV-Anbindung, Talent Access, Prestige, ESG stark gewichtet
|
||||
OFFICE: {
|
||||
area: 0.18, location: 0.18, budget: 0.15, timing: 0.09,
|
||||
prestige: 0.07, accessibility: 0.11, expansionPotential: 0.05,
|
||||
flexibility: 0.05, visibility: 0.02, footfall: 0.01,
|
||||
talentAccess: 0.07, esg: 0.02, taxEnvironment: 0.00,
|
||||
},
|
||||
// Retail: Frequenz, Sichtbarkeit und Standort dominieren
|
||||
RETAIL: {
|
||||
area: 0.10, location: 0.15, budget: 0.13, timing: 0.06,
|
||||
prestige: 0.04, accessibility: 0.07, expansionPotential: 0.03,
|
||||
flexibility: 0.08, visibility: 0.14, footfall: 0.18,
|
||||
talentAccess: 0.01, esg: 0.01, taxEnvironment: 0.00,
|
||||
},
|
||||
// Light Industrial: Fläche, Andienung (accessibility), Infrastruktur
|
||||
LIGHT_INDUSTRIAL: {
|
||||
area: 0.20, location: 0.12, budget: 0.18, timing: 0.10,
|
||||
prestige: 0.01, accessibility: 0.14, expansionPotential: 0.07,
|
||||
flexibility: 0.05, visibility: 0.01, footfall: 0.00,
|
||||
talentAccess: 0.04, esg: 0.04, taxEnvironment: 0.04,
|
||||
},
|
||||
// Logistik: Autobahnanbindung (accessibility), Andienung, Fläche, Verfügbarkeit
|
||||
LOGISTICS: {
|
||||
area: 0.18, location: 0.18, budget: 0.14, timing: 0.13,
|
||||
prestige: 0.01, accessibility: 0.18, expansionPotential: 0.06,
|
||||
flexibility: 0.04, visibility: 0.01, footfall: 0.00,
|
||||
talentAccess: 0.02, esg: 0.02, taxEnvironment: 0.03,
|
||||
},
|
||||
PRODUCTION: {
|
||||
area: 0.22, location: 0.13, budget: 0.18, timing: 0.10,
|
||||
prestige: 0.01, accessibility: 0.13, expansionPotential: 0.08,
|
||||
flexibility: 0.04, visibility: 0.01, footfall: 0.00,
|
||||
talentAccess: 0.04, esg: 0.03, taxEnvironment: 0.03,
|
||||
},
|
||||
DEFAULT: {
|
||||
area: 0.20, location: 0.18, budget: 0.18, timing: 0.10,
|
||||
prestige: 0.05, accessibility: 0.09, expansionPotential: 0.05,
|
||||
flexibility: 0.05, visibility: 0.02, footfall: 0.02,
|
||||
talentAccess: 0.03, esg: 0.02, taxEnvironment: 0.01,
|
||||
},
|
||||
}
|
||||
|
||||
// ── Engine IO Types ───────────────────────────────────────────────────────────
|
||||
|
||||
export interface HardFilterResult {
|
||||
excluded: boolean
|
||||
reason?: string
|
||||
severePenalty: number // extra points deducted on top of criterion score (0–30)
|
||||
}
|
||||
|
||||
export interface MatchEngineOutput {
|
||||
propertyId: string
|
||||
needId: string
|
||||
excluded: boolean
|
||||
excludedReason?: string
|
||||
finalScore: number // 0–100 clamped
|
||||
hardMatchScore: number // 0–100 normalized
|
||||
softFactorScore: number // 0–100 normalized
|
||||
dataQualityModifier: number
|
||||
confidenceModifier: number
|
||||
positiveFactors: ScoreFactor[]
|
||||
negativeFactors: ScoreFactor[]
|
||||
allHardFactors: ScoreFactor[]
|
||||
allSoftFactors: ScoreFactor[]
|
||||
tradeOffs: TradeOff[]
|
||||
risks: Risk[]
|
||||
missingData: MissingDataItem[]
|
||||
nextBestActions: NextBestAction[]
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { ShortlistStatus } from './enums'
|
||||
|
||||
export interface ShortlistItem {
|
||||
resultId: string
|
||||
resultType: string
|
||||
title: string
|
||||
matchScore: number
|
||||
confidenceScore?: number
|
||||
dataQualityScore?: number
|
||||
sourceLabel?: string
|
||||
addedAt: string
|
||||
addedBy: string
|
||||
note?: string
|
||||
propertyId?: string
|
||||
}
|
||||
|
||||
export interface ShortlistItemInput {
|
||||
resultId: string
|
||||
resultType: string
|
||||
title: string
|
||||
matchScore: number
|
||||
confidenceScore?: number
|
||||
dataQualityScore?: number
|
||||
sourceLabel?: string
|
||||
addedBy: string
|
||||
note?: string
|
||||
propertyId?: string
|
||||
}
|
||||
|
||||
export interface Shortlist {
|
||||
id: string
|
||||
title: string
|
||||
description?: string
|
||||
needId?: string
|
||||
ownerUserId?: string
|
||||
decisionBriefId?: string
|
||||
items: ShortlistItem[]
|
||||
status: ShortlistStatus
|
||||
createdBy: string
|
||||
organizationId?: string
|
||||
sharedWith?: string[]
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export type CreateShortlistInput = Omit<Shortlist, 'id' | 'createdAt' | 'updatedAt'>
|
||||
export type UpdateShortlistInput = Partial<CreateShortlistInput>
|
||||
@@ -0,0 +1,125 @@
|
||||
// ── Pipeline Stages ───────────────────────────────────────────────────────────
|
||||
export const PipelineStage = {
|
||||
STAGE_1_RAW_EVIDENCE: 'STAGE_1_RAW_EVIDENCE',
|
||||
STAGE_2_NORMALIZED: 'STAGE_2_NORMALIZED',
|
||||
STAGE_3_ENRICHED: 'STAGE_3_ENRICHED',
|
||||
STAGE_4_REVIEW_CANDIDATE: 'STAGE_4_REVIEW_CANDIDATE',
|
||||
STAGE_5_APPROVED_FUTURE: 'STAGE_5_APPROVED_FUTURE',
|
||||
STAGE_6_MATCHABLE_RESULT: 'STAGE_6_MATCHABLE_RESULT',
|
||||
STAGE_7_STRATEGIC_INPUT: 'STAGE_7_STRATEGIC_INPUT',
|
||||
} as const
|
||||
export type PipelineStage = typeof PipelineStage[keyof typeof PipelineStage]
|
||||
|
||||
export const PIPELINE_STAGE_LABELS: Record<PipelineStage, string> = {
|
||||
STAGE_1_RAW_EVIDENCE: 'Rohe Markt-Evidenz',
|
||||
STAGE_2_NORMALIZED: 'Normalisiertes Signal',
|
||||
STAGE_3_ENRICHED: 'Angereichertes Signal',
|
||||
STAGE_4_REVIEW_CANDIDATE: 'Review-Kandidat',
|
||||
STAGE_5_APPROVED_FUTURE: 'Genehmigtes Future Signal',
|
||||
STAGE_6_MATCHABLE_RESULT: 'Matchbares Ergebnis',
|
||||
STAGE_7_STRATEGIC_INPUT: 'Strategischer Entscheidungs-Input',
|
||||
}
|
||||
|
||||
export const PIPELINE_STAGE_DESCRIPTIONS: Record<PipelineStage, string> = {
|
||||
STAGE_1_RAW_EVIDENCE: 'Rohe Evidenz aus Quellen gesammelt – noch unverarbeitet',
|
||||
STAGE_2_NORMALIZED: 'Daten normalisiert, Felder validiert und vereinheitlicht',
|
||||
STAGE_3_ENRICHED: 'Entitäten extrahiert, Kontext angereichert und bewertet',
|
||||
STAGE_4_REVIEW_CANDIDATE: 'Signal bereit für manuellen Analyst-Review',
|
||||
STAGE_5_APPROVED_FUTURE: 'Genehmigt als Future Availability Signal – intern sichtbar',
|
||||
STAGE_6_MATCHABLE_RESULT: 'Im Unified Result Feed – Match-Engine nutzbar',
|
||||
STAGE_7_STRATEGIC_INPUT: 'Eingang in Decision Briefs und strategische Analyse',
|
||||
}
|
||||
|
||||
export const PIPELINE_STAGE_ORDER: PipelineStage[] = [
|
||||
'STAGE_1_RAW_EVIDENCE',
|
||||
'STAGE_2_NORMALIZED',
|
||||
'STAGE_3_ENRICHED',
|
||||
'STAGE_4_REVIEW_CANDIDATE',
|
||||
'STAGE_5_APPROVED_FUTURE',
|
||||
'STAGE_6_MATCHABLE_RESULT',
|
||||
'STAGE_7_STRATEGIC_INPUT',
|
||||
]
|
||||
|
||||
// ── Gate Types ────────────────────────────────────────────────────────────────
|
||||
export const GateType = {
|
||||
EVIDENCE_GATE: 'EVIDENCE_GATE',
|
||||
CONFIDENCE_GATE: 'CONFIDENCE_GATE',
|
||||
SENSITIVITY_GATE: 'SENSITIVITY_GATE',
|
||||
REVIEW_GATE: 'REVIEW_GATE',
|
||||
MATCHABILITY_GATE: 'MATCHABILITY_GATE',
|
||||
FEED_ELIGIBILITY_GATE: 'FEED_ELIGIBILITY_GATE',
|
||||
} as const
|
||||
export type GateType = typeof GateType[keyof typeof GateType]
|
||||
|
||||
export const GATE_LABELS: Record<GateType, string> = {
|
||||
EVIDENCE_GATE: 'Evidenz-Gate',
|
||||
CONFIDENCE_GATE: 'Konfidenz-Gate',
|
||||
SENSITIVITY_GATE: 'Sensitivitäts-Gate',
|
||||
REVIEW_GATE: 'Review-Gate',
|
||||
MATCHABILITY_GATE: 'Matchbarkeits-Gate',
|
||||
FEED_ELIGIBILITY_GATE: 'Feed-Eignung',
|
||||
}
|
||||
|
||||
// ── Gate Status ───────────────────────────────────────────────────────────────
|
||||
export const GateStatus = {
|
||||
PASSED: 'PASSED',
|
||||
FAILED: 'FAILED',
|
||||
PENDING: 'PENDING',
|
||||
BLOCKED: 'BLOCKED',
|
||||
SKIPPED: 'SKIPPED',
|
||||
} as const
|
||||
export type GateStatus = typeof GateStatus[keyof typeof GateStatus]
|
||||
|
||||
export const GATE_STATUS_LABELS: Record<GateStatus, string> = {
|
||||
PASSED: 'Bestanden',
|
||||
FAILED: 'Fehlgeschlagen',
|
||||
PENDING: 'Ausstehend',
|
||||
BLOCKED: 'Blockiert',
|
||||
SKIPPED: 'Übersprungen',
|
||||
}
|
||||
|
||||
export const GATE_STATUS_COLORS: Record<GateStatus, { bg: string; fg: string }> = {
|
||||
PASSED: { bg: 'rgba(22,163,74,0.1)', fg: '#15803d' },
|
||||
FAILED: { bg: 'rgba(239,68,68,0.1)', fg: '#dc2626' },
|
||||
PENDING: { bg: 'rgba(234,179,8,0.1)', fg: '#a16207' },
|
||||
BLOCKED: { bg: 'rgba(239,68,68,0.08)', fg: '#dc2626' },
|
||||
SKIPPED: { bg: 'rgba(148,163,184,0.1)', fg: '#64748b' },
|
||||
}
|
||||
|
||||
// ── Interfaces ────────────────────────────────────────────────────────────────
|
||||
export interface GateCheck {
|
||||
label: string
|
||||
passed: boolean
|
||||
value?: string
|
||||
note?: string
|
||||
}
|
||||
|
||||
export interface GateEvaluation {
|
||||
gateType: GateType
|
||||
status: GateStatus
|
||||
reason: string
|
||||
nextAction?: string
|
||||
evaluatedAt: string
|
||||
checks: GateCheck[]
|
||||
}
|
||||
|
||||
export interface PipelineState {
|
||||
signalId: string
|
||||
currentStage: PipelineStage
|
||||
gates: Record<GateType, GateEvaluation>
|
||||
overallEligible: boolean
|
||||
publishedToFutureAvailability: boolean
|
||||
publishedAt?: string
|
||||
feedDisclaimer?: string
|
||||
}
|
||||
|
||||
export interface AuditTrailEntry {
|
||||
id: string
|
||||
signalId: string
|
||||
timestamp: string
|
||||
stage: PipelineStage
|
||||
action: string
|
||||
performedBy: string
|
||||
details: string
|
||||
gateType?: GateType
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { ResultType } from './enums'
|
||||
import type { Property } from './property'
|
||||
import type { FutureSignal } from './futureSignal'
|
||||
import type { Match } from './match'
|
||||
|
||||
// ── Unified Match Result (discriminated union) ────────────────────────────────
|
||||
// Wraps the three result types so UI components can handle all three paths
|
||||
// without type confusion. Discriminate on `resultType`.
|
||||
|
||||
interface UnifiedResultBase {
|
||||
matchId: string
|
||||
needId: string
|
||||
matchScore: number
|
||||
resultType: ResultType
|
||||
}
|
||||
|
||||
export interface VerifiedPortfolioResult extends UnifiedResultBase {
|
||||
resultType: 'VERIFIED_PORTFOLIO'
|
||||
property: Property
|
||||
match: Match
|
||||
}
|
||||
|
||||
export interface ExternalMarketResult extends UnifiedResultBase {
|
||||
resultType: 'EXTERNAL_MARKET'
|
||||
property: Property
|
||||
match: Match
|
||||
}
|
||||
|
||||
export interface FutureAvailabilityResult extends UnifiedResultBase {
|
||||
resultType: 'FUTURE_AVAILABILITY'
|
||||
signal: FutureSignal
|
||||
match: Match
|
||||
}
|
||||
|
||||
export type UnifiedMatchResult =
|
||||
| VerifiedPortfolioResult
|
||||
| ExternalMarketResult
|
||||
| FutureAvailabilityResult
|
||||
Reference in New Issue
Block a user