feat: UC-A/B/C demo — 50 mock properties, district scoring, gold/silver cards visible

- Add 50 mock properties (prop-050–099) tuned for UC-A (Fahrradhändler RETAIL),
  UC-B (Umzugsfirma OFFICE Zürich-West) and UC-C (Vermögensverwalter PREMIUM)
- District-aware scoreLocation: district match→100, city-only→70 when need
  specifies districts, canton→60, no match→35; fixes UC-C prop-001 over-scoring
- mustHaveScorer: add klimatisierung keyword; parking minimum count logic
- Soft factor scale fix: integer 0–100 values no longer multiplied ×100
- footfall: map passerbyFrequency string (HIGH→85, MEDIUM_HIGH→68…) before
  enrichment fallback so RETAIL properties score correctly
- Parser: Kreis list extraction ("Kreis 3, 4, 5, und 8" → 4 district entries),
  neighbourhood→district map (Seefeld, Bahnhofstrasse), prestige signals
- Results: remove VERIFIED_PORTFOLIO role gate — all users see portfolio cards,
  enabling gold (85+) and silver (70–84) cards for every demo use case
- Fix flash of wrong cards on NeedBuilder nav (effectiveNeedId not activeNeed?.id)
- needs.ts: UC-A preferredLocations now includes Kreis 3/4/5/8 entries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-22 20:31:30 +02:00
parent 98b4a1146b
commit 9f7062d137
16 changed files with 3942 additions and 52 deletions
+12
View File
@@ -17,6 +17,15 @@ export interface PropertyNeedMatch {
matchHighlights: string[]
}
// ── Must-Have Evaluation ─────────────────────────────────────────────────────
export interface MustHaveResult {
criterion: string
passed: boolean
confidence: 'CERTAIN' | 'ESTIMATED' | 'UNKNOWN'
explanation: string
}
// ── Score Building Blocks ─────────────────────────────────────────────────────
export interface ScoreBreakdown {
@@ -115,6 +124,9 @@ export interface Match {
riskLevel: RiskLevel
uncertaintyIndicators: string[]
// Must-have evaluation results (from mustHaveScorer)
mustHaveEvaluation?: MustHaveResult[]
// Alternatives
alternativeStrategies?: AlternativeStrategy[]
+11
View File
@@ -82,6 +82,17 @@ export interface Need {
budgetRange: BudgetRange
timing: Timing
mustCriteriaText?: string[] // legacy — prefer mustHaveCriteria
// Structured requirement flags
requireGroundFloor?: boolean
requiredFitOut?: 'BASIC' | 'FULL' | 'PREMIUM'
requiredParkingMin?: number
requireAirConditioning?: boolean
requireLoadingDock?: boolean
requireBarrierFree?: boolean
minCeilingHeightM?: number
minContractDurationMonths?: number
softFactors?: SoftFactorPreferences
weightingProfile: WeightingProfile
confidenceInCriteria: number
+8
View File
@@ -8,6 +8,14 @@ export interface ParsedNeedCriteria {
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'
+2
View File
@@ -61,6 +61,8 @@ export interface PropertyHardFacts {
powerSupplyKva?: number
hasServerRoom?: boolean
isBarrierFree?: boolean
hasAirConditioning?: boolean
hasStorefront?: boolean
}
// ── Soft Factors ──────────────────────────────────────────────────────────────
+2 -1
View File
@@ -1,4 +1,4 @@
import type { ScoreFactor, TradeOff, Risk, MissingDataItem, NextBestAction } from './match'
import type { ScoreFactor, TradeOff, Risk, MissingDataItem, NextBestAction, MustHaveResult } from './match'
// ── Hard Filter Thresholds ────────────────────────────────────────────────────
@@ -141,4 +141,5 @@ export interface MatchEngineOutput {
risks: Risk[]
missingData: MissingDataItem[]
nextBestActions: NextBestAction[]
mustHaveEvaluation?: MustHaveResult[]
}