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

7.6 KiB

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 IIImageMatcher, ISequentialVisualOdometry, IModelManager
  • Error/Exception classes suffixed with ErrorOriginNotSetError, 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 SettingsDatabaseConfig, RecoveryConfig, AppSettings

Functions/Methods:

  • snake_caseset_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_caseflight_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:

"""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:

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:

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:

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:
    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:

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:

# ---------------------------------------------------------------------------
# State Machine
# ---------------------------------------------------------------------------

and

# =========================================================
# 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:

# 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:

# ---- 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