feat(domain): F004 – expand domain model layer
- need.ts: add SizeRange, MustHaveCriterion, WeightedPreference types; extend Need with desiredLocation, sizeRange, mustHaveCriteria, weightedPreferences, status - futureSignal.ts: add SignalEvidence type; extend FutureSignal with title, evidence, matchabilityScore, reviewStatus - aiOutput.ts: new AIOutput entity with type, inputHash, outputJson, provider/model/promptVersion/schemaVersion, reviewStatus - activityEvent.ts: new ActivityEvent entity with actorId, action, entityType, entityId, timestamp, metadata - unifiedResult.ts: discriminated union (VerifiedPortfolioResult | ExternalMarketResult | FutureAvailabilityResult) - index.ts: export all three new files - Properties.tsx: add LIGHT_INDUSTRIAL + UNKNOWN cases to exhaustive switches 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,25 @@
|
||||
import type { ReviewStatus } from './enums'
|
||||
|
||||
export const AIOutputType = {
|
||||
MATCH_SCORE: 'MATCH_SCORE',
|
||||
EXPLAINABILITY: 'EXPLAINABILITY',
|
||||
SIGNAL_EXTRACTION: 'SIGNAL_EXTRACTION',
|
||||
NEED_PARSING: 'NEED_PARSING',
|
||||
SUMMARY: 'SUMMARY',
|
||||
RECOMMENDATION: 'RECOMMENDATION',
|
||||
} as const
|
||||
export type AIOutputType = typeof AIOutputType[keyof typeof AIOutputType]
|
||||
|
||||
export interface AIOutput {
|
||||
id: string
|
||||
type: AIOutputType
|
||||
inputHash: string // hash of the input for cache/dedup
|
||||
outputJson: unknown // raw output — typed per consumer
|
||||
provider: string // e.g. "openai", "anthropic"
|
||||
model: string // e.g. "gpt-4o", "claude-3-5-sonnet"
|
||||
promptVersion: string // semver of the prompt template used
|
||||
schemaVersion: string // semver of expected output schema
|
||||
createdAt: string
|
||||
reviewedBy?: string
|
||||
reviewStatus?: ReviewStatus
|
||||
}
|
||||
+101
-6
@@ -1,13 +1,17 @@
|
||||
// ── Asset Types ───────────────────────────────────────────────────────────────
|
||||
export const AssetType = {
|
||||
OFFICE: 'OFFICE',
|
||||
RETAIL: 'RETAIL',
|
||||
GASTRO: 'GASTRO',
|
||||
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',
|
||||
@@ -15,6 +19,7 @@ export const ResultType = {
|
||||
} as const
|
||||
export type ResultType = typeof ResultType[keyof typeof ResultType]
|
||||
|
||||
// ── Match Strength ─────────────────────────────────────────────────────────────
|
||||
export const MatchStrength = {
|
||||
STRONG: 'STRONG',
|
||||
MODERATE: 'MODERATE',
|
||||
@@ -22,6 +27,17 @@ export const MatchStrength = {
|
||||
} 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',
|
||||
@@ -30,6 +46,26 @@ export const RiskLevel = {
|
||||
} 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',
|
||||
@@ -39,13 +75,70 @@ export const AvailabilityStatus = {
|
||||
} as const
|
||||
export type AvailabilityStatus = typeof AvailabilityStatus[keyof typeof AvailabilityStatus]
|
||||
|
||||
export const DataFreshness = {
|
||||
FRESH: 'FRESH',
|
||||
STALE: 'STALE',
|
||||
OUTDATED: 'OUTDATED',
|
||||
// ── 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 DataFreshness = typeof DataFreshness[keyof typeof DataFreshness]
|
||||
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 = {
|
||||
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',
|
||||
@@ -56,6 +149,7 @@ export const UserRole = {
|
||||
} as const
|
||||
export type UserRole = typeof UserRole[keyof typeof UserRole]
|
||||
|
||||
// ── Signal Type ───────────────────────────────────────────────────────────────
|
||||
export const SignalType = {
|
||||
EXPANSION: 'EXPANSION',
|
||||
POSSIBLE_MOVE_OUT: 'POSSIBLE_MOVE_OUT',
|
||||
@@ -66,6 +160,7 @@ export const SignalType = {
|
||||
} as const
|
||||
export type SignalType = typeof SignalType[keyof typeof SignalType]
|
||||
|
||||
// ── Workspace ─────────────────────────────────────────────────────────────────
|
||||
export const WorkspaceType = {
|
||||
SUPPLY: 'SUPPLY',
|
||||
DEMAND: 'DEMAND',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { SignalType, RiskLevel } from './enums'
|
||||
import type { SignalType, RiskLevel, ReviewStatus } from './enums'
|
||||
|
||||
export interface SignalSource {
|
||||
type: 'PRESS' | 'CONSTRUCTION_PERMIT' | 'JOB_POSTING' | 'COMPANY_REPORT' | 'MARKET_DATA' | 'MANUAL'
|
||||
@@ -7,6 +7,12 @@ export interface SignalSource {
|
||||
credibility: 'LOW' | 'MEDIUM' | 'HIGH'
|
||||
}
|
||||
|
||||
export interface SignalEvidence {
|
||||
summary: string
|
||||
sourceUrls?: string[]
|
||||
extractedAt?: string
|
||||
}
|
||||
|
||||
export interface FutureSignal {
|
||||
id: string
|
||||
signalType: SignalType
|
||||
@@ -30,4 +36,10 @@ export interface FutureSignal {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -3,3 +3,6 @@ export * from './property'
|
||||
export * from './need'
|
||||
export * from './match'
|
||||
export * from './futureSignal'
|
||||
export * from './aiOutput'
|
||||
export * from './activityEvent'
|
||||
export * from './unifiedResult'
|
||||
|
||||
+71
-15
@@ -1,52 +1,108 @@
|
||||
import type { MatchStrength, RiskLevel } from './enums'
|
||||
import type { MatchStrength, MatchStatus, RiskLevel, ResultType, ConfidenceLevel } from './enums'
|
||||
|
||||
// ── Score Building Blocks ─────────────────────────────────────────────────────
|
||||
|
||||
export interface ScoreBreakdown {
|
||||
hardMatchScore: number
|
||||
softFactorScore: number
|
||||
confidenceModifier: number
|
||||
dataQualityModifier: number
|
||||
totalScore: number
|
||||
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
|
||||
score: number
|
||||
contribution: number
|
||||
weight: number // 0–1 relative weight
|
||||
score: number // 0–100
|
||||
contribution: number // weighted points added to total
|
||||
explanation: string
|
||||
}
|
||||
|
||||
export interface Tradeoff {
|
||||
// ── 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
|
||||
propertyId: string
|
||||
needId: string
|
||||
matchScore: number
|
||||
|
||||
// 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[]
|
||||
tradeOffs?: TradeOff[] // alias for F004 naming convention
|
||||
risks?: Risk[]
|
||||
missingData?: MissingDataItem[]
|
||||
nextBestActions?: NextBestAction[]
|
||||
explainabilitySummary: string
|
||||
confidenceLevel: number
|
||||
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
|
||||
isApproved?: boolean
|
||||
organizationId?: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
|
||||
+28
-2
@@ -1,4 +1,4 @@
|
||||
import type { AssetType } from './enums'
|
||||
import type { AssetType, MatchStatus } from './enums'
|
||||
|
||||
export interface AreaRange {
|
||||
min: number
|
||||
@@ -40,17 +40,43 @@ export interface SoftFactorPreferences {
|
||||
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[]
|
||||
mustCriteriaText?: string[] // legacy — prefer mustHaveCriteria
|
||||
softFactors?: SoftFactorPreferences
|
||||
weightingProfile: WeightingProfile
|
||||
confidenceInCriteria: number
|
||||
|
||||
+82
-16
@@ -1,14 +1,17 @@
|
||||
import type { AssetType, ResultType, AvailabilityStatus, DataFreshness, RiskLevel } from './enums'
|
||||
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
|
||||
}
|
||||
coordinates?: { lat: number; lng: number }
|
||||
}
|
||||
|
||||
export interface Address {
|
||||
@@ -19,10 +22,58 @@ export interface Address {
|
||||
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
|
||||
visibilityScore?: number
|
||||
talentAccess?: number
|
||||
esgRating?: string
|
||||
passerbyFrequency?: 'LOW' | 'MEDIUM' | 'HIGH' | 'VERY_HIGH'
|
||||
@@ -31,40 +82,55 @@ export interface SoftFactors {
|
||||
infrastructureNotes?: string
|
||||
}
|
||||
|
||||
export interface DataQuality {
|
||||
score: number
|
||||
missingCriticalFields: string[]
|
||||
missingOptionalFields: string[]
|
||||
lastVerifiedAt?: string
|
||||
freshness: DataFreshness
|
||||
warnings: 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
|
||||
ancillaryCosts?: number
|
||||
riskLevel?: RiskLevel
|
||||
description?: string
|
||||
images?: string[]
|
||||
organizationId?: string
|
||||
|
||||
status?: 'ACTIVE' | 'INACTIVE' | 'DRAFT' | 'ARCHIVED'
|
||||
lastReviewedAt?: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -30,6 +30,8 @@ function getAssetTypeLabel(type: AssetType): string {
|
||||
case AssetType.GASTRO: return 'Gastro'
|
||||
case AssetType.PRODUCTION: return 'Produktion'
|
||||
case AssetType.MIXED: return 'Gemischt'
|
||||
case AssetType.LIGHT_INDUSTRIAL: return 'Leichtindustrie'
|
||||
case AssetType.UNKNOWN: return 'Unbekannt'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +43,8 @@ function getAssetTypeColor(type: AssetType): string {
|
||||
case AssetType.GASTRO: return '#0d9488'
|
||||
case AssetType.PRODUCTION: return '#92400e'
|
||||
case AssetType.MIXED: return '#6b7280'
|
||||
case AssetType.LIGHT_INDUSTRIAL: return '#b45309'
|
||||
case AssetType.UNKNOWN: return '#9ca3af'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user