refactor: architecture compliance pass — DS tokens, hook boundary, god component split, AI hardening
- DS token migration: Anfragen.tsx + child components (AnfragenInquiryItem, AnfragenMessageBubble) fully migrated; DS_TEXT.brandDark added; scoreTheme.ts moved to src/lib/ with re-export proxy - Hook boundary: Results.tsx no longer calls needService directly — routes through useNeeds() with optional refetchOnMount/gcTime overrides - NewListing.tsx (440L) split into useNewListingForm hook + 8 section components under src/components/new-listing/; page shell reduced to 121 lines - AI hardening: Zod .strict() on all schemas, AIProvenance extended with schemaVersion/ fallbackReason/traceId/latencyMs, AITraceStore stats with p50/p90/p99 + failure breakdowns, MockAIService buildFollowUpQuestions with priority ordering + area-ambiguity detection, prompt templates updated (LIGHT_INDUSTRIAL, budget unit, ambiguity detection, decimal precision) - Tests: all 154 passing; fixed test regression caused by OfferEmailResponseSchema body min(50) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,8 +24,12 @@ PRIORITÄTSREIHENFOLGE:
|
||||
5. timing (recommended) — wichtig für Verfügbarkeitsabgleich
|
||||
6. mustHaveCriteria (optional) — Pflichtmerkmale (Parkplätze, Laderampe etc.)
|
||||
|
||||
MEHRDEUTIGKEITSERKENNUNG — prüfe auch bekannte Felder auf Ambiguität:
|
||||
- areaRange vorhanden, aber max/min-Verhältnis > 5: generiere eine Präzisierungsfrage (targetField: "areaRange", importance: "required") statt sie als vollständig zu behandeln
|
||||
- areaRange vorhanden, aber min = 0 oder max = 0: generiere dieselbe Präzisierungsfrage
|
||||
|
||||
VERBOTE — NIEMALS:
|
||||
- Fragen zu bereits bekannten Kriterien stellen
|
||||
- Fragen zu bereits bekannten, eindeutigen Kriterien stellen
|
||||
- Mehr als 3 Fragen ausgeben
|
||||
- Fragen erfinden, die nicht einem der 6 definierten Zielfelder entsprechen
|
||||
- Doppelfragen stellen
|
||||
@@ -54,7 +58,7 @@ Ausgabe:
|
||||
"importance": "required"
|
||||
},
|
||||
{
|
||||
"questionText": "Was ist Ihr maximales Budget pro m² und Monat (CHF)?",
|
||||
"questionText": "Was ist Ihr maximales Budget pro m² und Jahr (CHF)?",
|
||||
"targetField": "budgetRange",
|
||||
"reason": "Budget ist wichtig für die Filterung unpassender Objekte",
|
||||
"suggestedAnswerOptions": [],
|
||||
|
||||
@@ -34,6 +34,7 @@ WEITERE VERBOTE:
|
||||
- Fläche in m² schätzen, wenn kein konkreter Hinweis im Text steht (→ null setzen)
|
||||
- Zeithorizont nennen, wenn er nicht aus dem Text ableitbar ist (→ null setzen)
|
||||
- probability > 0.85 setzen ohne mehrere unabhängige, verlässliche Bestätigungen
|
||||
- probability mit mehr als 2 Dezimalstellen angeben (z.B. 0.724 → 0.72, 0.6666 → 0.67)
|
||||
|
||||
AUSGABEFORMAT — antworte ausschliesslich als valides JSON (kein Markdown-Block, keine Erklärungen):
|
||||
{
|
||||
|
||||
@@ -20,10 +20,10 @@ VERBOTE — NIEMALS:
|
||||
|
||||
AUSGABEFORMAT — antworte ausschliesslich als valides JSON (kein Markdown-Block, keine Erklärungen):
|
||||
{
|
||||
"assetType": "OFFICE" | "RETAIL" | "LOGISTICS" | "PRODUCTION" | "GASTRO" | "MIXED" | "UNKNOWN" | null,
|
||||
"areaRange": { "min": number, "max": number } | null,
|
||||
"assetType": "OFFICE" | "RETAIL" | "LOGISTICS" | "PRODUCTION" | "LIGHT_INDUSTRIAL" | "GASTRO" | "MIXED" | "UNKNOWN" | null,
|
||||
"areaRange": { "min": number (≥1), "max": number (≥1, ≥ min) } | null,
|
||||
"preferredLocations": string[],
|
||||
"budgetRange": { "maxPerSqm": number, "currency": "CHF" } | null,
|
||||
"budgetRange": { "maxPerSqm": number (CHF/m²/Jahr), "currency": "CHF" } | null,
|
||||
"timing": {
|
||||
"earliestMoveIn": "YYYY-MM-DD" | null,
|
||||
"latestMoveIn": "YYYY-MM-DD" | null,
|
||||
@@ -34,14 +34,20 @@ AUSGABEFORMAT — antworte ausschliesslich als valides JSON (kein Markdown-Block
|
||||
"assumptions": string[]
|
||||
}
|
||||
|
||||
FELDREGELN:
|
||||
- areaRange.min und areaRange.max müssen beide ≥ 1 sein — nie 0 setzen
|
||||
- budgetRange.maxPerSqm ist CHF pro m² pro Jahr (Jahresmiete) — nicht Monatsmiete
|
||||
- LIGHT_INDUSTRIAL: Leichtindustrielle Nutzung (Werkstatt, Atelier, kleine Produktion), klar abgegrenzt von LOGISTICS
|
||||
- Maximale Einträge: preferredLocations max. 20, mustHaveCriteria max. 10
|
||||
|
||||
BEISPIEL:
|
||||
Eingabe: "Wir suchen ein Büro für ca. 20 Personen in Zürich, Budget rund 50 CHF/m², Einzug ab März 2026"
|
||||
Eingabe: "Wir suchen ein Büro für ca. 20 Personen in Zürich, Budget rund 600 CHF/m²/Jahr, Einzug ab März 2026"
|
||||
Ausgabe:
|
||||
{
|
||||
"assetType": "OFFICE",
|
||||
"areaRange": { "min": 200, "max": 400 },
|
||||
"preferredLocations": ["Zürich"],
|
||||
"budgetRange": { "maxPerSqm": 50, "currency": "CHF" },
|
||||
"budgetRange": { "maxPerSqm": 600, "currency": "CHF" },
|
||||
"timing": { "earliestMoveIn": "2026-03-01", "latestMoveIn": null, "flexibleTiming": false },
|
||||
"mustHaveCriteria": [],
|
||||
"missingFields": ["timing.latestMoveIn", "mustHaveCriteria"],
|
||||
|
||||
Reference in New Issue
Block a user