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 = {
|
export const AssetType = {
|
||||||
OFFICE: 'OFFICE',
|
OFFICE: 'OFFICE',
|
||||||
RETAIL: 'RETAIL',
|
RETAIL: 'RETAIL',
|
||||||
GASTRO: 'GASTRO',
|
GASTRO: 'GASTRO', // legacy – kept for mock-data compatibility
|
||||||
|
LIGHT_INDUSTRIAL: 'LIGHT_INDUSTRIAL',
|
||||||
LOGISTICS: 'LOGISTICS',
|
LOGISTICS: 'LOGISTICS',
|
||||||
PRODUCTION: 'PRODUCTION',
|
PRODUCTION: 'PRODUCTION',
|
||||||
MIXED: 'MIXED',
|
MIXED: 'MIXED',
|
||||||
|
UNKNOWN: 'UNKNOWN',
|
||||||
} as const
|
} as const
|
||||||
export type AssetType = typeof AssetType[keyof typeof AssetType]
|
export type AssetType = typeof AssetType[keyof typeof AssetType]
|
||||||
|
|
||||||
|
// ── Result Types ──────────────────────────────────────────────────────────────
|
||||||
export const ResultType = {
|
export const ResultType = {
|
||||||
VERIFIED_PORTFOLIO: 'VERIFIED_PORTFOLIO',
|
VERIFIED_PORTFOLIO: 'VERIFIED_PORTFOLIO',
|
||||||
EXTERNAL_MARKET: 'EXTERNAL_MARKET',
|
EXTERNAL_MARKET: 'EXTERNAL_MARKET',
|
||||||
@@ -15,6 +19,7 @@ export const ResultType = {
|
|||||||
} as const
|
} as const
|
||||||
export type ResultType = typeof ResultType[keyof typeof ResultType]
|
export type ResultType = typeof ResultType[keyof typeof ResultType]
|
||||||
|
|
||||||
|
// ── Match Strength ─────────────────────────────────────────────────────────────
|
||||||
export const MatchStrength = {
|
export const MatchStrength = {
|
||||||
STRONG: 'STRONG',
|
STRONG: 'STRONG',
|
||||||
MODERATE: 'MODERATE',
|
MODERATE: 'MODERATE',
|
||||||
@@ -22,6 +27,17 @@ export const MatchStrength = {
|
|||||||
} as const
|
} as const
|
||||||
export type MatchStrength = typeof MatchStrength[keyof typeof MatchStrength]
|
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 = {
|
export const RiskLevel = {
|
||||||
LOW: 'LOW',
|
LOW: 'LOW',
|
||||||
MEDIUM: 'MEDIUM',
|
MEDIUM: 'MEDIUM',
|
||||||
@@ -30,6 +46,26 @@ export const RiskLevel = {
|
|||||||
} as const
|
} as const
|
||||||
export type RiskLevel = typeof RiskLevel[keyof typeof RiskLevel]
|
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 = {
|
export const AvailabilityStatus = {
|
||||||
AVAILABLE_NOW: 'AVAILABLE_NOW',
|
AVAILABLE_NOW: 'AVAILABLE_NOW',
|
||||||
AVAILABLE_SOON: 'AVAILABLE_SOON',
|
AVAILABLE_SOON: 'AVAILABLE_SOON',
|
||||||
@@ -39,13 +75,70 @@ export const AvailabilityStatus = {
|
|||||||
} as const
|
} as const
|
||||||
export type AvailabilityStatus = typeof AvailabilityStatus[keyof typeof AvailabilityStatus]
|
export type AvailabilityStatus = typeof AvailabilityStatus[keyof typeof AvailabilityStatus]
|
||||||
|
|
||||||
export const DataFreshness = {
|
// ── Availability Type (structural distinction) ────────────────────────────────
|
||||||
FRESH: 'FRESH',
|
export const AvailabilityType = {
|
||||||
STALE: 'STALE',
|
CONFIRMED: 'CONFIRMED', // verified, date known
|
||||||
OUTDATED: 'OUTDATED',
|
INDICATIVE: 'INDICATIVE', // external listing, unconfirmed
|
||||||
|
PROBABILISTIC: 'PROBABILISTIC', // AI signal, no confirmed date
|
||||||
} as const
|
} 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 = {
|
export const UserRole = {
|
||||||
SUPER_ADMIN: 'SUPER_ADMIN',
|
SUPER_ADMIN: 'SUPER_ADMIN',
|
||||||
ORGANIZATION_ADMIN: 'ORGANIZATION_ADMIN',
|
ORGANIZATION_ADMIN: 'ORGANIZATION_ADMIN',
|
||||||
@@ -56,6 +149,7 @@ export const UserRole = {
|
|||||||
} as const
|
} as const
|
||||||
export type UserRole = typeof UserRole[keyof typeof UserRole]
|
export type UserRole = typeof UserRole[keyof typeof UserRole]
|
||||||
|
|
||||||
|
// ── Signal Type ───────────────────────────────────────────────────────────────
|
||||||
export const SignalType = {
|
export const SignalType = {
|
||||||
EXPANSION: 'EXPANSION',
|
EXPANSION: 'EXPANSION',
|
||||||
POSSIBLE_MOVE_OUT: 'POSSIBLE_MOVE_OUT',
|
POSSIBLE_MOVE_OUT: 'POSSIBLE_MOVE_OUT',
|
||||||
@@ -66,6 +160,7 @@ export const SignalType = {
|
|||||||
} as const
|
} as const
|
||||||
export type SignalType = typeof SignalType[keyof typeof SignalType]
|
export type SignalType = typeof SignalType[keyof typeof SignalType]
|
||||||
|
|
||||||
|
// ── Workspace ─────────────────────────────────────────────────────────────────
|
||||||
export const WorkspaceType = {
|
export const WorkspaceType = {
|
||||||
SUPPLY: 'SUPPLY',
|
SUPPLY: 'SUPPLY',
|
||||||
DEMAND: 'DEMAND',
|
DEMAND: 'DEMAND',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { SignalType, RiskLevel } from './enums'
|
import type { SignalType, RiskLevel, ReviewStatus } from './enums'
|
||||||
|
|
||||||
export interface SignalSource {
|
export interface SignalSource {
|
||||||
type: 'PRESS' | 'CONSTRUCTION_PERMIT' | 'JOB_POSTING' | 'COMPANY_REPORT' | 'MARKET_DATA' | 'MANUAL'
|
type: 'PRESS' | 'CONSTRUCTION_PERMIT' | 'JOB_POSTING' | 'COMPANY_REPORT' | 'MARKET_DATA' | 'MANUAL'
|
||||||
@@ -7,6 +7,12 @@ export interface SignalSource {
|
|||||||
credibility: 'LOW' | 'MEDIUM' | 'HIGH'
|
credibility: 'LOW' | 'MEDIUM' | 'HIGH'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SignalEvidence {
|
||||||
|
summary: string
|
||||||
|
sourceUrls?: string[]
|
||||||
|
extractedAt?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface FutureSignal {
|
export interface FutureSignal {
|
||||||
id: string
|
id: string
|
||||||
signalType: SignalType
|
signalType: SignalType
|
||||||
@@ -30,4 +36,10 @@ export interface FutureSignal {
|
|||||||
organizationId?: string
|
organizationId?: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
updatedAt: 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 './need'
|
||||||
export * from './match'
|
export * from './match'
|
||||||
export * from './futureSignal'
|
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 {
|
export interface ScoreBreakdown {
|
||||||
hardMatchScore: number
|
hardMatchScore: number // 0–100 hard criteria score
|
||||||
softFactorScore: number
|
softFactorScore: number // 0–100 soft factors score
|
||||||
confidenceModifier: number
|
confidenceModifier: number // -20 to +5 adjustment
|
||||||
dataQualityModifier: number
|
dataQualityModifier: number // -15 to 0 adjustment
|
||||||
totalScore: number
|
totalScore: number // final 0–100
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScoreFactor {
|
export interface ScoreFactor {
|
||||||
criterion: string
|
criterion: string
|
||||||
weight: number
|
weight: number // 0–1 relative weight
|
||||||
score: number
|
score: number // 0–100
|
||||||
contribution: number
|
contribution: number // weighted points added to total
|
||||||
explanation: string
|
explanation: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Tradeoff {
|
// ── Explainability Types ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Canonical tradeoff type (F004) */
|
||||||
|
export interface TradeOff {
|
||||||
criterion: string
|
criterion: string
|
||||||
concern: string
|
concern: string
|
||||||
severity: 'LOW' | 'MEDIUM' | 'HIGH'
|
severity: 'LOW' | 'MEDIUM' | 'HIGH'
|
||||||
mitigation?: string
|
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 {
|
export interface AlternativeStrategy {
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
expectedScore?: number
|
expectedScore?: number
|
||||||
|
reasoning?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Match (core entity) ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
export interface Match {
|
export interface Match {
|
||||||
id: string
|
id: string
|
||||||
propertyId: string
|
|
||||||
needId: 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
|
matchStrength: MatchStrength
|
||||||
scoreBreakdown: ScoreBreakdown
|
scoreBreakdown: ScoreBreakdown
|
||||||
|
confidenceLevel: number // 0–1 numeric
|
||||||
|
confidenceLevelLabel?: ConfidenceLevel
|
||||||
|
dataConfidenceScore?: number // 0–1 data quality contribution
|
||||||
|
|
||||||
|
// Explainability
|
||||||
positiveFactors: ScoreFactor[]
|
positiveFactors: ScoreFactor[]
|
||||||
negativeFactors: ScoreFactor[]
|
negativeFactors: ScoreFactor[]
|
||||||
tradeoffs: Tradeoff[]
|
tradeoffs: TradeOff[]
|
||||||
|
tradeOffs?: TradeOff[] // alias for F004 naming convention
|
||||||
|
risks?: Risk[]
|
||||||
|
missingData?: MissingDataItem[]
|
||||||
|
nextBestActions?: NextBestAction[]
|
||||||
explainabilitySummary: string
|
explainabilitySummary: string
|
||||||
confidenceLevel: number
|
explanation?: string // alias / longer form
|
||||||
|
|
||||||
|
// Risk
|
||||||
riskLevel: RiskLevel
|
riskLevel: RiskLevel
|
||||||
uncertaintyIndicators: string[]
|
uncertaintyIndicators: string[]
|
||||||
|
|
||||||
|
// Alternatives
|
||||||
alternativeStrategies?: AlternativeStrategy[]
|
alternativeStrategies?: AlternativeStrategy[]
|
||||||
|
|
||||||
|
// Review / lifecycle
|
||||||
|
status?: MatchStatus
|
||||||
|
isApproved?: boolean // legacy — prefer status
|
||||||
reviewedBy?: string
|
reviewedBy?: string
|
||||||
reviewedAt?: string
|
reviewedAt?: string
|
||||||
isApproved?: boolean
|
|
||||||
organizationId?: string
|
organizationId?: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
|
|||||||
+28
-2
@@ -1,4 +1,4 @@
|
|||||||
import type { AssetType } from './enums'
|
import type { AssetType, MatchStatus } from './enums'
|
||||||
|
|
||||||
export interface AreaRange {
|
export interface AreaRange {
|
||||||
min: number
|
min: number
|
||||||
@@ -40,17 +40,43 @@ export interface SoftFactorPreferences {
|
|||||||
requireHighVisibility?: boolean
|
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 {
|
export interface Need {
|
||||||
id: string
|
id: string
|
||||||
companyName: string
|
companyName: string
|
||||||
contactName?: string
|
contactName?: string
|
||||||
assetType: AssetType
|
assetType: AssetType
|
||||||
requiredArea: AreaRange
|
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[]
|
preferredLocations: string[]
|
||||||
excludedLocations?: string[]
|
excludedLocations?: string[]
|
||||||
budgetRange: BudgetRange
|
budgetRange: BudgetRange
|
||||||
timing: Timing
|
timing: Timing
|
||||||
mustCriteriaText?: string[]
|
mustCriteriaText?: string[] // legacy — prefer mustHaveCriteria
|
||||||
softFactors?: SoftFactorPreferences
|
softFactors?: SoftFactorPreferences
|
||||||
weightingProfile: WeightingProfile
|
weightingProfile: WeightingProfile
|
||||||
confidenceInCriteria: number
|
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 {
|
export interface Location {
|
||||||
city: string
|
city: string
|
||||||
district?: string
|
district?: string
|
||||||
canton?: string
|
canton?: string
|
||||||
|
region?: string
|
||||||
country: string
|
country: string
|
||||||
coordinates?: {
|
coordinates?: { lat: number; lng: number }
|
||||||
lat: number
|
|
||||||
lng: number
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Address {
|
export interface Address {
|
||||||
@@ -19,10 +22,58 @@ export interface Address {
|
|||||||
country: 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 {
|
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
|
prestige?: number
|
||||||
accessibility?: number
|
accessibility?: number
|
||||||
visibilityScore?: number
|
|
||||||
talentAccess?: number
|
talentAccess?: number
|
||||||
esgRating?: string
|
esgRating?: string
|
||||||
passerbyFrequency?: 'LOW' | 'MEDIUM' | 'HIGH' | 'VERY_HIGH'
|
passerbyFrequency?: 'LOW' | 'MEDIUM' | 'HIGH' | 'VERY_HIGH'
|
||||||
@@ -31,40 +82,55 @@ export interface SoftFactors {
|
|||||||
infrastructureNotes?: string
|
infrastructureNotes?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataQuality {
|
// ── Property ──────────────────────────────────────────────────────────────────
|
||||||
score: number
|
|
||||||
missingCriticalFields: string[]
|
|
||||||
missingOptionalFields: string[]
|
|
||||||
lastVerifiedAt?: string
|
|
||||||
freshness: DataFreshness
|
|
||||||
warnings: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Property {
|
export interface Property {
|
||||||
id: string
|
id: string
|
||||||
|
organizationId?: string
|
||||||
title: string
|
title: string
|
||||||
|
|
||||||
assetType: AssetType
|
assetType: AssetType
|
||||||
resultType: ResultType
|
resultType: ResultType
|
||||||
|
|
||||||
location: Location
|
location: Location
|
||||||
address: Address
|
address: Address
|
||||||
|
|
||||||
areaSqm: number
|
areaSqm: number
|
||||||
|
areaSqmMin?: number
|
||||||
|
areaSqmMax?: number
|
||||||
|
|
||||||
rentPricePerSqm: number
|
rentPricePerSqm: number
|
||||||
|
rentChfSqmYear?: number
|
||||||
totalRentMonthly?: number
|
totalRentMonthly?: number
|
||||||
|
ancillaryCosts?: number
|
||||||
|
|
||||||
availabilityDate: string
|
availabilityDate: string
|
||||||
availabilityStatus: AvailabilityStatus
|
availabilityStatus: AvailabilityStatus
|
||||||
|
availabilityType?: AvailabilityType
|
||||||
|
|
||||||
sourceType: string
|
sourceType: string
|
||||||
|
sourceLabel?: string
|
||||||
sourceUrl?: string
|
sourceUrl?: string
|
||||||
|
sourceUpdatedAt?: string
|
||||||
|
sourceMeta?: SourceMeta
|
||||||
|
|
||||||
confidenceScore: number
|
confidenceScore: number
|
||||||
dataQuality: DataQuality
|
dataQuality: DataQuality
|
||||||
|
|
||||||
softFactors?: SoftFactors
|
softFactors?: SoftFactors
|
||||||
|
hardFacts?: PropertyHardFacts
|
||||||
|
|
||||||
|
// Legacy fields — kept for backward compat
|
||||||
floorLevel?: number
|
floorLevel?: number
|
||||||
expansionPotentialSqm?: number
|
expansionPotentialSqm?: number
|
||||||
|
|
||||||
contractDurationMonths?: number
|
contractDurationMonths?: number
|
||||||
ancillaryCosts?: number
|
|
||||||
riskLevel?: RiskLevel
|
riskLevel?: RiskLevel
|
||||||
description?: string
|
description?: string
|
||||||
images?: string[]
|
images?: string[]
|
||||||
organizationId?: string
|
|
||||||
|
status?: 'ACTIVE' | 'INACTIVE' | 'DRAFT' | 'ARCHIVED'
|
||||||
|
lastReviewedAt?: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
updatedAt: 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.GASTRO: return 'Gastro'
|
||||||
case AssetType.PRODUCTION: return 'Produktion'
|
case AssetType.PRODUCTION: return 'Produktion'
|
||||||
case AssetType.MIXED: return 'Gemischt'
|
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.GASTRO: return '#0d9488'
|
||||||
case AssetType.PRODUCTION: return '#92400e'
|
case AssetType.PRODUCTION: return '#92400e'
|
||||||
case AssetType.MIXED: return '#6b7280'
|
case AssetType.MIXED: return '#6b7280'
|
||||||
|
case AssetType.LIGHT_INDUSTRIAL: return '#b45309'
|
||||||
|
case AssetType.UNKNOWN: return '#9ca3af'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user