Files
property-match/src/domain/needBuilder.ts
T
Benjamin Sutter b2530f9e20 feat: Reale Jahresbelastung, must-have fixes, fitOut data coverage
Reale Jahresbelastung (Change 6):
- FitOutCostPanel zeigt Jahresmiete + amortisierte Ausbaukosten für alle fitOut-Werte
- FULL/PREMIUM: Ausbau CHF 0 (bezugsfertig), SHELL/BASIC: CRB/BKP-Richtwerte amortisiert über 5 J.
- Match-Card-Chip zeigt geschätzte Investition (orange wenn >100k)
- FitOutInvestment-Typ + calcFitOutInvestment() in fitOutUtils.ts
- BackendAIService + MockAIService mit generateFitOutAdvice (IAIService-Interface)

Must-have Kriterien (Nicht prüfbar Fix):
- ÖV-Anbindung: Minutengrenze aus Freitext extrahiert, gegen publicTransportMinutes geprüft
- Mindestfläche: m²-Wert aus Freitext extrahiert, gegen areaSqm geprüft
- Ausbaugrad: neues Keyword-Rule für FULL/PREMIUM

fitOut-Datenpflege:
- fitOut-Werte zu 32 fehlenden Properties ergänzt (Logistik=SHELL, Standard=BASIC, Modern=FULL)
- MAB-Werte (200/150/250 CHF/m²) zu 3 BASIC-Objekten hinzugefügt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 20:09:55 +02:00

92 lines
2.9 KiB
TypeScript

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 }
requireGroundFloor?: boolean
requiredFitOut?: 'BASIC' | 'FULL' | 'PREMIUM'
requiredParkingMin?: number
requireAirConditioning?: boolean
requireLoadingDock?: boolean
requireBarrierFree?: boolean
minCeilingHeightM?: number
minContractDurationMonths?: number
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
searchRadius?: number
isAnonymous?: boolean
requiresDivisibility?: boolean
minDivisibleUnit?: number
fitOutBudgetMaxPerSqm?: number
}
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',
'visibility', 'footfall', 'talentAccess', 'esg', 'taxEnvironment',
] 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',
visibility: 'Sichtbarkeit',
footfall: 'Passantenfrequenz',
talentAccess: 'Talent-Zugang',
esg: 'ESG',
taxEnvironment: 'Steuerlast',
}