Commit Graph

78 Commits

Author SHA1 Message Date
Benjamin Sutter 609a3634bd feat: remove EXTERNAL_MARKET result type entirely
All external market properties now surface as VERIFIED_PORTFOLIO.
Removes the type from enums, domain types, mock data, matching engine,
components, hooks, and tests — zero user-visible distinction remains.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 17:28:42 +02:00
Benjamin Sutter 99c26b5464 feat: Decision Intelligence simplification pass
Phase 1 — MatchDetail: collapse from 11 panels to 3 visible sections
(Warum dieser Match? / Nächste Aktion / toggle). All analysis panels
(ScoreBreakdown, Risk, MissingData, NeedAlignment, Location, Tradeoffs,
FutureAvailabilityContext) hidden behind "Vollständige Analyse anzeigen".
Removes 2-column sidebar layout for cleaner single-column reading flow.

Phase 2 — Supply Dashboard: replace data quality KPIs with demand
intelligence. Hero now shows "Starke Match-Anfragen", active properties,
and Zukunftssignale count. Top 4 matches listed with score + reason +
next action. Data quality demoted to secondary collapsed notice.

Phase 3 — Future Availability: add informational banner above the results
feed when Zukunftssignale are present. Framed as professional market
intelligence (contract expiries, construction signals) not as risk.
Removed Zukunftssignal from DecisionContextPanel risks array.

Phase 4 — Naming: standardise RESULT_TYPE_META to "Verifiziertes Objekt" /
"Externes Angebot" / "Maison Work" / "Zukunftssignal" across all screens.
EXTERNAL_MARKET gets distinct amber color (#d97706). Remove Gold/Silver/
Bronze tier label from match score badge — only the % number is shown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 17:09:12 +02:00
Benjamin Sutter e1f4beb898 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>
2026-05-24 16:10:39 +02:00
Benjamin Sutter e36c5bc979 refactor(arch): eliminate direct service calls in components — route all through hooks
New hook files:
- useAuth.ts: useLogin, useSwitchDemoRole, useSwitchOrganization
- useAI.ts: useParseNeed, useGenerateOfferEmail, useGenerateDecisionBrief, useParseListingText
- useAssistant.ts: useAssistantSuggestions (useQuery), useAssistantAnswer (useMutation)
- useOfferReport.ts: useOfferReportByInquiry, useCreateOfferReport, useUpdateOfferReport, useGenerateOfferReportPdf
- useInquiryReport.ts: useInquiryReportByInquiry, useCreateInquiryReport, useUpdateInquiryReport, useFinalizeInquiryReport
- useMarketReport.ts: useMarketReport
- useWeighting.ts: useDefaultWeights (synchronous wrapper)
- useUnitMatches.ts: useUnitMatchesMap, useUnitBundle, useBundleMatches (useMemo wrappers)

Extended hooks: useProperties (add update/create/remove mutations),
useNeeds (add useCreateNeed), useMatches (add useNeedMatchesForProperty,
useAdditionalMatchesForInquiry), useReviewQueue (add useCreateReviewTask)

Updated 21 components/pages: all direct service imports replaced with hooks.
Deliberate exception: getRecommendedActions in DataQuality.tsx (pure sync utility, no provider access).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:56:32 +02:00
Benjamin Sutter f487435a94 fix: correct rent unit labels and monthly/annual calculation consistency
- NeedAlignmentPanel: display budget row in monthly (CHF/m²/Mt.) to match
  the Preis section, use exact division (no Math.round) to avoid 13×12≠152
- MatchDetailPropertySections + PropertyDetailPublicSections: replace
  Math.round(rentPricePerSqm/12) with exact division; show 2 decimal places
  when monthly is not a whole number (e.g. CHF 12.67 instead of CHF 13)
- Add /Jahr suffix to all 15 displays showing rentPricePerSqm or maxPerSqm
  without a time unit across results, compare, match-detail, supply, and
  anfragencenter components

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:35:20 +02:00
Benjamin Sutter 7f090a48ff refactor: replace hardcoded hex colors with design tokens across priority components
- Add DS_COLORS.heat, DS_COLORS.futureCard, INQUIRY_STATUS_META tokens to ds.ts
- Eliminate 4 duplicate RESULT_TYPE_META / TYPE_META constants (now all use ds.ts)
- Eliminate 3 duplicate scoreColor / SCORE_COLOR functions (now all use matchScoreHex)
- HeatBadge: use DS_COLORS.heat.VERY_HOT / HOT tokens
- FutureAvailabilityCard: use DS_COLORS.futureCard.controlled / signal tokens
- IntelligenceMatchCard: import RESULT_TYPE_META from ds.ts, remove primary override
- MatchScoreDisplay: replace local scoreColor() with matchScoreHex()
- CompareColumnHeader: remove local TYPE_META + SCORE_COLOR, use ds.ts + utils.ts
- CompareTableBody: import RESULT_TYPE_META + matchScoreHex, fix #7c3aed → token
- compareUtils: remove TYPE_META + SCORE_COLOR, use dataQualityHex in scoreBar
- Anfragen + AnfragenInquiryItem: deduplicate STATUS_CONFIG → INQUIRY_STATUS_META
- Anfragen: #1e3a5f → 'primary.main', KI alert → futureCard.controlled tokens
- AnfragenMessageBubble: own-message bubble → 'primary.main', AI bubble → tokens
- ScoreBreakdownPanel: #1e3a5f → 'primary.main', #6d28d9 → futureCard.controlled
- ESLint: add warn rule against new hex colors in component sx props

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:02:01 +02:00
Benjamin Sutter 723f553939 refactor: move PipelineItems from Zustand to Provider→Service→React Query
PipelineItems are domain data and must not live in Zustand. Moves the
full stack to the correct layer: MockupPipelineProvider (localStorage
persistence + seed fallback) → pipelineService → usePipeline hooks
(useQuery for reads, useMutation for writes with cache invalidation).

pipelineStore is now UI-only: dialogOpen, pendingItem, openSavedDialog,
closeSavedDialog. All consumers updated to use the new hooks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 12:32:57 +02:00
Benjamin Sutter ce67da73b3 perf: memoize expensive list computations + memo on grid/list items
Results.tsx:
- useMemo: filter + sort in one pass (was 7 separate array iterations per render)
- useMemo: platform/maison/future/missingData counts in single for-loop
- Fix: move queryClient.invalidateQueries from render body into useEffect

Properties.tsx:
- useMemo: wrap applyFilters() call (was full copy+sort on every render)
- useMemo: compute matchReady/criticalGaps/lowConfidence/staleOrOutdated/
  allMissingFields in a single for-loop (was 5 separate filter passes)

ReminderFeed.tsx:
- useMemo: wrap applyFilters() call
- useCallback: resetFilters (passed to ReminderEmptyState)

PropertyIntelligenceCard, ReminderListRow:
- React.memo: grid/list items no longer re-render when unrelated parent
  state changes (e.g. selectedId, filter UI state)

tsc --noEmit passes with zero errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 01:53:03 +02:00
Benjamin Sutter c6055f0611 refactor: extract CompareTableBody from Compare.tsx (441 → 147 lines)
Move 18 inline table row definitions into CompareTableBody component

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 01:13:59 +02:00
Benjamin Sutter f0988b7250 refactor: extract constants + mapper from NewListing.tsx (526 → 442 lines)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 01:05:57 +02:00
Benjamin Sutter 0bacd188d6 refactor: split AppShell, match-detail panels, extract useCompareData
AppShell.tsx: 627→116 lines
- appShellConfig.ts: NavItem/WorkspaceConfig types, WORKSPACE_CONFIG, nav helpers
- AppShellSidebar.tsx: Sidebar component with visual constants
- AppShellTopBar.tsx: TopBar component

Match-detail panels:
- ScoreBreakdownPanel: 377→303 lines (scoreBreakdownConstants.ts + CriterionRow.tsx extracted)
- LocationIntelligencePanel: 383→333 lines (SoftFactorBar.tsx extracted)
- FutureAvailabilityContextPanel: 386→351 lines (futureAvailabilityConstants.tsx extracted)

Compare.tsx: 485→441 lines
- useCompareData hook: all queries and derived state extracted to hooks/useCompareData.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 01:00:38 +02:00
Benjamin Sutter ae82d0e6a0 refactor: split PropertyDetail, Anfragen, AISearch god components
PropertyDetail.tsx: 565→118 lines
- Removed duplicate constants (import from MatchDetailPropertyDetails)
- PropertyDetailPublicSections: Preis/Hauptangaben/Eigenschaften/Wegzeit/Einheiten/Beschreibung/Quelle sections
- PropertyContactForm: Verwaltung kontaktieren form

Anfragen.tsx: 481→320 lines
- anfragenKiDetection.ts: STAGE_ORDER, STAGE_LABELS, KI_RULES, detectKiStage
- AnfragenMessageBubble: chat message bubble component
- AnfragenInquiryItem: inquiry list row component

AISearch.tsx: 398→342 lines
- needSearchMapper.ts: generateSummary + buildNeedInput pure functions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 00:55:55 +02:00
Benjamin Sutter 4d4ea6d2ff refactor: split PropertyDetailView (998→192 lines) and MatchDetail (464→236 lines)
PropertyDetailView.tsx extracted into 5 focused components:
- PropertyDetailHelpers: Field, FieldGrid, SectionTitle, floorLabel
- UnitStructurePanel: floor structure with unit matching
- PreMarketPanel: schattenmarkt release controls
- PropertyDetailOverview: overview tab content
- MatchabilityTabPanel: need matches tab

MatchDetail.tsx extracted into 2 focused components:
- MatchDetailHero: image/map, header, key facts strip
- MatchDetailPropertySections: Preis/Hauptangaben/Eigenschaften/etc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 00:48:13 +02:00
Benjamin Sutter 6515acb7f0 feat: unified error handling + AI service modularisation
Error handling (Prompt 2):
- src/services/errors.ts: AppError class, normalizeError(), throwServiceError() helper
- 6 services wrapped with try/catch (property, match, need, shortlist, futureSignal, inquiry)
- inquiryService aligned from custom ServiceResult<T> to standard ServiceResponse types
- Results, MatchCenter, FutureAvailability pages show <ErrorState onRetry> on query failure

AI modularisation (Prompt 3):
- src/services/aiService.ts reduced from 755 → 19 lines (barrel re-export)
- src/services/ai/IAIService.ts: typed interface + all response types
- src/services/ai/mock/: needParser, compareBuilder, decisionBrief, listingParser, MockAIService
- src/services/ai/openrouter/OpenRouterAIService.ts: model-agnostic skeleton
- src/services/ai/prompts/: 4 prompt template files (needParsing, matchExplanation, compareSummary, decisionBrief)
- src/services/ai/index.ts: factory selects Mock or OpenRouter via VITE_USE_REAL_AI flag
- All existing import paths unchanged — zero call-site modifications

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 00:32:01 +02:00
Benjamin Sutter 22c195b4a5 refactor: split large page components + taxonomy/HeatBadge/FutureAvailability improvements
- Taxonomy: merge VERIFIED_PORTFOLIO + EXTERNAL_MARKET display → 'Plattform' (dark blue) across all surfaces
- HeatBadge: new flame indicator for hot properties (grid, list, pipeline views)
- FutureAvailabilityContextPanel: richer detail page with AI summary, strategic assessment, sources
- Refactor Pipeline.tsx (630→152 lines) → pipeline/PipelineCard, PipelineColumn, PipelineDetailPanel, pipelineConstants, pipelineUtils
- Refactor IntelligenceMatchCard.tsx (483→179 lines) → FutureAvailabilityCard extracted
- Refactor MatchDetail.tsx (559→464 lines) → useMatchDetailData hook, MatchDetailPropertyDetails
- Refactor Compare.tsx (638→485 lines) → compareUtils, CompareCriteriaCard extracted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:36:03 +02:00
Benjamin Sutter 72e4f08900 fix: Compare→Pipeline flow, pipeline card navigation, remove floating AI button
- Remove CompareTray fixed bottom bar; replace with compare count badge on Vergleich nav item
- Pipeline cards redesigned to match search result card visual style (MatchScoreDisplay, type chip, stage chip, MapPin layout)
- CompareColumnHeader: redesigned with proper Details/Merken action buttons, clickable title
- AddToPipelineDialog now mounted on Compare page (was missing — bookmark had no effect)
- Pipeline card ExternalLink icon navigates to property detail page; detailPath prefers stable propertyId over volatile session matchId
- Add matchId field to PipelineItem domain; pipelineStore stores it on save
- All 8 mock pipeline items now have propertyId for reliable cross-session navigation
- Remove GlobalAIAssistantButton floating overlay (was blocking form submissions and clicks)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 22:11:29 +02:00
Benjamin Sutter 6a6ff7f2e2 feat: Pipeline↔Anfragen integration + Compare→Pipeline + KI stage detection
Navigation: Deal Pipeline moved after Vergleich (before Anfragen)

Compare → Pipeline:
- Bookmark icon per column header; BookmarkCheck when already in pipeline
- Passes propertyId, propertyAddress, area/rent labels on save

Pipeline cards now unit-level:
- propertyAddress shown with MapPin on every card
- Chat icon (MessageSquare) on cards with linked inquiry → navigates to /demand/anfragen?inquiry=xxx
- Detail panel: Chat chip links to specific inquiry thread, propertyAddress displayed

Anfragen → Pipeline KI detection:
- Keyword scan on every sent message (besichtigung → VISITED, mietvertrag → NEGOTIATION, unterschrieben → CLOSED_WON)
- Only advances stage, never goes back
- Purple KI alert banner with direct Pipeline link, auto-dismisses after 6s
- Pipeline badge in inquiry list + stage chip in chat header with nav link
- URL param ?inquiry=xxx pre-selects inquiry (used from Pipeline chat button)

Domain: PipelineItem gains propertyId, unitId, propertyAddress, inquiryId
Mock data: pl-001/pl-002/pl-004 linked to inq-001/inq-005/inq-004

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 21:42:21 +02:00
Benjamin Sutter a002597f5b feat: drag & drop for Pipeline Kanban columns (@dnd-kit)
Cards can be dragged between all 7 stages. Drop target highlights with
a dashed border + tinted background. A floating card overlay follows
the cursor during drag. Click-to-select still works (fires only when
no drag occurred, guarded by activeId check).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 21:34:00 +02:00
Benjamin Sutter de00f758e9 feat: merge Merkliste into Pipeline (SAVED stage) + Anfragen chat page
- Remove standalone Merkliste/Shortlists nav item; pipeline now covers the full funnel
- Add SAVED as first PipelineStage — 'Merken' on result cards lands here
- pipelineStore (Zustand) holds shared items; AddToPipelineDialog replaces AddToShortlistDialog in demand workspace
- New /demand/anfragen split-panel: searchable inquiry list + chat thread with compose
- Pipeline detail panel: KI insight, stage actions, editable notes, mock documents
- ShortlistItemCard: cards clickable → property detail, Anfrage button with inline dialog
- Supply workspace (FutureAvailability, Anfragencenter) unchanged

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 21:31:30 +02:00
Benjamin Sutter 76f9927c7c fix: UX polish — score formula, role badges, compare view, page titles
- Score: remove hidden dataQuality/confidence modifiers from finalScore;
  formula now correctly shows hard×60% + soft×40% = displayed total
- "Ihr Objekt" badge: only shown for PROPERTY_MANAGER/ORGANIZATION_ADMIN
  in both IntelligenceMatchCard and MatchCardHeader (DEMAND_USER sees
  Verified Portfolio as a normal listing without ownership indicator)
- Compare: fix factor lookup to use allFactors (includes mid-range 45–70
  scores) so Prestige, Erreichbarkeit etc. no longer show "Nicht verfügbar"
- Compare: richer AI summary with overallAssessment, perPropertyAssessment
  (strengths/weaknesses/bestFor/keyRisk per property), and recommendation
- Compare/Results: pb:10 so CompareTray never overlaps last row of content
- AppShell: dynamic route names for /demand/results/:id → "Match Detail"
  and /demand/property/:id → "Objekt Detail" (no more UUID in header)
- MatchDetail: remove "Match EF9" debug chip from sticky nav
- LocationIntelligencePanel: comparable listings are now clickable links
  navigating to /demand/property/:id
- Comparable links: blue text + hover highlight

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 21:05:36 +02:00
Benjamin Sutter 9f7062d137 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>
2026-05-22 20:31:30 +02:00
Benjamin Sutter 9f877614c1 fix: soft factor scores use 0-1 scale matching the scoring engine
LOW=0.30 / MEDIUM=0.55 / HIGH=0.85 — previously 3/5/8 which clamped
every soft factor to 100 regardless of level chosen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 10:11:18 +02:00
Benjamin Sutter 955da564a0 fix: invalidate properties cache after listing creation
Without this, navigating to Meine Inserate within 5 min of visiting
it previously would show a stale empty list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 21:22:34 +02:00
Benjamin Sutter c9324e9cc8 feat: expand NewListing with AI, soft factors, hard facts, images + MyListings manager
- KI-Hilfe: text description → auto-fills all fields (parseListingText mock parser)
- Lage & Ausstrahlung: 9 soft factor selects (Tief/Mittel/Hoch) mapped to scoring engine
- Technische Details: floor, fit-out, parking, ceiling height
- Bilder: URL list with add/remove
- New /supply/my-listings page: list, status toggle, delete for DIRECT listings
- Added sourceType filter to PropertyFilters + MockupPropertyProvider
- Nav entry "Meine Inserate" added to supply sidebar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 21:19:45 +02:00
Benjamin Sutter 0dac528f98 fix: remove Quelle & Herkunft panel from demand MatchDetail
Internal data provenance is irrelevant for property seekers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 21:12:33 +02:00
Benjamin Sutter 67d3d06a53 feat: Inserat erstellen — direct listing flow for units and standalone
- New /supply/new-listing page (Path A: pre-filled from portfolio unit,
  Path B: standalone blank form); creates EXTERNAL_MARKET + sourceType DIRECT
- "+ Inserat" button on available units in PropertyDetailView navigates
  with prefilled address/area/rent
- Nav entry "Neues Inserat" added to supply sidebar
- SourceProvenancePanel hidden for DIRECT source listings (no external source)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 21:10:41 +02:00
Benjamin Sutter 113274cfc7 fix: remove need selector chip strip from results page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 20:51:59 +02:00
Benjamin Sutter d8cce96bd8 fix: hide 'Neue Suche' entries from need selector chips
Only named needs (company name != 'Neue Suche') appear in the chip strip.
Default active need is also the first named need, not the most-recently-created
test search.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 20:50:22 +02:00
Benjamin Sutter 11ecd5d900 fix: need selector on results page + retail budget parsing
- Results page: chip strip shows all saved searches — click to switch without
  creating a new search. Defaults to first need instead of most-recently-created
  so pre-existing mock needs load immediately on navigation.
- AI parser: asset-type-aware monthly budget threshold (RETAIL: 500, others: 150)
  so "150 CHF/m²" for retail is correctly converted to 1800 CHF/m²/year instead
  of being stored as an impossibly low annual value that excludes all properties.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 20:47:14 +02:00
Benjamin Sutter 27c53f3af1 feat: score transparency on all cards + budget parser fix
- Single scoring system: MockupNeedProvider rewrites matches via calculateScore
  exclusively — allFactors always populated, no more dual-system (computeScore
  removed), weights from weightingProfile reflected in every breakdown
- ScoreInlineBreakdown: new component shows hard/soft criteria with importance
  labels (Entscheidend/Sehr wichtig/…) and formula on compact + expanded cards
- MatchCardAdapter: passes scoreBreakdown + allFactors to ViewModel
- MatchDetail: 'Zukunftssignal' label replaced with 'Future Availability'
- aiService budget parser: values < 100 treated as monthly and multiplied by 12
  to produce correct annual CHF/m²/year value — fixes 0-result searches

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 20:41:46 +02:00
Benjamin Sutter 69b96f293a refactor: remove Schattenmarkt from all user-visible UI strings
- Pipeline.tsx: FUTURE_AVAILABILITY label → 'Future Availability'
- ReminderTypeBadge: SCHATTENMARKT_RELEASE label → 'Pre-Market'
- ReminderFilterBar: SCHATTENMARKT_RELEASE label → 'Pre-Market'
- ReminderKpiBar: 'Schattenmarkt-Risiko' → 'Pre-Market Risiko'
- ReminderDetailDrawer: section title + switch label → 'Pre-Market'
- mock-data/matches: explainabilitySummary → 'Pre-Market freigegeben'
- mock-data/reminders: notes → 'Pre-Market Freigabe'

Internal identifiers (schattenmarktRelease field, useSchattenmarktSignals hook,
signal ID prefix schattenmarkt-*) are unchanged — renaming them touches too many
call sites for no user-visible gain.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 00:11:35 +02:00
Benjamin Sutter d902feb6c0 feat: Objekt→Einheit architecture — unit-level pre-market, property detail page, click-through from cards
- Domain: PropertyUnit extended with propertyId + schattenmarktRelease per unit
- Domain: FutureAvailabilityResult carries resolved property + unit
- useSchattenmarktSignals: generates unit-level signals (schattenmarkt-{propId}-{unitId})
- useUnifiedResults: resolves backing property + unit on FUTURE_AVAILABILITY fast path
- IUnitProvider + MockupUnitProvider: first-class unit access and mutation
- matchCardAdapter: maps preMarketUnit, preMarketAllUnits, propertyId, unitId to ViewModel
- IntelligenceMatchCard: PRE-MARKET VERIFIED shows unit info strip + "Zur Einheit →" button
- PropertyDetailView: unit-level toggles + date pickers inside PreMarketPanel
- New page: /demand/property/:propertyId with unit table, status chips, inquiry form
- App.tsx: demand route /demand/property/:propertyId registered
- Mock data: prop-001/007/037 units updated with correct lease dates + unit-level schattenmarktRelease

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 19:47:59 +02:00
Benjamin Sutter 0dc04a14c5 fix: MarketLeads zeigt 0 Leads — Provider liest live aus Mock-Daten
MockupFutureSignalProvider kopierte mockFutureSignals einmalig in einen
store-Array beim Modulload. Vite HMR und React Query Cache (staleTime 5min)
führten dazu, dass signalDirection-Feld in gecachten Resultaten fehlte.

Lösung: Provider liest jetzt immer direkt aus dem lebenden mockFutureSignals-
Import via resolve()-Helper. Mutationen (verify, updateStatus) werden in einer
Map gespeichert und beim Lesen überlagert. Damit sind HMR-Änderungen an
Mock-Daten sofort sichtbar ohne Browser-Neustart.

Ausserdem: irreführenden CircularProgress aus Empty-State entfernt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 16:02:32 +02:00
Benjamin Sutter b0ad6643ff feat: Future Availability — Logiktrennung Supply/Demand + Markt-Leads
EXPANSION-Signale (Unternehmen sucht Fläche) sind Nachfragesignale und
gehören nicht in den Demand-Feed. Neue Architektur:

- signalDirection: 'SUPPLY' | 'DEMAND' auf FutureSignal-Domain
- SUPPLY-Signale: POSSIBLE_MOVE_OUT / CONSTRUCTION_PROJECT (Crawler)
  und LEASE_EXPIRY+isVerified (Verwaltung ERP) → Demand-Feed wie bisher
- DEMAND-Signale: EXPANSION → aus Demand-Feed gefiltert
- Neues useMarketLeads-Hook + /supply/market-leads Seite für Verwaltung:
  zeigt EXPANSION-Signale als potenzielle Mieter mit Portfolio-Matching
- Alle 15 Signale in futureSignals.ts inhaltlich bereinigt und
  mit korrekter signalDirection versehen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 15:56:29 +02:00
Benjamin Sutter 5fe15bac62 feat: Future Availability Intelligence — rebrand & card redesign
Replaces "Schattenmarkt" branding with professional Future Availability
Intelligence throughout the UI. Two distinct card types: Pre-Market
(LEASE_EXPIRY + verified, green accent) and Market Signal (probabilistic,
blue accent). Professioneller Data-Header replaces dark purple gradient.
New detail panel sections: Erkannte Marktindikatoren, Strategische
Interpretation, Bestätigt/Nicht bestätigt. All 15 mock signals enriched
with concrete Swiss CRE market indicators and strategic interpretations.
Signal quality shown as 4-dot indicator (Hoch/Mittel/Niedrig).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 13:06:16 +02:00
Benjamin Sutter d5a4862a28 feat: redesign Schattenmarkt card & detail view for explainability
- Remove abstract Konfidenz/DQ badges from match detail header
- Replace ScoreBreakdownPanel with 3-step Bewertungsherleitung for
  FUTURE_AVAILABILITY: Kriterien-Match per factor with bars, Signal-
  Abschlag (verified ERP = kein Abschlag / probabilistic = -N Punkte),
  Gesamt-Score
- Restructure FutureAvailabilityContextPanel: source section first,
  verified ERP box for LEASE_CONTRACT, remove Konfidenz row, disclaimer
  as grey footnote instead of yellow alert
- Add LEASE_EXPIRY to signal type/relevance bridge labels
- Show up to 3 match reasons with explanation on cards (was 1)
- matchCardAdapter: return top-3 positive factors as reasons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 12:39:12 +02:00
Benjamin Sutter 9f391d17cb feat: Reminder Manager + Schattenmarkt-Freigabe + mock data overhaul
- Add Reminder Manager page (/supply/reminder-manager) with KPI bar,
  filter bar, list/card feed, and detail drawer (7 sections incl.
  activity log, snooze, complete, dismiss actions)
- Add Schattenmarkt-Freigabe toggle on PropertyDetailView: Verwaltung
  can opt-in properties for early market exposure before contract expiry
- Auto-generate FutureSignal cards via useSchattenmarktSignals hook
  when leaseEndDate - leadTimeMonths <= MOCK_TODAY
- Fix useUnifiedResults: use property.resultType as fallback (was
  defaulting everything to VERIFIED_PORTFOLIO)
- Fix demand Results: hide VERIFIED_PORTFOLIO from non-manager users
  even when showOwnProperties toggle was previously enabled
- Fix AppShell: redirect to allowed workspace on user role switch
- Fix 3 wrong match scores (match-002: 93→62, match-009: 86→55,
  match-017: 87→52)
- Add 7 new match records (match-050–056) for need-001, need-002,
  need-011
- Add need-011 (Retail Bern Innenstadt)
- Add prop-031–036 (EXTERNAL_MARKET / MAISON_WORK / FUTURE_AVAILABILITY)
- Fix duplicate image URLs across all properties
- Add signal-011 (Bern Altstadt, Mode Boutique) + propertyId to
  signal-001

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 11:52:24 +02:00
Benjamin Sutter 0663757cde fix: Schattenmarkt bypass source filter + bump external/maison match scores for all tiers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 23:10:41 +02:00
Benjamin Sutter 3756675b6f feat: add Maison Work source type, rename ExternalMarket to Direktinserat, hide own portfolio from demand search by default
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 23:02:05 +02:00
Benjamin Sutter a521155bb2 feat: mix Schattenmarkt into result tiers, add filter toggle
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 22:36:27 +02:00
Benjamin Sutter a36a827564 feat: reduce demo login to Verwaltung + Bürosuche only
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 20:35:55 +02:00
Benjamin Sutter d15a13e485 feat: remove Administration workspace — keep only Verwaltung + Suche
- Delete all ops page components (ReviewQueue, AIMonitoring, Governance,
  SourceMonitoring, ActivityTimeline, SignalPipeline)
- Remove OPERATIONS workspace from AppShell config, nav order, path detection
- Remove all /ops/* routes from App.tsx
- Remove WorkspaceType.OPERATIONS from allowedWorkspaces in authService,
  sessionStore, permissions
- Keep MarketIntelligence page (already moved to /supply/market-intelligence)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 20:32:41 +02:00
Benjamin Sutter 18f4b2b37a feat: navigate from Marktsignale tab to Anfragencenter latente Anfragen
'Angebot erstellen' button in Marktsignale tab pre-selects the property
in the offer store and navigates to /supply/anfragen on the Latente
Anfragen tab. Also fixes offerWizardStore.open() to preserve
selectedPropertyIds so pre-selection survives the wizard open call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 16:00:00 +02:00
Benjamin Sutter 920ce8eb09 feat: Verwaltungszugang refactor — lease data, 3-tab detail, market signals
- Dashboard: remove QuickActionPanel, StrongMatchOverview, FutureSignalWidget,
  DataQualityWidget, header buttons, Marktchancen nav; KpiGrid → 4 cards
- Property domain: 9 new fields (leaseTerm, leaseStartDate/End, breakoutOption,
  currentTenant, importedFrom, importedAt, lastUpdatedAt)
- Mock data: annual CHF/m²/Jahr prices (×12), Swiss images, tenant/lease data
  for all 10 VERIFIED_PORTFOLIO properties; need budgets updated to annual
- PropertyTable: swap columns — add Aktueller Mieter, Mietlaufzeit,
  Breakoutoption, Breakoutoption Zeitpunkt; remove Verfügbarkeit, Konfidenz, Quelle
- PropertyDetailView: 3 tabs (Übersicht, Matchability, Marktsignale) with
  inline edit mode, NeedMatchCard list, PropertyMarketSignalsTab
- New: marketReport domain + mock data + service, NeedMatchCard,
  PropertyMarketSignalsTab with 4 sections + PDF button
- matchService: getNeedMatchesForProperty(propertyId, {minScore})

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 15:43:52 +02:00
Benjamin Sutter 14badd1ca8 feat: all 4 UX priorities — wizard, examples, onboarding, pipeline
- Offer wizard: 4→3 steps by merging 'Prüfen' into PDF review step
- Search mask: quick-select chips for area ranges, budget, and city presets
- Onboarding: WelcomeDialog (3 slides, localStorage) on first visit
- Deal Pipeline: Kanban board (6 stages, 8 mock items, advance-to-next-stage)
  added to demand workspace navigation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:09:06 +02:00
Benjamin Sutter a75818b983 feat: full responsive layout — mobile sidebar, stacked panels, fullscreen drawers
- AppShell: permanent sidebar on desktop, temporary drawer + hamburger on mobile (<md)
- TopBar: hamburger icon on mobile, AI-Assistent als Icon-Button auf kleinen Screens
- MatchDetail: Sidebar stacks below content on mobile, key facts wrap, px responsive
- Properties: Drawer fullscreen (100vw) on mobile, 650px on desktop
- ActiveInquiriesTab: mobile shows list → tap opens detail with back button
- LatentInquiriesTab: mobile sequential navigation (list → detail → own properties), tablet 2-col, desktop 3-col
- PublicNeedList: fullWidth prop for mobile layout
- InquiryDetailPanel: property card stacks below chat on mobile

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 21:24:00 +02:00
Benjamin Sutter c4c29ef7d3 feat: F030 Anfragencenter — Aktive & Latente Anfragen + Angebot-Wizard
Neuer Menüpunkt «Anfragencenter» (ehemals «Eingehende Bedarfe»):

Aktive Anfragen:
- Split-View: Anfrageliste (Liste/Grid-Toggle) + Chat-Detailansicht
- Chat-Verlauf mit Sender-Styling (Mieter links, Supply rechts)
- Antwortformular mit Betreff, Nachricht, Anhänge, Statuswechsel
- Zugehörige Property Card neben dem Chat

Latente Anfragen:
- Drei-Spalten-Layout: Need-Liste | Need-Detail | Eigene Objekte
- Need Cards mit AI-Zusammenfassung, Must-Haves, Präferenzen
- Eigene Portfolio-Objekte sortiert nach deterministischem Match-Score
- Checkbox-Selektion für Angebotsauswahl

Angebot-Wizard (4 Schritte):
- Schritt 1: Objekte auswählen (mit Score-Vorschau)
- Schritt 2: PDF-Vorschau + editierbare Textfelder
- Schritt 3: Angebot prüfen & bestätigen
- Schritt 4: Nachricht an Suchenden mit KI-generierter Mail

Architektur:
- Domain: Inquiry, LatentNeed, OfferDraft Types
- Provider: IInquiryProvider, ILatentNeedProvider, IOfferProvider + Mockups
- Services: inquiryService, latentNeedService, offerService
- Hooks: useInquiries, useLatentNeeds, useOffers (TanStack Query)
- Store: offerWizardStore (Zustand, lokaler Wizard-State)
- 27 neue Komponenten, alle Loading/Empty/Error States

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 18:03:05 +02:00
Benjamin Sutter 05ac0083b2 feat: Steuerlast-Kriterium, ImmoScout-Layout, Gold/Silver/Bronze-Trennung
- Flächensuche: Flexibilität durch Steuerlast (taxEnvironment) ersetzt
- MatchDetail: Bild als Hero-Banner oben, Karte eingebettet im Inhalt darunter
- Ergebnisliste: Gold/Silber/Bronze-Abschnitte mit farbigen Trennlinien
- ScoreBreakdown: KI-Steuerlast-Link zur kantonalen Steuerrechner-Seite
- Beispieldaten: alle Objekte mit passenden Bildern versehen
- locationIntelligence: taxCalculatorUrl pro Kanton ergänzt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 14:14:10 +02:00
Benjamin Sutter 5b917bd619 feat: embedded interactive map in property + match detail views
Integrates react-leaflet with OpenStreetMap tiles. PropertyDetailView
and MatchDetail now show property photo + zoomable/pannable map above
the tabs/content — always visible on card click, no redirect. Map
renders at correct lat/lng with a location marker and popup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 13:34:57 +02:00
Benjamin Sutter c75d3c8121 feat: F029 intelligence card system — premium AI-native CRE cards
Gold/Silver/Bronze FIFA-style score badges overlaid on location imagery.
List/grid view toggle on Results and Properties pages (persisted in
localStorage). IntelligenceMatchCard for match results grid view;
PropertyIntelligenceCard for property grid view with warm brown theme.
LocationPreview component with Google Maps link. 10 mock properties
enriched with Unsplash building images.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 13:25:27 +02:00