Files
boc/docs/design/PR-001-DOMAIN-MODEL.md
T
Bernt b4391f9600 docs: PR-001 Domain Model v1.1 — Human Intelligence as future module
- Added Human Intelligence as separate external module (not in core domain)
- Positioning: External Intelligence Module, not part of Epic-001
- Architecture: Reality Intelligence with four branches:
  - Infrastructure Intelligence (core, Epic-001)
  - Operational Intelligence (future)
  - Human Intelligence (future module)
  - External Intelligence (future)

- Human Insight Artifact: Source, Geography, Period, Metrics, Confidence
- Usage: Linked to Area or Decision Case, never mixed with raw observations
- Example: Decision Case enriched with 'Many complaints last 30 days'
- When: After Epic-001 stable and 20+ Decision Cases exist

Rationale: Keep core domain clean. Human data is context, not observation.
Add after basic pipeline is proven.
2026-07-02 13:50:38 +00:00

604 lines
13 KiB
Markdown

# PR-001: Domain Model
**Package: `@landvex/domain`**
| | |
|---|---|
| **PR** | 001 |
| **Scope** | Domain language only — no database, no HTTP, no buckets, no AI |
| **Rule** | This package has zero dependencies on Express, PostgreSQL, S3, or AI frameworks |
---
## README
**This package describes the LandveX domain model. It contains no dependencies to database, HTTP, cloud storage, or AI frameworks. It defines only the language, objects, and rules that the rest of the platform builds upon.**
---
## Package Structure
```
packages/
├── domain/
│ ├── session/
│ ├── mission/
│ ├── artifact/
│ ├── observation/
│ ├── evidence/
│ ├── finding/
│ ├── decision/
│ ├── review/
│ ├── event/
│ └── common/
├── contracts/
│ ├── api/
│ ├── events/
│ └── dto/
├── shared/
└── infrastructure/
```
**Domain never depends on infrastructure.**
---
## Domain Objects
### Aggregate Roots
| Object | Type | Description |
|--------|------|-------------|
| `FieldSession` | Aggregate Root | Organizes field work |
| `Mission` | Aggregate Root | Single data collection task |
| `DecisionCase` | Aggregate Root | Complete decision chain |
### Entities
| Object | Description |
|--------|-------------|
| `MissionAsset` | Photo, video, or sensor data |
| `Artifact` | Any produced artifact (versioned, immutable) |
| `Observation` | Recorded fact from reality |
| `Evidence` | Linked observations with context |
| `Finding` | Pattern or conclusion |
| `Decision` | Recommended action |
| `Review` | Human quality assessment |
| `Action` | Executed task |
| `Outcome` | Measured result |
### Value Objects
| Object | Description | Example |
|--------|-------------|---------|
| `MissionId` | Unique mission identifier | `mission_20260814_000123` |
| `ArtifactId` | Unique artifact identifier | `artifact_000456` |
| `SessionId` | Unique session identifier | `session_20260814_000001` |
| `ObservationId` | Unique observation identifier | `obs_000981` |
| `DecisionId` | Unique decision identifier | `decision_000044` |
| `GeoLocation` | Latitude and longitude | `{ lat: 59.2371, lng: 18.1456 }` |
| `GpsAccuracy` | GPS accuracy in meters | `4.5` |
| `Confidence` | Model confidence 0-1 | `0.87` |
| `Severity` | Issue severity | `low`, `medium`, `high`, `critical` |
| `Priority` | Action priority | `low`, `medium`, `high`, `urgent` |
| `Hash` | SHA-256 checksum | `sha256:a3f7...` |
| `StorageUri` | Storage location | `s3://landvex-raw/...` |
| `Version` | Semantic version | `1`, `2`, `3` |
---
## Enums
### ArtifactType
```typescript
enum ArtifactType {
IMAGE = 'image',
VIDEO = 'video',
AUDIO = 'audio',
DATASET = 'dataset',
ANNOTATION = 'annotation',
DECISION_CASE = 'decision_case',
MODEL = 'model',
EVALUATION = 'evaluation',
REPLAY = 'replay'
}
```
### ReviewStatus
```typescript
enum ReviewStatus {
ASSIGNED = 'assigned',
IN_REVIEW = 'in_review',
APPROVED = 'approved',
REJECTED = 'rejected',
NEEDS_MORE_EVIDENCE = 'needs_more_evidence'
}
```
### DecisionVerb
```typescript
enum DecisionVerb {
INSPECT = 'inspect',
REPAIR = 'repair',
MONITOR = 'monitor',
WAIT = 'wait',
COLLECT = 'collect',
ESCALATE = 'escalate',
IGNORE = 'ignore',
PRIORITIZE = 'prioritize'
}
```
### MissionStatus
```typescript
enum MissionStatus {
CREATED = 'created',
UPLOADING = 'uploading',
PROCESSING = 'processing',
COMPLETED = 'completed',
FAILED = 'failed'
}
```
### SessionStatus
```typescript
enum SessionStatus {
PLANNED = 'planned',
ACTIVE = 'active',
COMPLETED = 'completed'
}
```
---
## Event Contracts
```typescript
// Session events
interface FieldSessionCreated {
type: 'FieldSessionCreated';
sessionId: SessionId;
location: GeoLocation;
date: Date;
timestamp: Date;
}
// Mission events
interface MissionCreated {
type: 'MissionCreated';
missionId: MissionId;
sessionId: SessionId;
location: GeoLocation;
timestamp: Date;
}
interface MissionImported {
type: 'MissionImported';
missionId: MissionId;
assetCount: number;
timestamp: Date;
}
// Artifact events
interface ArtifactUploaded {
type: 'ArtifactUploaded';
artifactId: ArtifactId;
missionId: MissionId;
type: ArtifactType;
storageUri: StorageUri;
hash: Hash;
timestamp: Date;
}
interface ArtifactValidated {
type: 'ArtifactValidated';
artifactId: ArtifactId;
validationResult: ValidationResult;
timestamp: Date;
}
// Observation events
interface ObservationCreated {
type: 'ObservationCreated';
observationId: ObservationId;
missionId: MissionId;
artifactId: ArtifactId;
description: string;
timestamp: Date;
}
// Evidence events
interface EvidenceCreated {
type: 'EvidenceCreated';
evidenceId: string;
observationIds: ObservationId[];
description: string;
timestamp: Date;
}
// Finding events
interface FindingCreated {
type: 'FindingCreated';
findingId: string;
evidenceId: string;
description: string;
severity: Severity;
timestamp: Date;
}
// Decision events
interface DecisionCreated {
type: 'DecisionCreated';
decisionId: DecisionId;
findingId: string;
verb: DecisionVerb;
description: string;
confidence: Confidence;
timestamp: Date;
}
interface DecisionReviewed {
type: 'DecisionReviewed';
decisionId: DecisionId;
reviewId: string;
status: ReviewStatus;
reviewer: string;
timestamp: Date;
}
interface DecisionApproved {
type: 'DecisionApproved';
decisionId: DecisionId;
reviewId: string;
approvedBy: string;
timestamp: Date;
}
```
---
## Domain Invariants
### FieldSession
- Must have exactly one location
- Must have a date
- Can have zero or more missions
- Cannot be completed if any mission is still uploading
### Mission
- Must belong to exactly one Session
- Must have at least one Artifact
- Cannot be completed before import is finished
- Status is a projection of event history, not mutable state
### DecisionCase
- Must have at least one Observation
- Must have at least one Evidence
- Must have exactly one Decision
- Cannot be Approved without Review
- Immutable once created — revisions create new versions
### Artifact
- Must have a unique hash
- Must have a storage URI
- Version is incremented on each change
- Parent reference creates lineage chain
---
## TypeScript Interfaces
```typescript
// Aggregate Roots
interface FieldSession {
id: SessionId;
location: GeoLocation;
date: Date;
status: SessionStatus;
missionIds: MissionId[];
createdAt: Date;
updatedAt: Date;
}
interface Mission {
id: MissionId;
sessionId: SessionId;
location: GeoLocation;
status: MissionStatus;
device: DeviceInfo;
artifactIds: ArtifactId[];
observationIds: ObservationId[];
createdAt: Date;
updatedAt: Date;
}
interface DecisionCase {
id: string;
missionId: MissionId;
observationIds: ObservationId[];
evidenceIds: string[];
findingId: string;
decisionId: DecisionId;
reviewId: string;
status: 'pending' | 'under_review' | 'approved' | 'rejected';
version: number;
createdAt: Date;
}
// Entities
interface MissionAsset {
id: ArtifactId;
missionId: MissionId;
type: ArtifactType;
storageUri: StorageUri;
hash: Hash;
sizeBytes: number;
mimeType: string;
metadata: AssetMetadata;
createdAt: Date;
}
interface Artifact {
id: ArtifactId;
type: ArtifactType;
version: Version;
hash: Hash;
createdBy: string;
storageUri: StorageUri;
parentId?: ArtifactId;
lineage: ArtifactId[];
createdAt: Date;
}
interface Observation {
id: ObservationId;
missionId: MissionId;
artifactId: ArtifactId;
type: string;
description: string;
location: GeoLocation;
confidence: Confidence;
boundingBox?: BoundingBox;
createdAt: Date;
}
interface Evidence {
id: string;
observationIds: ObservationId[];
description: string;
context: EvidenceContext;
createdAt: Date;
}
interface Finding {
id: string;
evidenceId: string;
description: string;
severity: Severity;
pattern: string;
confidence: Confidence;
createdAt: Date;
}
interface Decision {
id: DecisionId;
findingId: string;
verb: DecisionVerb;
description: string;
rationale: string;
confidence: Confidence;
consequence: string;
action: string;
businessImpact: BusinessImpact;
createdAt: Date;
}
interface Review {
id: string;
decisionId: DecisionId;
status: ReviewStatus;
reviewer: string;
comments: string;
createdAt: Date;
completedAt?: Date;
}
interface Action {
id: string;
decisionId: DecisionId;
description: string;
assignedTo: string;
scheduledDate: Date;
status: 'pending' | 'in_progress' | 'completed';
createdAt: Date;
}
interface Outcome {
id: string;
actionId: string;
description: string;
measuredImpact: BusinessImpact;
verifiedBy: string;
createdAt: Date;
}
// Value Objects
interface GeoLocation {
lat: number;
lng: number;
accuracy?: GpsAccuracy;
}
interface BoundingBox {
x: number;
y: number;
width: number;
height: number;
}
interface AssetMetadata {
exif: ExifData;
gps: GeoLocation;
device: DeviceInfo;
}
interface ExifData {
device?: string;
iso?: number;
exposure?: string;
focalLength?: string;
timestamp?: Date;
}
interface DeviceInfo {
model: string;
os: string;
appVersion: string;
}
interface EvidenceContext {
historicalData?: boolean;
weatherData?: boolean;
trafficData?: boolean;
gisData?: boolean;
}
interface BusinessImpact {
risk: string;
cost: string;
time: string;
opportunity: string;
}
interface ValidationResult {
valid: boolean;
issues: QualityIssue[];
}
interface QualityIssue {
type: 'blur' | 'duplicate' | 'bad_gps' | 'low_resolution' | 'missing_metadata' | 'wrong_timestamp' | 'occlusion';
severity: Severity;
description: string;
}
// ID Types (branded types for type safety)
type MissionId = string & { readonly __brand: 'MissionId' };
type ArtifactId = string & { readonly __brand: 'ArtifactId' };
type SessionId = string & { readonly __brand: 'SessionId' };
type ObservationId = string & { readonly __brand: 'ObservationId' };
type DecisionId = string & { readonly __brand: 'DecisionId' };
```
---
## Strict Rule
**No new feature may introduce new domain concepts without an approved change to the domain model.**
The concepts `Session`, `Mission`, `Artifact`, `Observation`, `Evidence`, `Finding`, `Decision`, `Review`, and `Outcome` become the shared language for the entire organization — code, documentation, APIs, tests, and product discussions use the same terms.
---
## Not in PR-001
| Item | Why Excluded |
|------|-------------|
| Repositories | Domain should not know about persistence |
| Services | Will come in next PR |
| Database code | Infrastructure concern |
| HTTP endpoints | API concern |
| Bucket code | Infrastructure concern |
| AI logic | Separate concern |
| Human Intelligence | Separate module, not core domain |
---
## Future Module: Human Intelligence (Not in Epic-001)
**Positioning:** External Intelligence Module, not part of core domain.
**Why separate:** Customer satisfaction data is not an observation from reality like a road crack. It is **context** that can influence priorities and decisions.
**Architecture:**
```
Reality Intelligence
├── Infrastructure Intelligence ← Core (Epic-001)
│ Roads, Buildings, Parks, Signs
├── Operational Intelligence ← Future
│ SLA, Tickets, Costs, Times
├── Human Intelligence ← Future module
│ Customer satisfaction
│ Citizen surveys
│ NPS, Complaints, Feedback
└── External Intelligence ← Future
│ Weather, Traffic, Demographics
```
**Human Insight Artifact:**
```
Human Insight
├── Source: "Kommunenkät 2026"
├── Geography: [Stockholm, Nacka]
├── Period: "Q2 2026"
├── Metrics:
│ ├── CSAT: 4.2
│ ├── NPS: 62
│ └── Complaint Index: 0.34
└── Confidence: 0.83
```
**Usage:** Linked to Area or Decision Case, but never mixed with raw field observations.
**Decision Case with Human Intelligence:**
```
Observation: "Crack on main street"
Evidence: [Image, Video, History, GIS]
Human Intelligence: "Many complaints last 30 days, low satisfaction index"
Decision: "Prioritize repair within 14 days"
Business Impact: "Reduced risk, lower future cost, improved citizen satisfaction"
```
**When:** After Epic-001 is stable and 20+ Decision Cases exist.
---
## Next PR
**PR-002: Persistence Adapter**
```
Domain
Repository Interface
PostgreSQL Adapter
S3 Adapter
Event Store Adapter
```
Freedom to change technology without touching core model.
---
## Status
**READY FOR IMPLEMENTATION**