# Property Match — Architecture Reference

> This document describes the architecture of the Property Match platform.
> For coding rules see [CLAUDE.md](./CLAUDE.md).
> For state management patterns see [STATE_MANAGEMENT.md](./STATE_MANAGEMENT.md).

---

## 1. Folder Structure

```
src/
├── assets/                      Static images, icons
│
├── domain/                      TypeScript types & enums (single source of truth)
│   ├── enums.ts                 AssetType, ResultType, MatchStatus, UserRole, WorkspaceType
│   ├── match.ts                 Match, ScoreBreakdown, ScoreFactor, TradeOff, Risk
│   ├── property.ts              Property, Location, ContactPerson
│   ├── need.ts                  Need, AreaRange, BudgetRange, WeightingProfile
│   ├── unifiedResult.ts         UnifiedMatchResult (discriminated union)
│   ├── scoring.ts               ScoringWeightProfile, MatchEngineOutput
│   ├── aiOutput.ts              AI scoring, extraction, monitoring types
│   ├── futureSignal.ts          Future availability signals
│   ├── pipeline.ts              Pipeline stages & items
│   ├── inquiry.ts               Demand-side inquiry records
│   ├── reminder.ts              Reminder & DueDate
│   └── index.ts                 Barrel re-exports
│
├── features/                    Business logic algorithms (pure TypeScript)
│   └── matching/
│       ├── scoreCalculator.ts   Hard/soft scoring, confidence & DQ modifiers
│       ├── rankingEngine.ts     Match ranking, unified result assembly
│       ├── mustHaveScorer.ts    Must-have criteria evaluation
│       ├── tradeOffAnalyzer.ts  Tradeoff, risk, and missing-data analysis
│       └── matchCardAdapter.ts  Match → MatchCardViewModel adapter
│
├── provider/                    Data access layer (interface + mock per entity)
│   ├── IPropertyProvider.ts
│   ├── MockupPropertyProvider.ts
│   ├── INeedProvider.ts
│   ├── MockupNeedProvider.ts
│   └── ... (one pair per entity)
│
├── services/                    Business logic services
│   ├── ai/
│   │   ├── IAIService.ts        AI capability interface
│   │   ├── MockAIService.ts     Deterministic mock (dev/CI)
│   │   ├── OpenRouterAIService.ts  Real LLM calls (production)
│   │   └── prompts/             Structured prompt templates
│   ├── matchService.ts
│   ├── needService.ts
│   ├── propertyService.ts
│   ├── aiMonitoringService.ts
│   ├── governanceService.ts
│   ├── types.ts                 ListResponse<T>, ItemResponse<T>
│   └── errors.ts                throwServiceError()
│
├── hooks/                       React Query wrappers
│   ├── useProperties.ts
│   ├── useMatches.ts
│   ├── useUnifiedResults.ts     Aggregates all result types for demand feed
│   └── ... (one file per entity)
│
├── stores/                      Zustand UI stores (global UI state only)
│   ├── sessionStore.ts          Auth, currentUser, workspace
│   ├── compareStore.ts          Compare tray items (max 4)
│   ├── pipelineStore.ts         Pipeline items + add-dialog
│   ├── assistantStore.ts        AI assistant drawer conversation
│   ├── offerWizardStore.ts      Multi-step offer wizard
│   ├── layoutStore.ts           Sidebar, right panel, active workspace
│   ├── matchCenterStore.ts      Supply match center selection
│   ├── reminderStore.ts         Reminder filters + drawer
│   ├── shortlistStore.ts        Shortlist selection + add-dialog
│   └── toastStore.ts            Toast notification queue
│
├── lib/                         Shared utilities & design system
│   ├── constants.ts             All thresholds, labels, stale times, routes
│   ├── ds.ts                    DS_COLORS, RESULT_TYPE_META, score → level converters
│   ├── utils.ts                 matchScoreHex, confidenceHex, dataQualityColor, formatters
│   ├── theme.ts                 MUI theme
│   ├── queryClient.ts           TanStack Query client config
│   ├── permissions.ts           Role-based access helpers
│   ├── locationIntelligence.ts  Location scoring
│   └── propertyHeat.ts          Market heat analysis
│
├── components/                  UI components (organized by feature)
│   ├── layout/                  AppShell, PageHeader, RightContextPanel
│   ├── ui/                      ErrorBoundary, EmptyState, Toast, DecisionContextPanel
│   ├── shared/                  LocationPreview, ViewToggle (cross-feature)
│   ├── match-card/              MatchCard variants (compact/expanded/review/compare-mini)
│   ├── match-detail/            MatchDetailHero, ScoreBreakdownPanel, CriterionRow
│   ├── compare/                 CompareMetricRow, CompareColumn, compareUtils
│   ├── pipeline/                PipelineCard, PipelineColumn, pipelineConstants, pipelineUtils
│   ├── results/                 UnifiedResultFeed, ResultFilterBar, ResultTypeBadge
│   ├── supply/                  PropertyIntelligenceCard, ReminderFeed, ReminderListRow, ...
│   ├── demand/                  NeedInput, WeightingEditor, CriteriaReviewPanel, ...
│   ├── future-signals/          SignalCard, SignalFilterBar, FutureAvailabilityPanel, ...
│   ├── data-quality/            DataQualityPanel, FreshnessIndicator, ProvenancePanel, ...
│   ├── badges/                  ConfidenceBadge, RiskBadge, FreshnessBadge, ...
│   ├── ai-monitoring/           AI audit log UI
│   └── ...
│
├── pages/                       Route-level page components
│   ├── auth/LoginScreen.tsx
│   ├── supply/                  SupplyDashboard, Properties, MatchCenter, ...
│   ├── demand/                  AISearch, Results, MatchDetail, Compare, Pipeline, ...
│   └── ops/                     MarketIntelligence
│
├── mock-data/                   Seed data for mock providers
│
├── App.tsx                      React Router setup (all routes, lazy-loaded)
└── index.css                    Tailwind layer imports (no project styles here)
```

---

## 2. Data Flow Overview

The core data flow is **unidirectional** across four layers.

```
┌─────────────────────────────────────────────────────────────────────────┐
│                          Component / Page                                │
│                                                                          │
│   Reads: useQuery hook         Writes: useMutation hook                 │
│   UI state: Zustand selector   Side effects: useEffect                  │
└────────────────┬──────────────────────────┬────────────────────────────┘
                 │ calls                     │ calls
                 ▼                           ▼
┌────────────────────────────────────────────────────────────────────────┐
│                             Hook Layer                                  │
│                        src/hooks/*.ts                                   │
│                                                                         │
│   useQuery({ queryFn: () => service.getAll() })                        │
│   useMutation({ mutationFn: (data) => service.create(data) })          │
└────────────────────────────────┬───────────────────────────────────────┘
                                 │ calls
                                 ▼
┌────────────────────────────────────────────────────────────────────────┐
│                           Service Layer                                 │
│                        src/services/*.ts                                │
│                                                                         │
│   Business logic, error handling, response standardization             │
│   Reads session via sessionStore.getState()                            │
│   Calls scoring engine (features/matching/) as needed                  │
└────────────────────────────────┬───────────────────────────────────────┘
                                 │ calls
                                 ▼
┌────────────────────────────────────────────────────────────────────────┐
│                           Provider Layer                                │
│                        src/provider/*.ts                                │
│                                                                         │
│   IPropertyProvider (interface)                                        │
│   MockupPropertyProvider (in-memory, dev)  ← swap →  RestPropertyProvider (prod)
└────────────────────────────────────────────────────────────────────────┘
```

---

## 3. Provider Swap Flow

Swapping from mock to real requires **one file change** — the service import. No hooks, components, or tests change.

```
Dev / CI                                Production
─────────────────────────────────────   ─────────────────────────────────────
propertyService.ts                      propertyService.ts
  import provider from                    import provider from
    './MockupPropertyProvider'              './RestPropertyProvider'
                                                  ↑
                                         One line change — nothing else
```

Provider interfaces enforce the contract. All methods are `async`. Mock providers hold in-memory seed data from `src/mock-data/`.

---

## 4. State Architecture

```
                  ┌──────────────────────────────────────────┐
                  │              React Query Cache            │
                  │                                          │
                  │   ['properties']  ──  5 min stale        │
                  │   ['matches', needId]  ──  2 min stale   │
                  │   ['signals']  ──  5 min stale           │
                  │   ['review-queue']  ──  30 s stale       │
                  │                                          │
                  │   ← server data, cached, invalidated     │
                  │     on mutation success →                │
                  └──────────────────────────────────────────┘

                  ┌──────────────────────────────────────────┐
                  │              Zustand Stores              │
                  │                                          │
                  │   sessionStore    ← auth, currentUser    │
                  │   layoutStore     ← sidebar, workspace   │
                  │   compareStore    ← compare tray items   │
                  │   pipelineStore   ← pipeline + dialog    │
                  │   assistantStore  ← AI chat conversation │
                  │   offerWizardStore← multi-step wizard    │
                  │   reminderStore   ← filter + drawer      │
                  │   toastStore      ← notification queue   │
                  │                                          │
                  │   ← UI state only, no server data →      │
                  └──────────────────────────────────────────┘

                  ┌──────────────────────────────────────────┐
                  │           Component Local State          │
                  │                                          │
                  │   useState  ← form inputs, toggles       │
                  │   useMemo   ← derived / filtered lists   │
                  │   useCallback ← stable callbacks         │
                  └──────────────────────────────────────────┘
```

---

## 5. Matching & Scoring Flow

The matching engine (`src/features/matching/`) produces all scores. Components never compute scores — they display them.

```
Need (criteria)    +    Property (attributes)
       │                        │
       └───────────┬────────────┘
                   ▼
        ┌─────────────────────┐
        │   scoreCalculator   │
        │                     │
        │  1. Hard filters    │  ← Exclusions (wrong type, out of area, etc.)
        │  2. Hard scoring    │  ← Area, location, budget, timing, asset type (0–100 each)
        │  3. Soft scoring    │  ← Prestige, accessibility, ESG, footfall, etc. (0–100 each)
        │  4. Modifiers       │  ← confidenceModifier (-20→+5) + dataQualityModifier (-15→0)
        │  5. totalScore      │  ← Weighted average + modifiers (0–100)
        └──────────┬──────────┘
                   │
                   ▼
        ┌─────────────────────┐
        │   tradeOffAnalyzer  │  ← TradeOff[], Risk[], MissingDataItem[]
        └──────────┬──────────┘
                   │
                   ▼
        ┌─────────────────────┐
        │   rankingEngine     │  ← Builds Match[], ranks, adds NextBestAction[]
        └──────────┬──────────┘
                   │
                   ▼
        ┌─────────────────────┐
        │  matchCardAdapter   │  ← Match → MatchCardViewModel (UI props)
        └──────────┬──────────┘
                   │
                   ▼
           MatchCard / MatchDetail / Compare
```

**ScoreBreakdown shape:**

```ts
{
  hardMatchScore: number       // 0–100: area, location, budget, timing, usage
  softFactorScore: number      // 0–100: prestige, accessibility, ESG, etc.
  confidenceModifier: number   // –20 → +5
  dataQualityModifier: number  // –15 → 0
  totalScore: number           // 0–100 final score
  positiveFactors: ScoreFactor[]   // top reasons this is a good match
  negativeFactors: ScoreFactor[]   // top concerns
  tradeOffs: TradeOff[]            // explicit trade-off explanations
  risks: Risk[]                    // risk categories
  missingData: MissingDataItem[]   // fields that would improve the score
}
```

---

## 6. AI Flow

```
User input (text / voice)
         │
         ▼
  NeedInput component
         │  calls
         ▼
  aiService.parseNeed(text)
         │  routes to
         ├── MockAIService (dev) ──────────────► deterministic ParsedNeed
         └── OpenRouterAIService (prod) ────────► LLM API call
                    │
                    │  validates with Zod
                    ▼
             ParseNeedResult
             { need: Partial<Need>, confidence: number, followUpQuestions: string[] }
                    │
                    ▼
       CriteriaReviewPanel
       (user confirms / overrides AI-extracted criteria)
                    │
                    ▼
          Need saved → Results feed runs
```

**AI Governance loop:**

```
Every AI call
     │
     ├─► aiMonitoringService.log({ modelId, promptHash, responseDigest, userId, ... })
     │
     └─► Visible in /ops/ai-monitoring (review queue for ops team)
```

**AI never speaks directly to the UI.** All AI outputs go through:
1. `IAIService` interface method
2. Zod validation
3. Typed domain object
4. Component that renders it with explainability metadata

---

## 7. Unified Result Feed Flow (Demand)

The demand workspace shows a unified feed that merges three result types.

```
useUnifiedResults(needId)
         │
         ├─► matchService.getForNeed(needId)
         │        └─► VERIFIED_PORTFOLIO matches (own properties)
         │        └─► EXTERNAL_MARKET matches (partner/scraped)
         │
         ├─► aiService.getMaisonWorkResults(needId)
         │        └─► MAISON_WORK matches (AI-suggested)
         │
         └─► futureSignalService.getForNeed(needId)
                  └─► FUTURE_AVAILABILITY signals (AI-predicted)
                           ├── probabilityScore (0–1)
                           ├── signalBasis (string)
                           └── expectedAvailabilityDate

All merged into UnifiedMatchResult[]
Sorted by matchScore desc (default)
         │
         ▼
UnifiedResultFeed
  ├── MatchCard (VERIFIED_PORTFOLIO, EXTERNAL_MARKET, MAISON_WORK)
  └── FutureAvailabilityCard (FUTURE_AVAILABILITY — different visual treatment)
```

**Future Availability signals are probabilistic** — they must always show `probabilityScore` and a basis explanation. They are never presented as confirmed availability.

---

## 8. Three-Workspace Architecture

```
App.tsx
  │
  ├── /auth/login  → LoginScreen
  │
  ├── /supply/*  ──────────────────────────────────► SUPPLY workspace
  │     Protected: WorkspaceType.SUPPLY             (PropertyManager, OrgAdmin)
  │     Pages: SupplyDashboard, Properties,
  │            MatchCenter, Anfragencenter,
  │            FutureAvailability, DataQuality,
  │            ReminderManager, MarketLeads,
  │            NewListing, MyListings
  │
  ├── /demand/*  ──────────────────────────────────► DEMAND workspace
  │     Protected: WorkspaceType.DEMAND             (Tenant, CompanyAdmin)
  │     Pages: AISearch, Results, MatchDetail,
  │            Compare, Pipeline, Anfragen,
  │            PropertyDetail, Shortlists
  │
  └── /ops/*  ────────────────────────────────────► OPERATIONS workspace
        Protected: WorkspaceType.OPERATIONS         (Internal staff)
        Pages: MarketIntelligence
```

Route protection enforced by `<ProtectedRoute workspace={...} />`. Each workspace has distinct data flows and does not share components with other workspaces unless through `src/components/shared/` or `src/components/ui/`.

---

## 9. Decision Screen Design Pattern

Every page is built around **one user decision**. The `DecisionContextPanel` component surfaces this explicitly at the top of each page.

```
┌─────────────────────────────────────────────────────────────┐
│ PageHeader — title, subtitle, secondary actions (ViewToggle) │
├─────────────────────────────────────────────────────────────┤
│ DecisionContextPanel                                         │
│   decision: "Which matches are ready to shortlist?"         │
│   context:  "Search: OFFICE · 200–500 m² · Zürich"         │
│   metrics:  [strongMatches: 3 ✓] [withGaps: 2 ⚠]          │
│   risks:    ["2 results have missing floor plan data"]      │
│   actions:  [Open Compare] [Refine Search]                  │
├─────────────────────────────────────────────────────────────┤
│ FilterBar / Controls                                         │
├─────────────────────────────────────────────────────────────┤
│ Content (list / grid / table / kanban)                       │
└─────────────────────────────────────────────────────────────┘
```

If a screen cannot answer "what decision does this help the user make?", the design is wrong.

---

## 10. Error Boundary Architecture

```
App.tsx
└── AppErrorBoundary (top-level catch-all)
    └── Route components
        └── Feature-level try/catch in services
            └── Error state passed via useQuery error prop
                └── <ErrorState message={...} /> or <UnauthorizedState />
```

Every page handles three states: loading (`<FeedSkeleton />`), error (`<ErrorState />`), and empty (`<FeedEmptyState />`). These are never omitted.

---

## 11. Component Dependency Rules

```
pages/          can import from   components/, hooks/, stores/, lib/, domain/
components/     can import from   components/, hooks/, stores/, lib/, domain/
hooks/          can import from   services/, lib/, domain/
services/       can import from   provider/, features/, lib/, domain/
features/       can import from   lib/, domain/
provider/       can import from   domain/, mock-data/
lib/            can import from   (nothing in src — pure utilities)
domain/         can import from   (nothing — types only)
stores/         can import from   domain/, lib/
```

**Forbidden imports:**
- `services/` must not import React hooks
- `domain/` must not import anything from `src/`
- `lib/constants.ts` must not import from components or services
- Circular imports of any kind

---

## 12. Key Design System Tokens

```
Scores (0–100 scale)
  ≥ 80  →  STRONG   →  DS_COLORS.confidence.high   (#1a7a4a)
  ≥ 60  →  MODERATE →  DS_COLORS.confidence.medium (#d97706)
  < 60  →  WEAK     →  DS_COLORS.confidence.low    (#c0392b)

Confidence (0–1 scale)
  ≥ 0.85  →  HIGH    →  DS_COLORS.confidence.high
  ≥ 0.65  →  MEDIUM  →  DS_COLORS.confidence.medium
  < 0.65  →  LOW     →  DS_COLORS.confidence.low

Data Quality (0–1 scale)
  ≥ 0.80  →  HIGH
  ≥ 0.60  →  MEDIUM
  < 0.60  →  LOW

Result Types
  VERIFIED_PORTFOLIO  →  label 'Plattform'      color #1e3a5f
  EXTERNAL_MARKET     →  label 'Plattform'      color #1e3a5f
  MAISON_WORK         →  label 'Maison Work'    color #0369a1
  FUTURE_AVAILABILITY →  label 'Zukunftssignal' color #7c3aed

Freshness
  FRESH    →  DS_COLORS.freshness.fresh    (#1a7a4a)
  STALE    →  DS_COLORS.freshness.stale    (#d97706)
  OUTDATED →  DS_COLORS.freshness.outdated (#dc2626)
```

All tokens from `src/lib/ds.ts`. All thresholds from `src/lib/constants.ts`.
