Skip to content

Monorepo Structure

This document describes the monorepo structure, shared code contracts, and build processes for the Chaverim ALPR Platform.

Document Status: Living Document Last Updated: 2025-12-29


Table of Contents

  1. Repository Layout
  2. Component Boundaries
  3. Shared Code
  4. Build System
  5. Development Workflow
  6. CI/CD Pipeline

1. Repository Layout

alpr_chaverim/
├── central/                    # Central server (FastAPI + PostgreSQL + MinIO)
│   ├── src/
│   │   ├── api/               # API routes and endpoints
│   │   ├── models/            # SQLAlchemy models
│   │   ├── services/          # Business logic
│   │   ├── workers/           # Background workers (alerts, etc.)
│   │   └── main.py            # FastAPI application entry
│   ├── templates/             # Jinja2 templates
│   ├── static/                # CSS, JS, images
│   ├── tests/
│   ├── migrations/            # Alembic migrations
│   ├── requirements.txt
│   └── Dockerfile
├── shared/                     # Shared code and schemas
│   ├── ingest/                # Unified ingest service (edge + central modes)
│   │   ├── adapters/          # Camera vendor adapters (pull + push)
│   │   ├── normalizer.py      # Detection normalization
│   │   ├── receiver.py        # Webhook endpoints (push mode)
│   │   ├── buffer.py          # SQLite buffer (edge mode)
│   │   ├── uploader.py        # HTTPS client to central
│   │   ├── config.py          # Layered config loader
│   │   └── main.py            # Entry point (--mode edge|central)
│   ├── schemas/               # Pydantic models for API contracts
│   │   ├── detections.py      # Detection ingestion schemas
│   │   ├── heartbeat.py       # Heartbeat request/response
│   │   ├── commands.py        # Command queue schemas
│   │   └── __init__.py
│   ├── constants/             # Shared constants
│   │   ├── plate.py           # Plate normalization rules
│   │   └── __init__.py
│   └── setup.py
├── deploy/                     # Deployment artifacts
│   ├── edge/                  # Edge deployment (Raspberry Pi)
│   │   ├── install.sh         # Deployment script
│   │   ├── systemd/           # Systemd service files
│   │   └── config/            # Config templates
│   └── central-ingest/        # Central ingest deployment (Docker)
│       ├── Dockerfile
│       ├── docker-compose.yml
│       └── config/
├── docs/                       # Project documentation
│   ├── architecture/          # Architecture specifications
│   ├── prp/                   # Project Reference Patterns
│   ├── phases/                # Phase-specific tracking
│   ├── testing/               # Testing documentation
│   └── issues/                # Issue tracking
├── scripts/                    # Utility scripts
│   ├── worktree-docker.sh     # Worktree management
│   ├── db-schema.sh           # Database schema inspection
│   └── check-worktree.sh      # Branch safety check
├── docker-compose.yml          # Central server stack (app, db, db-test, minio)
├── .env.template               # Environment template
├── CLAUDE.md                   # Claude Code instructions
└── README.md                   # Project overview

2. Component Boundaries

2.1 Central Server (central/)

Purpose: Web application, API server, data storage, alert processing

Dependencies: - PostgreSQL 17 (metadata storage) - MinIO (image storage) - shared/ package (API contracts)

Entry Points: - src/main.py - FastAPI application - src/workers/alert_worker.py - Background alert processing - src/workers/telegram_worker.py - Telegram notifications

2.2 Ingest Service (shared/ingest/)

Purpose: Camera Detection ingestion with two deployment modes (edge and central)

Dependencies: - SQLite (local buffer, edge mode only) - Camera vendor APIs (Hikvision ISAPI, Unifi Protect)

Entry Points: - main.py - Service entry point (--mode edge or --mode central)

Deployment: - Edge mode: Deployed on Raspberry Pi via deploy/edge/ - Central mode: Deployed via Docker using deploy/central-ingest/

Architecture: See Ingest Service for detailed design.

2.3 Shared Package (shared/)

Purpose: Ingest service, API contracts, shared constants

Consumers: Central server, ingest service (edge and central deployments)

Contains: - ingest/ - Unified ingest service (camera adapters, buffer, uploader) - schemas/ - Pydantic schemas for API request/response validation - constants/ - Constants that must be identical across components

2.4 Deployment Artifacts (deploy/)

Purpose: Deployment scripts and configuration for ingest service

Contains: - edge/ - Raspberry Pi deployment (install script, systemd, config templates) - central-ingest/ - Docker deployment (Dockerfile, compose, config)


3. Shared Code

3.1 API Contract Schemas

All edge-to-central API contracts are defined in shared/schemas/:

# shared/schemas/events.py
from pydantic import BaseModel, Field
from datetime import datetime
from typing import List  # Python 3.9+ can use list[] directly

class PlateData(BaseModel):
    """Plate detection data from camera."""
    text: str = Field(..., description="Raw plate text from camera")
    normalized: str = Field(..., description="Normalized plate (uppercase, no separators)")
    confidence: float = Field(..., ge=0.0, le=1.0)

class CameraData(BaseModel):
    """Camera identification."""
    id: str
    mac: str
    name: str | None = None

class VehicleData(BaseModel):
    """Optional vehicle metadata."""
    direction: str | None = None  # "entering", "exiting"
    type: str | None = None       # "car", "truck", "motorcycle"
    color: str | None = None

class Detection(BaseModel):
    """Single detection from edge collector."""
    detection_id: str
    timestamp: datetime
    camera: CameraData
    plate: PlateData
    vehicle: VehicleData | None = None

class EventBatchRequest(BaseModel):
    """Batch of events uploaded by edge collector."""
    events: List[Detection] = Field(..., max_length=100)

class EventBatchResponse(BaseModel):
    """Response to batch upload."""
    accepted: int
    rejected: int
    errors: list[str] | None = None

3.2 Heartbeat Contract

# shared/schemas/heartbeat.py
from pydantic import BaseModel
from typing import List  # Python 3.9+ can use list[] directly
from datetime import datetime

class HeartbeatRequest(BaseModel):
    """Heartbeat from edge collector."""
    uptime_seconds: int
    queue_depth: int
    cameras_online: int
    config_version: int
    edge_version: str

class PendingCommand(BaseModel):
    """Command to be executed by edge collector."""
    id: str
    cmd: str  # "config_update", "reboot", "update_cameras"
    payload: dict | None = None
    created_at: datetime

class HeartbeatResponse(BaseModel):
    """Response to heartbeat."""
    status: str = "ok"
    server_time: datetime
    pending_commands: List[PendingCommand] = []

3.3 Plate Normalization Constants

# shared/constants/plate.py

# Characters that are commonly confused by OCR
OCR_CONFUSION_MAP = {
    '0': 'O',  # Zero -> Letter O
    '1': 'I',  # One -> Letter I
    '5': 'S',  # Five -> Letter S
    '8': 'B',  # Eight -> Letter B
}

# Characters to strip during normalization
STRIP_CHARACTERS = " -."

def normalize_plate(plate: str) -> str:
    """
    Normalize plate number for consistent storage and matching.

    - Uppercase
    - Remove spaces, dashes, dots
    - Does NOT apply OCR confusion mapping (that's for search)
    """
    result = plate.upper()
    for char in STRIP_CHARACTERS:
        result = result.replace(char, "")
    return result

3.4 Using Shared Package

Both components install the shared package in development mode:

Central (central/requirements.txt):

-e ../shared

Edge (edge/requirements.txt):

-e ../shared

Usage in code:

from shared.schemas.events import Detection, EventBatchRequest
from shared.constants.plate import normalize_plate


4. Build System

4.1 Central Server Build

Development:

cd central/
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt  # Includes -e ../shared

Docker Build:

# central/Dockerfile
FROM python:3.13-slim

WORKDIR /app

# Copy shared package first (for layer caching)
COPY shared/ /shared/
RUN pip install /shared/

# Copy and install central requirements
COPY central/requirements.txt .
RUN pip install -r requirements.txt

COPY central/ .

CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]

4.2 Edge Collector Build

Development (on Pi or local):

cd edge/
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt  # Includes -e ../shared

Production (Raspberry Pi):

# Build wheel package for deployment
cd shared/
python -m build

cd ../edge/
python -m build

# Deploy to Pi
scp dist/*.whl pi@edge-device:/tmp/
ssh pi@edge-device "pip install /tmp/*.whl"

4.3 Shared Package Build

# shared/setup.py
from setuptools import setup, find_packages

setup(
    name="chaverim-shared",
    version="0.1.0",
    packages=find_packages(),
    install_requires=[
        "pydantic>=2.0",
    ],
    python_requires=">=3.11",
)

5. Development Workflow

5.1 Working on Central Server

# Start development environment
./scripts/worktree-docker.sh up -d

# Run tests
./scripts/worktree-docker.sh exec app pytest

# Run migrations
./scripts/worktree-docker.sh exec app alembic upgrade head

5.2 Working on Edge Collector

# Local development (simulated)
cd edge/
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
pytest tests/

# On actual Raspberry Pi
ssh pi@edge-device
cd /opt/chaverim-edge
sudo systemctl restart chaverim-edge
journalctl -u chaverim-edge -f

5.3 Working on Shared Package

When modifying shared schemas:

  1. Update schema in shared/schemas/
  2. Verify backward compatibility (additive changes only)
  3. Update both central and edge to handle new fields
  4. Test integration between components
# After modifying shared/
cd central/
pip install -e ../shared  # Reinstall to pick up changes
pytest tests/

cd ../edge/
pip install -e ../shared
pytest tests/

6. CI/CD Pipeline

6.1 Continuous Integration

Triggered on: Pull request, push to main

Steps:

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test-central:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:17
        env:
          POSTGRES_PASSWORD: test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.13'
      - name: Install dependencies
        run: |
          pip install -e shared/
          pip install -r central/requirements.txt
      - name: Run tests
        run: pytest central/tests/

  test-edge:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.13'
      - name: Install dependencies
        run: |
          pip install -e shared/
          pip install -r edge/requirements.txt
      - name: Run tests
        run: pytest edge/tests/

  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
      - name: Lint
        run: |
          pip install ruff
          ruff check .

6.2 Deployment

Central Server: - Docker image built and pushed to registry - Docker Compose stack updated on server - Database migrations run automatically

Edge Collectors: - Wheel packages built for arm64 - Distributed via package server or manual SCP - Systemd service restarted


Appendix: Dependency Matrix

Package Central Edge Shared
FastAPI Yes - -
SQLAlchemy Yes - -
Flask - Yes -
httpx Yes Yes -
Pydantic Yes Yes Yes
SQLite - Yes -
PostgreSQL driver Yes - -

Document Maintainer: Architecture Team Review Cycle: Update when structure changes