docs: engineering governance — CLAUDE.md, ARCHITECTURE.md, CODE_REVIEW_CHECKLIST.md
Complete rewrite of CLAUDE.md with full binding rules (vision, architecture layers, component limits, state management, design system, AI integration, performance, security). New ARCHITECTURE.md covers folder structure, all data flows with ASCII diagrams, scoring pipeline, AI flow, unified result feed, and workspace routing. New CODE_REVIEW_CHECKLIST.md provides a 10-section review guide and pre-commit quick checklist. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+476
@@ -0,0 +1,476 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user