From ba910806d6c100851192ed868b52e4c390e7a63f Mon Sep 17 00:00:00 2001 From: Benjamin Sutter Date: Thu, 21 May 2026 20:58:01 +0200 Subject: [PATCH] feat: expose all 13 scoring criteria as weighting sliders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously only 8 of the 13 engine criteria had sliders — flexibility, visibility, footfall, talentAccess, esg were scored using fixed defaults with no user control. Now all 13 criteria appear in the WeightingEditor. - needBuilder.ts: WEIGHTING_KEYS and WEIGHTING_LABELS extended to all 13 - scoreCalculator.ts: CORE array in resolveProfile now includes all 13 - need.ts: WeightingProfile interface explicit for all 13 fields - needs.ts: all 11 mock need profiles updated with asset-type-appropriate weights (RETAIL needs high footfall/visibility; LOGISTICS low; OFFICE high talentAccess; TechStart high taxEnvironment for Zug, etc.) Co-Authored-By: Claude Sonnet 4.6 --- src/domain/need.ts | 5 + src/domain/needBuilder.ts | 11 ++- src/features/matching/scoreCalculator.ts | 6 +- src/mock-data/needs.ts | 121 +++++++---------------- 4 files changed, 53 insertions(+), 90 deletions(-) diff --git a/src/domain/need.ts b/src/domain/need.ts index 7f5553d..66e5e5e 100644 --- a/src/domain/need.ts +++ b/src/domain/need.ts @@ -27,6 +27,11 @@ export interface WeightingProfile { prestige: number accessibility: number expansionPotential: number + flexibility: number + visibility: number + footfall: number + talentAccess: number + esg: number taxEnvironment: number [key: string]: number } diff --git a/src/domain/needBuilder.ts b/src/domain/needBuilder.ts index 11db396..2cf99c4 100644 --- a/src/domain/needBuilder.ts +++ b/src/domain/needBuilder.ts @@ -54,7 +54,11 @@ export const NeedBuilderStep = { } as const export type NeedBuilderStep = typeof NeedBuilderStep[keyof typeof NeedBuilderStep] -export const WEIGHTING_KEYS = ['area', 'location', 'budget', 'timing', 'prestige', 'accessibility', 'expansionPotential', 'taxEnvironment'] as const +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 = { @@ -65,5 +69,10 @@ export const WEIGHTING_LABELS: Record = { prestige: 'Prestige', accessibility: 'Erreichbarkeit', expansionPotential: 'Expansionspotenzial', + flexibility: 'Flexibilität', + visibility: 'Sichtbarkeit', + footfall: 'Passantenfrequenz', + talentAccess: 'Talent-Zugang', + esg: 'ESG', taxEnvironment: 'Steuerlast', } diff --git a/src/features/matching/scoreCalculator.ts b/src/features/matching/scoreCalculator.ts index ba774ca..5b7bab9 100644 --- a/src/features/matching/scoreCalculator.ts +++ b/src/features/matching/scoreCalculator.ts @@ -22,7 +22,11 @@ function resolveProfile(need: Need, property: Property): ScoringWeightProfile { if (!np) return base // Apply need's custom core weights, then renormalize the full profile to 1.00 - const CORE = ['area', 'location', 'budget', 'timing', 'prestige', 'accessibility', 'expansionPotential', 'taxEnvironment'] + const CORE = [ + 'area', 'location', 'budget', 'timing', + 'prestige', 'accessibility', 'expansionPotential', 'flexibility', + 'visibility', 'footfall', 'talentAccess', 'esg', 'taxEnvironment', + ] for (const key of CORE) { if (typeof np[key] === 'number') base[key] = np[key] } diff --git a/src/mock-data/needs.ts b/src/mock-data/needs.ts index 8121b1b..4140dfb 100644 --- a/src/mock-data/needs.ts +++ b/src/mock-data/needs.ts @@ -26,14 +26,9 @@ export const mockNeeds: Need[] = [ maxPublicTransportMinutes: 6, }, weightingProfile: { - area: 0.20, - location: 0.20, - budget: 0.15, - timing: 0.15, - prestige: 0.10, - accessibility: 0.10, - expansionPotential: 0.05, - flexibility: 0.05, + area: 0.18, location: 0.18, budget: 0.14, timing: 0.13, + prestige: 0.08, accessibility: 0.10, expansionPotential: 0.04, flexibility: 0.05, + visibility: 0.02, footfall: 0.01, talentAccess: 0.06, esg: 0.02, taxEnvironment: 0.00, }, confidenceInCriteria: 0.88, extractedFromText: 'Wir suchen moderne Büroflächen in Zürich-West, ca. 700–900m², Budget max CHF 42/m², Bezug Herbst 2025.', @@ -62,14 +57,9 @@ export const mockNeeds: Need[] = [ requireParking: true, }, weightingProfile: { - area: 0.25, - location: 0.20, - budget: 0.20, - timing: 0.15, - prestige: 0.02, - accessibility: 0.10, - expansionPotential: 0.05, - flexibility: 0.03, + area: 0.23, location: 0.18, budget: 0.18, timing: 0.13, + prestige: 0.02, accessibility: 0.10, expansionPotential: 0.05, flexibility: 0.03, + visibility: 0.01, footfall: 0.00, talentAccess: 0.02, esg: 0.02, taxEnvironment: 0.03, }, confidenceInCriteria: 0.94, organizationId: 'org-wincasa', @@ -101,14 +91,9 @@ export const mockNeeds: Need[] = [ maxPublicTransportMinutes: 10, }, weightingProfile: { - area: 0.20, - location: 0.25, - budget: 0.20, - timing: 0.10, - prestige: 0.12, - accessibility: 0.08, - expansionPotential: 0.03, - flexibility: 0.02, + area: 0.18, location: 0.22, budget: 0.17, timing: 0.09, + prestige: 0.10, accessibility: 0.08, expansionPotential: 0.03, flexibility: 0.02, + visibility: 0.02, footfall: 0.01, talentAccess: 0.08, esg: 0.05, taxEnvironment: 0.00, }, confidenceInCriteria: 0.91, extractedFromText: 'Suche repräsentative Büroflächen im Raum Basel/Allschwil, 500–800m², max. CHF 38/m², Bezug Q4 2025.', @@ -140,14 +125,9 @@ export const mockNeeds: Need[] = [ maxPublicTransportMinutes: 5, }, weightingProfile: { - area: 0.15, - location: 0.35, - budget: 0.15, - timing: 0.10, - prestige: 0.15, - accessibility: 0.05, - expansionPotential: 0.02, - flexibility: 0.03, + area: 0.10, location: 0.20, budget: 0.12, timing: 0.08, + prestige: 0.06, accessibility: 0.04, expansionPotential: 0.02, flexibility: 0.04, + visibility: 0.12, footfall: 0.15, talentAccess: 0.01, esg: 0.01, taxEnvironment: 0.00, }, confidenceInCriteria: 0.96, extractedFromText: 'Exklusive Retailfläche in Zürich Innenstadt gesucht, 250–450m², Schaufenster, max. CHF 95/m².', @@ -180,14 +160,9 @@ export const mockNeeds: Need[] = [ maxPublicTransportMinutes: 10, }, weightingProfile: { - area: 0.20, - location: 0.25, - budget: 0.20, - timing: 0.15, - prestige: 0.05, - accessibility: 0.10, - expansionPotential: 0.03, - flexibility: 0.02, + area: 0.17, location: 0.22, budget: 0.17, timing: 0.12, + prestige: 0.05, accessibility: 0.10, expansionPotential: 0.03, flexibility: 0.02, + visibility: 0.02, footfall: 0.01, talentAccess: 0.08, esg: 0.02, taxEnvironment: 0.04, }, confidenceInCriteria: 0.85, extractedFromText: 'Junges Tech-Unternehmen sucht Büro in Zug oder Zürich, 350–600m², moderner Ausbau, max. CHF 45/m².', @@ -216,14 +191,9 @@ export const mockNeeds: Need[] = [ requireParking: true, }, weightingProfile: { - area: 0.30, - location: 0.25, - budget: 0.20, - timing: 0.10, - prestige: 0.01, - accessibility: 0.10, - expansionPotential: 0.02, - flexibility: 0.02, + area: 0.27, location: 0.22, budget: 0.18, timing: 0.10, + prestige: 0.01, accessibility: 0.10, expansionPotential: 0.02, flexibility: 0.02, + visibility: 0.01, footfall: 0.00, talentAccess: 0.02, esg: 0.02, taxEnvironment: 0.02, }, confidenceInCriteria: 0.92, organizationId: 'org-wincasa', @@ -255,14 +225,9 @@ export const mockNeeds: Need[] = [ maxPublicTransportMinutes: 12, }, weightingProfile: { - area: 0.20, - location: 0.20, - budget: 0.15, - timing: 0.15, - prestige: 0.10, - accessibility: 0.10, - expansionPotential: 0.05, - flexibility: 0.05, + area: 0.17, location: 0.15, budget: 0.13, timing: 0.12, + prestige: 0.07, accessibility: 0.09, expansionPotential: 0.04, flexibility: 0.06, + visibility: 0.07, footfall: 0.05, talentAccess: 0.06, esg: 0.03, taxEnvironment: 0.00, }, confidenceInCriteria: 0.82, extractedFromText: 'Kreativagentur sucht Gewerbe-/Bürofläche in Zürich oder Bern, 900–1400m², gemischte Nutzung, Budget max. CHF 50/m².', @@ -291,14 +256,9 @@ export const mockNeeds: Need[] = [ requireParking: true, }, weightingProfile: { - area: 0.30, - location: 0.20, - budget: 0.20, - timing: 0.10, - prestige: 0.01, - accessibility: 0.10, - expansionPotential: 0.07, - flexibility: 0.02, + area: 0.26, location: 0.18, budget: 0.18, timing: 0.10, + prestige: 0.01, accessibility: 0.12, expansionPotential: 0.07, flexibility: 0.02, + visibility: 0.01, footfall: 0.00, talentAccess: 0.03, esg: 0.03, taxEnvironment: 0.02, }, confidenceInCriteria: 0.95, organizationId: 'org-wincasa', @@ -329,14 +289,9 @@ export const mockNeeds: Need[] = [ maxPublicTransportMinutes: 5, }, weightingProfile: { - area: 0.15, - location: 0.35, - budget: 0.15, - timing: 0.10, - prestige: 0.15, - accessibility: 0.05, - expansionPotential: 0.02, - flexibility: 0.03, + area: 0.10, location: 0.22, budget: 0.12, timing: 0.08, + prestige: 0.07, accessibility: 0.04, expansionPotential: 0.01, flexibility: 0.04, + visibility: 0.12, footfall: 0.14, talentAccess: 0.01, esg: 0.01, taxEnvironment: 0.00, }, confidenceInCriteria: 0.93, extractedFromText: 'Recherche surface commerciale en centre-ville de Genève, 200–350m², vitrine obligatoire, budget max CHF 115/m².', @@ -368,14 +323,9 @@ export const mockNeeds: Need[] = [ maxPublicTransportMinutes: 5, }, weightingProfile: { - area: 0.15, - location: 0.35, - budget: 0.15, - timing: 0.10, - prestige: 0.15, - accessibility: 0.05, - expansionPotential: 0.02, - flexibility: 0.03, + area: 0.10, location: 0.22, budget: 0.12, timing: 0.08, + prestige: 0.06, accessibility: 0.04, expansionPotential: 0.01, flexibility: 0.04, + visibility: 0.12, footfall: 0.15, talentAccess: 0.01, esg: 0.01, taxEnvironment: 0.00, }, confidenceInCriteria: 0.92, extractedFromText: 'Suche Retailfläche in Bern Innenstadt, 200–400m², Schaufensterfront, Fussgängerzone, max. CHF 150/m².', @@ -408,14 +358,9 @@ export const mockNeeds: Need[] = [ maxPublicTransportMinutes: 8, }, weightingProfile: { - area: 0.22, - location: 0.28, - budget: 0.20, - timing: 0.12, - prestige: 0.08, - accessibility: 0.06, - expansionPotential: 0.02, - flexibility: 0.02, + area: 0.20, location: 0.25, budget: 0.18, timing: 0.11, + prestige: 0.07, accessibility: 0.06, expansionPotential: 0.02, flexibility: 0.02, + visibility: 0.02, footfall: 0.01, talentAccess: 0.05, esg: 0.02, taxEnvironment: 0.00, }, confidenceInCriteria: 0.87, extractedFromText: 'Büroflächen in St. Gallen oder Umgebung gesucht, 450–750m², max. CHF 32/m², Bezug Anfang 2026.',