feat: expose all 13 scoring criteria as weighting sliders
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 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,11 @@ export interface WeightingProfile {
|
|||||||
prestige: number
|
prestige: number
|
||||||
accessibility: number
|
accessibility: number
|
||||||
expansionPotential: number
|
expansionPotential: number
|
||||||
|
flexibility: number
|
||||||
|
visibility: number
|
||||||
|
footfall: number
|
||||||
|
talentAccess: number
|
||||||
|
esg: number
|
||||||
taxEnvironment: number
|
taxEnvironment: number
|
||||||
[key: string]: number
|
[key: string]: number
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,11 @@ export const NeedBuilderStep = {
|
|||||||
} as const
|
} as const
|
||||||
export type NeedBuilderStep = typeof NeedBuilderStep[keyof typeof NeedBuilderStep]
|
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 type WeightingKey = typeof WEIGHTING_KEYS[number]
|
||||||
|
|
||||||
export const WEIGHTING_LABELS: Record<WeightingKey, string> = {
|
export const WEIGHTING_LABELS: Record<WeightingKey, string> = {
|
||||||
@@ -65,5 +69,10 @@ export const WEIGHTING_LABELS: Record<WeightingKey, string> = {
|
|||||||
prestige: 'Prestige',
|
prestige: 'Prestige',
|
||||||
accessibility: 'Erreichbarkeit',
|
accessibility: 'Erreichbarkeit',
|
||||||
expansionPotential: 'Expansionspotenzial',
|
expansionPotential: 'Expansionspotenzial',
|
||||||
|
flexibility: 'Flexibilität',
|
||||||
|
visibility: 'Sichtbarkeit',
|
||||||
|
footfall: 'Passantenfrequenz',
|
||||||
|
talentAccess: 'Talent-Zugang',
|
||||||
|
esg: 'ESG',
|
||||||
taxEnvironment: 'Steuerlast',
|
taxEnvironment: 'Steuerlast',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,11 @@ function resolveProfile(need: Need, property: Property): ScoringWeightProfile {
|
|||||||
if (!np) return base
|
if (!np) return base
|
||||||
|
|
||||||
// Apply need's custom core weights, then renormalize the full profile to 1.00
|
// 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) {
|
for (const key of CORE) {
|
||||||
if (typeof np[key] === 'number') base[key] = np[key]
|
if (typeof np[key] === 'number') base[key] = np[key]
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-88
@@ -26,14 +26,9 @@ export const mockNeeds: Need[] = [
|
|||||||
maxPublicTransportMinutes: 6,
|
maxPublicTransportMinutes: 6,
|
||||||
},
|
},
|
||||||
weightingProfile: {
|
weightingProfile: {
|
||||||
area: 0.20,
|
area: 0.18, location: 0.18, budget: 0.14, timing: 0.13,
|
||||||
location: 0.20,
|
prestige: 0.08, accessibility: 0.10, expansionPotential: 0.04, flexibility: 0.05,
|
||||||
budget: 0.15,
|
visibility: 0.02, footfall: 0.01, talentAccess: 0.06, esg: 0.02, taxEnvironment: 0.00,
|
||||||
timing: 0.15,
|
|
||||||
prestige: 0.10,
|
|
||||||
accessibility: 0.10,
|
|
||||||
expansionPotential: 0.05,
|
|
||||||
flexibility: 0.05,
|
|
||||||
},
|
},
|
||||||
confidenceInCriteria: 0.88,
|
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.',
|
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,
|
requireParking: true,
|
||||||
},
|
},
|
||||||
weightingProfile: {
|
weightingProfile: {
|
||||||
area: 0.25,
|
area: 0.23, location: 0.18, budget: 0.18, timing: 0.13,
|
||||||
location: 0.20,
|
prestige: 0.02, accessibility: 0.10, expansionPotential: 0.05, flexibility: 0.03,
|
||||||
budget: 0.20,
|
visibility: 0.01, footfall: 0.00, talentAccess: 0.02, esg: 0.02, taxEnvironment: 0.03,
|
||||||
timing: 0.15,
|
|
||||||
prestige: 0.02,
|
|
||||||
accessibility: 0.10,
|
|
||||||
expansionPotential: 0.05,
|
|
||||||
flexibility: 0.03,
|
|
||||||
},
|
},
|
||||||
confidenceInCriteria: 0.94,
|
confidenceInCriteria: 0.94,
|
||||||
organizationId: 'org-wincasa',
|
organizationId: 'org-wincasa',
|
||||||
@@ -101,14 +91,9 @@ export const mockNeeds: Need[] = [
|
|||||||
maxPublicTransportMinutes: 10,
|
maxPublicTransportMinutes: 10,
|
||||||
},
|
},
|
||||||
weightingProfile: {
|
weightingProfile: {
|
||||||
area: 0.20,
|
area: 0.18, location: 0.22, budget: 0.17, timing: 0.09,
|
||||||
location: 0.25,
|
prestige: 0.10, accessibility: 0.08, expansionPotential: 0.03, flexibility: 0.02,
|
||||||
budget: 0.20,
|
visibility: 0.02, footfall: 0.01, talentAccess: 0.08, esg: 0.05, taxEnvironment: 0.00,
|
||||||
timing: 0.10,
|
|
||||||
prestige: 0.12,
|
|
||||||
accessibility: 0.08,
|
|
||||||
expansionPotential: 0.03,
|
|
||||||
flexibility: 0.02,
|
|
||||||
},
|
},
|
||||||
confidenceInCriteria: 0.91,
|
confidenceInCriteria: 0.91,
|
||||||
extractedFromText: 'Suche repräsentative Büroflächen im Raum Basel/Allschwil, 500–800m², max. CHF 38/m², Bezug Q4 2025.',
|
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,
|
maxPublicTransportMinutes: 5,
|
||||||
},
|
},
|
||||||
weightingProfile: {
|
weightingProfile: {
|
||||||
area: 0.15,
|
area: 0.10, location: 0.20, budget: 0.12, timing: 0.08,
|
||||||
location: 0.35,
|
prestige: 0.06, accessibility: 0.04, expansionPotential: 0.02, flexibility: 0.04,
|
||||||
budget: 0.15,
|
visibility: 0.12, footfall: 0.15, talentAccess: 0.01, esg: 0.01, taxEnvironment: 0.00,
|
||||||
timing: 0.10,
|
|
||||||
prestige: 0.15,
|
|
||||||
accessibility: 0.05,
|
|
||||||
expansionPotential: 0.02,
|
|
||||||
flexibility: 0.03,
|
|
||||||
},
|
},
|
||||||
confidenceInCriteria: 0.96,
|
confidenceInCriteria: 0.96,
|
||||||
extractedFromText: 'Exklusive Retailfläche in Zürich Innenstadt gesucht, 250–450m², Schaufenster, max. CHF 95/m².',
|
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,
|
maxPublicTransportMinutes: 10,
|
||||||
},
|
},
|
||||||
weightingProfile: {
|
weightingProfile: {
|
||||||
area: 0.20,
|
area: 0.17, location: 0.22, budget: 0.17, timing: 0.12,
|
||||||
location: 0.25,
|
prestige: 0.05, accessibility: 0.10, expansionPotential: 0.03, flexibility: 0.02,
|
||||||
budget: 0.20,
|
visibility: 0.02, footfall: 0.01, talentAccess: 0.08, esg: 0.02, taxEnvironment: 0.04,
|
||||||
timing: 0.15,
|
|
||||||
prestige: 0.05,
|
|
||||||
accessibility: 0.10,
|
|
||||||
expansionPotential: 0.03,
|
|
||||||
flexibility: 0.02,
|
|
||||||
},
|
},
|
||||||
confidenceInCriteria: 0.85,
|
confidenceInCriteria: 0.85,
|
||||||
extractedFromText: 'Junges Tech-Unternehmen sucht Büro in Zug oder Zürich, 350–600m², moderner Ausbau, max. CHF 45/m².',
|
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,
|
requireParking: true,
|
||||||
},
|
},
|
||||||
weightingProfile: {
|
weightingProfile: {
|
||||||
area: 0.30,
|
area: 0.27, location: 0.22, budget: 0.18, timing: 0.10,
|
||||||
location: 0.25,
|
prestige: 0.01, accessibility: 0.10, expansionPotential: 0.02, flexibility: 0.02,
|
||||||
budget: 0.20,
|
visibility: 0.01, footfall: 0.00, talentAccess: 0.02, esg: 0.02, taxEnvironment: 0.02,
|
||||||
timing: 0.10,
|
|
||||||
prestige: 0.01,
|
|
||||||
accessibility: 0.10,
|
|
||||||
expansionPotential: 0.02,
|
|
||||||
flexibility: 0.02,
|
|
||||||
},
|
},
|
||||||
confidenceInCriteria: 0.92,
|
confidenceInCriteria: 0.92,
|
||||||
organizationId: 'org-wincasa',
|
organizationId: 'org-wincasa',
|
||||||
@@ -255,14 +225,9 @@ export const mockNeeds: Need[] = [
|
|||||||
maxPublicTransportMinutes: 12,
|
maxPublicTransportMinutes: 12,
|
||||||
},
|
},
|
||||||
weightingProfile: {
|
weightingProfile: {
|
||||||
area: 0.20,
|
area: 0.17, location: 0.15, budget: 0.13, timing: 0.12,
|
||||||
location: 0.20,
|
prestige: 0.07, accessibility: 0.09, expansionPotential: 0.04, flexibility: 0.06,
|
||||||
budget: 0.15,
|
visibility: 0.07, footfall: 0.05, talentAccess: 0.06, esg: 0.03, taxEnvironment: 0.00,
|
||||||
timing: 0.15,
|
|
||||||
prestige: 0.10,
|
|
||||||
accessibility: 0.10,
|
|
||||||
expansionPotential: 0.05,
|
|
||||||
flexibility: 0.05,
|
|
||||||
},
|
},
|
||||||
confidenceInCriteria: 0.82,
|
confidenceInCriteria: 0.82,
|
||||||
extractedFromText: 'Kreativagentur sucht Gewerbe-/Bürofläche in Zürich oder Bern, 900–1400m², gemischte Nutzung, Budget max. CHF 50/m².',
|
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,
|
requireParking: true,
|
||||||
},
|
},
|
||||||
weightingProfile: {
|
weightingProfile: {
|
||||||
area: 0.30,
|
area: 0.26, location: 0.18, budget: 0.18, timing: 0.10,
|
||||||
location: 0.20,
|
prestige: 0.01, accessibility: 0.12, expansionPotential: 0.07, flexibility: 0.02,
|
||||||
budget: 0.20,
|
visibility: 0.01, footfall: 0.00, talentAccess: 0.03, esg: 0.03, taxEnvironment: 0.02,
|
||||||
timing: 0.10,
|
|
||||||
prestige: 0.01,
|
|
||||||
accessibility: 0.10,
|
|
||||||
expansionPotential: 0.07,
|
|
||||||
flexibility: 0.02,
|
|
||||||
},
|
},
|
||||||
confidenceInCriteria: 0.95,
|
confidenceInCriteria: 0.95,
|
||||||
organizationId: 'org-wincasa',
|
organizationId: 'org-wincasa',
|
||||||
@@ -329,14 +289,9 @@ export const mockNeeds: Need[] = [
|
|||||||
maxPublicTransportMinutes: 5,
|
maxPublicTransportMinutes: 5,
|
||||||
},
|
},
|
||||||
weightingProfile: {
|
weightingProfile: {
|
||||||
area: 0.15,
|
area: 0.10, location: 0.22, budget: 0.12, timing: 0.08,
|
||||||
location: 0.35,
|
prestige: 0.07, accessibility: 0.04, expansionPotential: 0.01, flexibility: 0.04,
|
||||||
budget: 0.15,
|
visibility: 0.12, footfall: 0.14, talentAccess: 0.01, esg: 0.01, taxEnvironment: 0.00,
|
||||||
timing: 0.10,
|
|
||||||
prestige: 0.15,
|
|
||||||
accessibility: 0.05,
|
|
||||||
expansionPotential: 0.02,
|
|
||||||
flexibility: 0.03,
|
|
||||||
},
|
},
|
||||||
confidenceInCriteria: 0.93,
|
confidenceInCriteria: 0.93,
|
||||||
extractedFromText: 'Recherche surface commerciale en centre-ville de Genève, 200–350m², vitrine obligatoire, budget max CHF 115/m².',
|
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,
|
maxPublicTransportMinutes: 5,
|
||||||
},
|
},
|
||||||
weightingProfile: {
|
weightingProfile: {
|
||||||
area: 0.15,
|
area: 0.10, location: 0.22, budget: 0.12, timing: 0.08,
|
||||||
location: 0.35,
|
prestige: 0.06, accessibility: 0.04, expansionPotential: 0.01, flexibility: 0.04,
|
||||||
budget: 0.15,
|
visibility: 0.12, footfall: 0.15, talentAccess: 0.01, esg: 0.01, taxEnvironment: 0.00,
|
||||||
timing: 0.10,
|
|
||||||
prestige: 0.15,
|
|
||||||
accessibility: 0.05,
|
|
||||||
expansionPotential: 0.02,
|
|
||||||
flexibility: 0.03,
|
|
||||||
},
|
},
|
||||||
confidenceInCriteria: 0.92,
|
confidenceInCriteria: 0.92,
|
||||||
extractedFromText: 'Suche Retailfläche in Bern Innenstadt, 200–400m², Schaufensterfront, Fussgängerzone, max. CHF 150/m².',
|
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,
|
maxPublicTransportMinutes: 8,
|
||||||
},
|
},
|
||||||
weightingProfile: {
|
weightingProfile: {
|
||||||
area: 0.22,
|
area: 0.20, location: 0.25, budget: 0.18, timing: 0.11,
|
||||||
location: 0.28,
|
prestige: 0.07, accessibility: 0.06, expansionPotential: 0.02, flexibility: 0.02,
|
||||||
budget: 0.20,
|
visibility: 0.02, footfall: 0.01, talentAccess: 0.05, esg: 0.02, taxEnvironment: 0.00,
|
||||||
timing: 0.12,
|
|
||||||
prestige: 0.08,
|
|
||||||
accessibility: 0.06,
|
|
||||||
expansionPotential: 0.02,
|
|
||||||
flexibility: 0.02,
|
|
||||||
},
|
},
|
||||||
confidenceInCriteria: 0.87,
|
confidenceInCriteria: 0.87,
|
||||||
extractedFromText: 'Büroflächen in St. Gallen oder Umgebung gesucht, 450–750m², max. CHF 32/m², Bezug Anfang 2026.',
|
extractedFromText: 'Büroflächen in St. Gallen oder Umgebung gesucht, 450–750m², max. CHF 32/m², Bezug Anfang 2026.',
|
||||||
|
|||||||
Reference in New Issue
Block a user