Backend Architecture Diagrams¶
Visual representations of the LCO backend architecture to help understand the system structure and data flow.
System Architecture¶
High-Level Architecture¶
┌──────────────────────────────────────────────────────────────┐
│ Frontend Layer │
│ (React + Vite + TypeScript) │
│ │
│ Components → State Management → API Client │
└───────────────────────────┬──────────────────────────────────┘
│ HTTP/JSON (REST)
│ CORS Enabled
↓
┌──────────────────────────────────────────────────────────────┐
│ FastAPI Application │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ API Layer (Routers) │ │
│ │ • Request validation │ │
│ │ • Response serialization │ │
│ │ • HTTP status codes │ │
│ │ • Error handling │ │
│ └────────────────┬───────────────────────────────────────┘ │
│ │ Dependency Injection │
│ ↓ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Schema Layer (Pydantic Models) │ │
│ │ • Data validation │ │
│ │ • Type checking │ │
│ │ • Serialization/Deserialization │ │
│ │ • Field aliases (camelCase ↔ snake_case) │ │
│ └────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Repository Layer (Data Access) │ │
│ │ • CRUD operations │ │
│ │ • Query building │ │
│ │ • Partition key management │ │
│ │ • Business logic │ │
│ └────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Database Layer (Cosmos Client) │ │
│ │ • Connection management │ │
│ │ • Container initialization │ │
│ │ • Health checks │ │
│ └────────────────┬───────────────────────────────────────┘ │
└───────────────────┼──────────────────────────────────────────┘
│ Azure SDK
↓
┌────────────────────────────────────────────────────────────────────┐
│ Azure Cosmos DB (NoSQL - SQL API) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Clients │ │ Projects │ │ Crews │ │ Services │ │
│ │Container │ │Container │ │Container │ │ Container │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │Equipment │ │CrewTrades│ │CrewMembrs│ │ServiceCrews │ │
│ │Container │ │Container │ │Container │ │ Container │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────────┘ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Estimations │ │MaterialTakeoff│ │
│ │ Container │ │ Container │ │
│ └──────────────┘ └──────────────┘ │
└────────────────────────────────────────────────────────────────────┘
Request/Response Flow¶
Complete Request Lifecycle¶
1. Client Request
↓
┌─────────────────────────────────────────────┐
│ POST /api/v1/clients │
│ { │
│ "clientCode": "ACME", │
│ "clientName": "ACME Corp", │
│ "clientType": "private" │
│ } │
└──────────────────┬──────────────────────────┘
│
2. FastAPI Router │
↓ │
┌─────────────────────────────────────────────┐
│ @router.post("", response_model=Client) │
│ async def create_client( │
│ client_data: ClientCreate, ← Validation │
│ repo = Depends(get_repo) ← Injection │
│ ) │
└──────────────────┬──────────────────────────┘
│
3. Validation │
↓ │
┌─────────────────────────────────────────────┐
│ ClientCreate Pydantic Model │
│ • Validates types │
│ • Checks required fields │
│ • Applies constraints │
│ ✓ Valid → Continue │
│ ✗ Invalid → 422 Validation Error │
└──────────────────┬──────────────────────────┘
│
4. Repository │
↓ │
┌─────────────────────────────────────────────┐
│ ClientRepository.create_client() │
│ • Transform to dict │
│ • Add metadata (id, timestamps) │
│ • Set partition key │
│ • Call base repository │
└──────────────────┬──────────────────────────┘
│
5. Database │
↓ │
┌─────────────────────────────────────────────┐
│ Cosmos DB Container │
│ await container.create_item(body=item) │
│ • Stores document │
│ • Returns created item │
└──────────────────┬──────────────────────────┘
│
6. Response │
↓ │
┌─────────────────────────────────────────────┐
│ Client Pydantic Model │
│ • Converts dict to model │
│ • Validates response │
│ • Serializes to JSON │
└──────────────────┬──────────────────────────┘
│
7. HTTP Response │
↓ │
┌─────────────────────────────────────────────┐
│ 201 Created │
│ { │
│ "id": "generated-uuid", │
│ "clientCode": "ACME", │
│ "clientName": "ACME Corp", │
│ "clientType": "private", │
│ "createdAt": "2025-10-09T...", │
│ "updatedAt": "2025-10-09T..." │
│ } │
└─────────────────────────────────────────────┘
Dependency Injection Flow¶
How Dependencies are Resolved¶
Endpoint Definition
┌──────────────────────────────────────────────┐
│ @router.get("/{client_id}") │
│ async def get_client( │
│ client_id: str, ← Path param │
│ repo: ClientRepository = Depends(get_repo) │
│ ): │
│ return await repo.get_client(client_id) │
└──────────────────┬───────────────────────────┘
│
Request comes in with client_id="123"
│
↓
FastAPI sees Depends(get_repo)
│
↓
┌──────────────────────────────────────────────┐
│ async def get_repo() -> ClientRepository: │
│ container = cosmos_db.get_container(...) │
│ return ClientRepository(container) │
└──────────────────┬───────────────────────────┘
│
↓
┌──────────────────────────────────────────────┐
│ cosmos_db.get_container("clients") │
│ • Returns container client │
└──────────────────┬───────────────────────────┘
│
↓
┌──────────────────────────────────────────────┐
│ ClientRepository(container) │
│ • Creates repository instance │
└──────────────────┬───────────────────────────┘
│
↓
Back to endpoint with repo injected
│
↓
┌──────────────────────────────────────────────┐
│ async def get_client( │
│ client_id="123", │
│ repo=<ClientRepository instance> │
│ ): │
│ return await repo.get_client("123") │
└──────────────────────────────────────────────┘
Repository Pattern Structure¶
Class Hierarchy and Relationships¶
┌─────────────────────────────────────┐
│ BaseRepository<T> │
│ (Generic Abstract) │
├─────────────────────────────────────┤
│ - container: ContainerProxy │
├─────────────────────────────────────┤
│ + create(item) │
│ + get_by_id(id, pk) │
│ + update(item) │
│ + delete(id, pk) │
│ + query(sql, params) │
│ + query_with_pagination() │
│ + upsert(item) │
│ + batch_create(items) │
│ # get_partition_key(item) ← Abstract│
└──────────────┬──────────────────────┘
│ Extends
↓
┌─────────────────────────────────────┐
│ ClientRepository │
│ (Concrete Implementation) │
├─────────────────────────────────────┤
│ + get_partition_key(item) │
│ + create_client(data) │
│ + get_client(id) │
│ + get_client_by_code(code) │
│ + update_client(id, data) │
│ + delete_client(id) │
│ + get_all_clients(filters) │
│ + get_client_projects(id) │
│ + update_project_counts(id) │
│ + get_client_service_history(id) │
└─────────────────────────────────────┘
Similar pattern for:
- ProjectRepository
- CrewRepository
- ServiceRepository
- ServiceCrewRepository
- EquipmentRepository
Pydantic Model Hierarchy¶
Schema Inheritance Pattern¶
For each entity (e.g., Client):
┌─────────────────────────────────────┐
│ BaseSchema │
│ (Common Pydantic config) │
├─────────────────────────────────────┤
│ model_config = ConfigDict(...) │
│ • populate_by_name=True │
│ • use_enum_values=True │
│ • validate_assignment=True │
└──────────────┬──────────────────────┘
│ Inherits
↓
┌─────────────────────────────────────┐
│ ClientBase │
│ (Shared fields) │
├─────────────────────────────────────┤
│ - client_code: str │
│ - client_name: str │
│ - client_type: ClientType │
│ - industry: Optional[Industry] │
│ - contact_info: Optional[Contact] │
└──────┬──────────┬───────────────────┘
│ │
│ └─────────────┐
↓ ↓
┌──────────────────┐ ┌──────────────────┐
│ ClientCreate │ │ ClientUpdate │
│ (For POST) │ │ (For PUT) │
├──────────────────┤ ├──────────────────┤
│ All fields │ │ All fields │
│ from ClientBase │ │ Optional │
│ (Required) │ │ │
└──────────────────┘ └──────────────────┘
┌──────────────────┐
│ BaseDocument │
│ (DB fields) │
├──────────────────┤
│ - id: str │
│ - created_at │
│ - updated_at │
│ - created_by │
│ - updated_by │
└────────┬─────────┘
│
┌────────────────┴─────────────────┐
↓ ↓
┌──────────────────┐ ┌──────────────────┐
│ Client │ │ ClientDetailed │
│ (Response) │ │ (Extended) │
├──────────────────┤ ├──────────────────┤
│ Inherits from: │ │ Inherits from: │
│ • ClientBase │ │ • Client │
│ • BaseDocument │ ├──────────────────┤
├──────────────────┤ │ Additional: │
│ Additional: │ │ - recentProjects │
│ - client_id │ │ - serviceHistory │
│ - type │ │ │
│ - project_count │ │ │
└──────────────────┘ └──────────────────┘
Database Schema (Cosmos DB)¶
Container Structure and Partition Keys¶
Database: lco-construction
│
├─ Container: Clients
│ ├─ Partition Key: /clientId
│ ├─ Document Structure:
│ │ {
│ │ "id": "uuid",
│ │ "clientId": "same-as-id",
│ │ "type": "client",
│ │ "clientCode": "ACME",
│ │ "clientName": "ACME Corp",
│ │ "clientType": "private",
│ │ "industry": "commercial",
│ │ "projectCount": 5,
│ │ "activeProjectCount": 2,
│ │ "createdAt": "2025-10-15T...",
│ │ "updatedAt": "2025-10-15T..."
│ │ }
│
├─ Container: Projects
│ ├─ Partition Key: /clientId (co-located with client for efficient queries)
│ ├─ Document Structure:
│ │ {
│ │ "id": "uuid",
│ │ "clientId": "client-uuid",
│ │ "projectId": "same-as-id",
│ │ "type": "project",
│ │ "projectCode": "PRJ-001",
│ │ "projectName": "Building Construction",
│ │ "projectType": "commercial",
│ │ "contractType": "lump-sum",
│ │ "status": "active",
│ │ "location": {"country": "Canada", "province": "Ontario", ...},
│ │ "year": 2025,
│ │ "quarter": "Q1",
│ │ "workingDays": 240,
│ │ "workingHours": 1920,
│ │ "createdAt": "2025-10-15T...",
│ │ "updatedAt": "2025-10-15T..."
│ │ }
│
├─ Container: Services
│ ├─ Partition Key: /projectId (co-located with project)
│ ├─ Document Structure:
│ │ {
│ │ "id": "uuid",
│ │ "projectId": "project-uuid",
│ │ "serviceId": "same-as-id",
│ │ "type": "service",
│ │ "serviceType": "estimation",
│ │ "status": "active",
│ │ "startDate": "2025-01-01",
│ │ "endDate": "2025-12-31",
│ │ "createdAt": "2025-10-15T..."
│ │ }
│
├─ Container: Crews
│ ├─ Partition Key: /crewId (self-referencing)
│ ├─ Document Structure:
│ │ {
│ │ "id": "uuid",
│ │ "crewId": "same-as-id",
│ │ "type": "crew",
│ │ "crewCode": "CONC-001",
│ │ "crewName": "Concrete Pour Crew",
│ │ "discipline": "concrete",
│ │ "manpower": [
│ │ {"tradeCode": "CONC", "laborDesignation": "Foreman", "quantity": 1},
│ │ {"tradeCode": "CONC", "laborDesignation": "Finisher", "quantity": 3}
│ │ ],
│ │ "equipment": [
│ │ {"equipmentCode": "MIX-001", "quantity": 1}
│ │ ],
│ │ "productivityFactor": 1.0
│ │ }
│
├─ Container: CrewTrades
│ ├─ Partition Key: /tradeCode
│ ├─ Document Structure:
│ │ {
│ │ "id": "uuid",
│ │ "tradeCode": "CARP",
│ │ "tradeName": "Carpenter",
│ │ "category": "skilled",
│ │ "description": "Carpentry work including framing and finishing"
│ │ }
│
├─ Container: CrewMembers
│ ├─ Partition Key: /locationKey
│ ├─ Format: country|province|tradeCode|laborDesignation
│ ├─ Document Structure:
│ │ {
│ │ "id": "uuid",
│ │ "locationKey": "Canada|Ontario|CARP|Journeyman",
│ │ "tradeCode": "CARP",
│ │ "laborDesignation": "Journeyman Carpenter",
│ │ "country": "Canada",
│ │ "province": "Ontario",
│ │ "region": "Toronto",
│ │ "year": 2025,
│ │ "quarter": "Q1",
│ │ "projectType": "commercial",
│ │ "baseRate": 45.00,
│ │ "overtimeRate": 67.50,
│ │ "doubleTimeRate": 90.00,
│ │ "tripleTimeRate": 135.00
│ │ }
│
├─ Container: ServiceCrews
│ ├─ Partition Key: /serviceCrewId (self-referencing)
│ ├─ Document Structure:
│ │ {
│ │ "id": "uuid",
│ │ "serviceCrewId": "same-as-id",
│ │ "serviceId": "service-uuid",
│ │ "crewId": "crew-uuid",
│ │ "crewCode": "CONC-001",
│ │ "crewName": "Concrete Pour Crew",
│ │ "quantity": 1,
│ │ "rate": 450.00,
│ │ "total": 450.00,
│ │ "indirectCosts": {
│ │ "labour": {
│ │ "employerHealthTax": 5.0,
│ │ "employmentInsurance": 3.5,
│ │ "canadaPensionPlan": 6.0,
│ │ ...
│ │ },
│ │ "equipment": {
│ │ "smallTools": 2.0,
│ │ "consumables": 1.5,
│ │ ...
│ │ }
│ │ }
│ │ }
│
├─ Container: Equipment
│ ├─ Partition Key: /equipmentCode
│ ├─ Document Structure:
│ │ {
│ │ "id": "uuid",
│ │ "equipmentCode": "EXC-001",
│ │ "equipmentName": "Hydraulic Excavator",
│ │ "equipmentType": "excavator",
│ │ "category": "heavy",
│ │ "unit": "day",
│ │ "baseUnitPrice": 850.00,
│ │ "description": "20-ton excavator with bucket"
│ │ }
│
├─ Container: Estimations
│ ├─ Partition Key: /serviceId (co-located with service)
│ ├─ Document Structure:
│ │ {
│ │ "id": "uuid",
│ │ "serviceId": "service-uuid",
│ │ "estimationId": "same-as-id",
│ │ "estimationType": "preliminary",
│ │ "totalEstimate": 1500000.00,
│ │ "breakdown": {...}
│ │ }
│
└─ Container: MaterialTakeoff (MTO)
├─ Partition Key: /projectId (co-located with project)
├─ Document Structure:
{
"id": "uuid",
"projectId": "project-uuid",
"serviceId": "service-uuid",
"discipline": "electrical",
"items": [
{
"description": "Conduit EMT 3/4\"",
"quantity": 500,
"unit": "LF",
"unitCost": 2.50,
"totalCost": 1250.00
}
],
"totalMaterialCost": 125000.00
}
Why These Partition Keys?¶
Co-location Strategy:
- Projects use /clientId → All projects for a client are in the same partition
- Services use /projectId → All services for a project are in the same partition
- Enables efficient queries within a client's data
- Reduces RU costs for related data access
Self-referencing:
- Clients use /clientId (same as id)
- Crews use /crewId (same as id)
- Simpler partition key management
- Efficient single-item lookups
Categorical:
- CrewTrades use /tradeCode → Group by trade type
- CrewMembers use /locationKey → Group by location/time
- Equipment use /equipmentCode → Group by equipment type
- Enables efficient category-based queries
API Router Structure¶
Router Organization¶
app/api/v1/routers/
│
├─ clients.py
│ ├─ GET /api/v1/clients
│ ├─ POST /api/v1/clients
│ ├─ GET /api/v1/clients/{client_id}
│ ├─ PUT /api/v1/clients/{client_id}
│ └─ DELETE /api/v1/clients/{client_id}
│
├─ projects.py
│ ├─ GET /api/v1/projects
│ ├─ POST /api/v1/projects
│ ├─ GET /api/v1/projects/{project_id}
│ ├─ PUT /api/v1/projects/{project_id}
│ └─ DELETE /api/v1/projects/{project_id}
│
├─ services.py
│ ├─ GET /api/v1/projects/{project_id}/services
│ ├─ POST /api/v1/projects/{project_id}/services
│ ├─ GET /api/v1/projects/{project_id}/services/{service_id}
│ ├─ PUT /api/v1/projects/{project_id}/services/{service_id}
│ └─ DELETE /api/v1/projects/{project_id}/services/{service_id}
│
├─ crews.py
│ ├─ Crews:
│ │ ├─ GET /api/v1/crews
│ │ ├─ POST /api/v1/crews
│ │ ├─ GET /api/v1/crews/{crew_id}
│ │ ├─ PUT /api/v1/crews/{crew_id}
│ │ ├─ DELETE /api/v1/crews/{crew_id}
│ │ └─ POST /api/v1/crews/{crew_id}/duplicate
│ │
│ ├─ Crew Trades:
│ │ ├─ GET /api/v1/crews/trades
│ │ ├─ POST /api/v1/crews/trades
│ │ └─ GET /api/v1/crews/trades/{trade_code}
│ │
│ └─ Crew Members:
│ ├─ GET /api/v1/crews/members
│ ├─ POST /api/v1/crews/members
│ └─ GET /api/v1/crews/members/{member_id}
│
├─ service_crews.py
│ ├─ GET /api/v1/service-crews
│ ├─ POST /api/v1/service-crews
│ ├─ GET /api/v1/service-crews/{service_crew_id}
│ ├─ PUT /api/v1/service-crews/{service_crew_id}
│ └─ DELETE /api/v1/service-crews/{service_crew_id}
│
└─ equipment.py
├─ GET /api/v1/equipment
├─ POST /api/v1/equipment
├─ GET /api/v1/equipment/{equipment_id}
├─ PUT /api/v1/equipment/{equipment_id}
└─ DELETE /api/v1/equipment/{equipment_id}
Error Handling Flow¶
How Errors Propagate¶
Error occurs in Repository
│
↓
┌─────────────────────────────┐
│ try: │
│ result = await container │
│ .create_item(...) │
│ except CosmosHttpResponse │
│ Error as e: │
│ logger.error(...) │
│ raise ← Re-raise │
└────────┬────────────────────┘
│
↓
Error bubbles to Router
│
↓
┌─────────────────────────────┐
│ try: │
│ client = await repo │
│ .create_client(...) │
│ except ValueError as e: │
│ raise HTTPException( │
│ status_code=400, │
│ detail=str(e) │
│ ) │
│ except Exception as e: │
│ logger.error(...) │
│ raise HTTPException( │
│ status_code=500, │
│ detail="Internal error" │
│ ) │
└────────┬────────────────────┘
│
↓
FastAPI catches HTTPException
│
↓
┌─────────────────────────────┐
│ Returns HTTP Response: │
│ { │
│ "detail": "Error message" │
│ } │
│ Status: 400/404/500/etc. │
└─────────────────────────────┘
Lifecycle Management¶
Application Startup and Shutdown¶
Application Start
│
↓
┌──────────────────────────────┐
│ main.py loads │
└──────────┬───────────────────┘
│
↓
┌──────────────────────────────┐
│ @asynccontextmanager │
│ async def lifespan(app): │
└──────────┬───────────────────┘
│
↓ STARTUP
┌──────────────────────────────┐
│ await cosmos_db.initialize() │
│ • Connect to Cosmos DB │
│ • Get/create database │
│ • Get/create containers │
│ • Set _initialized = True │
└──────────┬───────────────────┘
│
↓
┌──────────────────────────────┐
│ Application Running │
│ • Accepting requests │
│ • Processing endpoints │
│ • Using database │
└──────────┬───────────────────┘
│
↓ SHUTDOWN SIGNAL
┌──────────────────────────────┐
│ await cosmos_db.close() │
│ • Close Cosmos client │
│ • Set _initialized = False │
└──────────┬───────────────────┘
│
↓
Application Stopped
Data Transformation Flow¶
Pydantic Model Transformations¶
Frontend sends JSON
┌─────────────────────────────┐
│ { │
│ "clientCode": "ACME", │
│ "clientName": "ACME Corp" │
│ } │
└────────┬────────────────────┘
│
↓ FastAPI deserializes
┌─────────────────────────────┐
│ ClientCreate( │
│ client_code="ACME", │ ← Field aliases
│ client_name="ACME Corp" │ convert camelCase
│ ) │ to snake_case
└────────┬────────────────────┘
│
↓ Repository transforms
┌─────────────────────────────┐
│ client_dict = data │
│ .model_dump( │
│ by_alias=True, │ ← Use aliases
│ mode='json' │ for output
│ ) │
│ # Result: │
│ # { │
│ # "clientCode": "ACME", │
│ # "clientName": "ACME..."│
│ # } │
└────────┬────────────────────┘
│
↓ Add metadata
┌─────────────────────────────┐
│ client_dict['id'] = uuid() │
│ client_dict['clientId'] = │
│ client_dict['id'] │
│ client_dict['type'] = │
│ 'client' │
│ client_dict['createdAt'] = │
│ datetime.utcnow()... │
└────────┬────────────────────┘
│
↓ Save to Cosmos DB
┌─────────────────────────────┐
│ await container │
│ .create_item(client_dict) │
└────────┬────────────────────┘
│
↓ Cosmos returns dict
┌─────────────────────────────┐
│ { │
│ "id": "uuid-123", │
│ "clientCode": "ACME", │
│ "clientName": "ACME Corp",│
│ "clientId": "uuid-123", │
│ "type": "client", │
│ "createdAt": "2025-...", │
│ "_rid": "...", │ ← Cosmos metadata
│ "_self": "..." │
│ } │
└────────┬────────────────────┘
│
↓ Convert to Pydantic
┌─────────────────────────────┐
│ Client(**result) │
│ • Validates │
│ • Filters Cosmos metadata │
│ • Creates typed object │
└────────┬────────────────────┘
│
↓ FastAPI serializes
┌─────────────────────────────┐
│ { │
│ "id": "uuid-123", │
│ "clientCode": "ACME", │ ← camelCase
│ "clientName": "ACME Corp",│ for frontend
│ "clientId": "uuid-123", │
│ "createdAt": "2025-..." │
│ } │
└─────────────────────────────┘
│
↓
Sent to Frontend
These diagrams provide a visual reference for understanding the backend architecture, data flow, and key patterns used throughout the application.