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¶
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):
Edge (edge/requirements.txt):
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:
- Update schema in
shared/schemas/ - Verify backward compatibility (additive changes only)
- Update both central and edge to handle new fields
- 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