import type { ScoreFactor, TradeOff, Risk, MissingDataItem, NextBestAction, MustHaveResult } 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 MAISON_WORK: -2, // MAISON_WORK — Maison Work API (slightly more trusted than scraped market) 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 = { // 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[] mustHaveEvaluation?: MustHaveResult[] }