Files
gps-denied-onboard/.planning/codebase/CONVENTIONS.md
T
Yuzviak 2dd60a0e37 Add codebase map to .planning/codebase/
7 structured documents covering stack, integrations, architecture,
structure, conventions, testing, and concerns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 20:26:52 +03:00

185 lines
7.6 KiB
Markdown

# Coding Conventions
**Analysis Date:** 2026-04-01
## Naming Patterns
**Files:**
- `snake_case.py` throughout — `coordinates.py`, `chunk_manager.py`, `processor.py`
- Test files: `test_<module>.py` prefix — `test_coordinates.py`, `test_api_flights.py`
- Schema files grouped by domain — `flight.py`, `vo.py`, `graph.py`, `metric.py`
**Classes:**
- `PascalCase` for all classes — `FlightProcessor`, `CoordinateTransformer`, `FactorGraphOptimizer`
- Interface/ABC classes prefixed with `I``IImageMatcher`, `ISequentialVisualOdometry`, `IModelManager`
- Error/Exception classes suffixed with `Error``OriginNotSetError`, `QueueFullError`, `ValidationError`
- Pydantic schema classes named after the concept they represent, suffixed with `Request`/`Response` for API boundaries — `FlightCreateRequest`, `FlightDetailResponse`, `BatchUpdateResponse`
- Config classes suffixed with `Config` or `Settings``DatabaseConfig`, `RecoveryConfig`, `AppSettings`
**Functions/Methods:**
- `snake_case``set_enu_origin`, `compute_relative_pose`, `retrieve_candidate_tiles`
- Async functions not distinguished by name from sync (no `async_` prefix); the `async def` keyword is the signal
- Private/internal methods prefixed with single underscore — `_init_flight`, `_cleanup_flight`, `_publish_frame_result`
**Variables:**
- `snake_case``flight_id`, `rel_pose`, `chunk_mgr`
- Per-flight in-memory dicts named `_<plural_noun>` keyed by `flight_id``_origins`, `_flight_states`, `_prev_images`, `_flight_cameras`
- Constants: uppercase not consistently enforced; numeric magic values appear in comments (e.g., `111319.5`)
**Type Parameters:**
- `PascalCase` — standard Python typing conventions
## Code Style
**Formatter/Linter:**
- `ruff` — configured in `pyproject.toml`
- Line length: 100 characters (`line-length = 100`)
- Target: Python 3.11 (`target-version = "py311"`)
- Ruff rule sets active: `E` (pycodestyle errors), `F` (Pyflakes), `I` (isort), `W` (pycodestyle warnings)
- No `B` (flake8-bugbear) or `UP` (pyupgrade) rules enabled
**Type Hints:**
- Type hints on all public method signatures — parameters and return types
- `from __future__ import annotations` used in modules with complex forward references (`processor.py`, `flight.py`, `config.py`)
- `Optional[T]` used alongside `T | None` syntax (inconsistency — both styles present)
- `dict[str, X]` and `list[X]` lowercase generics (Python 3.9+ style)
- Untyped component slots use `= None` without annotation — e.g., `self._vo = None` — intentional for lazy init
## Module Docstrings
Every source module starts with a one-line docstring naming the component:
```python
"""Coordinate Transformer (Component F13)."""
"""Core Flight Processor — Full Processing Pipeline (Stage 10)."""
"""Sequential Visual Odometry (Component F07)."""
```
Test files also carry docstrings stating what they test and which component ID they cover.
## Import Organization
**Order (enforced by ruff `I` rules):**
1. Standard library (`__future__`, `asyncio`, `logging`, `math`, `abc`)
2. Third-party (`fastapi`, `numpy`, `cv2`, `pydantic`, `sqlalchemy`)
3. Internal project (`gps_denied.*`)
**Internal imports:**
- Always absolute — `from gps_denied.core.coordinates import CoordinateTransformer`
- Schema imports collected into explicit multi-line blocks at top of file
- Lazy in-function imports used to avoid circular imports: `from gps_denied.schemas import Geofences` inside a method body in `processor.py`
## ABC/Interface Pattern
All major processing components define an Abstract Base Class in the same module:
```python
class ISequentialVisualOdometry(ABC):
@abstractmethod
def compute_relative_pose(...) -> RelativePose | None: ...
class SequentialVisualOdometry(ISequentialVisualOdometry):
...
```
Components that depend on another component accept the interface type in `__init__`, enabling mock injection:
```python
class SequentialVisualOdometry(ISequentialVisualOdometry):
def __init__(self, model_manager: IModelManager): ...
```
This pattern appears in: `vo.py`, `models.py`, `rotation.py`.
`FlightProcessor` uses a post-construction injection method instead:
```python
def attach_components(self, vo=None, gpr=None, metric=None, ...): ...
```
## Pydantic Schema Conventions
- All schemas inherit from `pydantic.BaseModel`
- Validation constraints use `Field(..., ge=..., le=..., gt=..., min_length=..., max_length=...)`
- Default values use `Field(default_factory=...)` for mutable defaults
- `model_dump()` and `model_validate_json()` used (Pydantic v2 API)
- Config classes inherit from `pydantic_settings.BaseSettings` with `SettingsConfigDict(env_prefix=...)`
- Grouped into separate schema files by domain: `flight.py`, `vo.py`, `graph.py`, `metric.py`, `gpr.py`, `chunk.py`, `rotation.py`, `satellite.py`, `events.py`, `image.py`, `model.py`
## Error Handling
**Custom exceptions:**
- Defined in the module where they originate — `OriginNotSetError` in `coordinates.py`, `QueueFullError` / `ValidationError` in `pipeline.py`
- Inherit directly from `Exception` (no base project exception class)
**In async pipeline code:**
- `try/except Exception as exc` with `logger.warning(...)` — swallowed errors are logged, not re-raised:
```python
except Exception as exc:
logger.warning("VO failed for frame %d: %s", frame_id, exc)
```
- HTTP layer raises `HTTPException` from FastAPI — no custom HTTP exception hierarchy
**Type-ignore suppressions:**
- `# type: ignore[union-attr]` used on SQLAlchemy `result.rowcount` accesses in `repository.py`
- `# type: ignore` on two FastAPI dependency parameters in `flights.py` (multipart form endpoint)
## Logging
**Framework:** Python stdlib `logging`
**Setup per module:**
```python
import logging
logger = logging.getLogger(__name__)
```
**Usage patterns:**
- `logger.warning(...)` for recoverable failures (VO failure, drift correction failure)
- `logger.info(...)` for state machine transitions: `"Flight %s → LOST at frame %d"`
- `logger.debug(...)` for high-frequency data: optimization convergence results
## Section Separators
Long modules use visual comment dividers to delineate sections:
```python
# ---------------------------------------------------------------------------
# State Machine
# ---------------------------------------------------------------------------
```
and
```python
# =========================================================
# process_frame — central orchestration
# =========================================================
```
## Function Design
**Size:** Methods tend to be 10-40 lines; `process_frame` is intentionally longer (~80 lines) as the central orchestrator.
**Parameters:** Typed, ordered: `self`, required positional, optional keyword with defaults.
**Return Values:**
- Async methods return domain schema objects or `None` on not-found
- Sync methods return `bool` for success/failure of CRUD operations
- `Optional[T]` / `T | None` used for nullable returns consistently
## Comments
**Inline comments** used liberally to explain non-obvious math or mock logic:
```python
# 111319.5 meters per degree at equator
# FAKE Math for mockup:
# Very rough scaling: assume 1 pixel is ~0.1 meter
```
**Section-level comments** label major logical steps inside long methods:
```python
# ---- 1. Visual Odometry (frame-to-frame) ----
# ---- 2. State Machine transitions ----
```
## Module Design
**Exports:** No explicit `__all__` in any module; consumers import directly from sub-modules.
**Barrel Files:** `src/gps_denied/schemas/__init__.py` re-exports common types (`GPSPoint`, `CameraParameters`, `Geofences`, `Polygon`). Other packages use direct imports.
---
*Convention analysis: 2026-04-01*