sprint-0: fundaciones VMS-Sailor

Sprint 0 completo del producto VMS-Sailor (Vessel Management System
integrado para buques 30-40m). Brief de referencia en
VMS_Sailor_v2_Parte_*.md (intacto).

Core (vmssailor.core, 95.17% coverage, 99 tests verde):
- ShipCoord: sistema naval x_pp/y_cl/z_bl frozen
- Vessel, Deck, Bulkhead
- Equipment, EquipmentModel, Sensor, EquipmentSpec
- Tag, AlarmConfig, TagBinding, Scaling
- CardInstance, Bus, Topology con validacion 21 puntos I/O AR-NMEA-IO-v1.0
- Alarm, PermissiveRule, Condition
- Project agregado raiz con validacion cross-entity
- Persistencia portable .vmsproj (SQLite) con roundtrip verificable

Biblioteca curada seed (vmssailor.library):
- systems_catalog.json completo (catalogo maestro Parte 1 sec 7)
- 2 vessels: Sunseeker 76, Ferretti 850
- 2 motores: MTU 12V 2000 M96, Volvo D13-900
- 1 genset: Northern Lights M65C13
- yacht_motor_planeo.yaml (reglas heuristicas)
- TODO marcado data_source=seed_estimate - requiere validacion datasheets

Tools:
- vms-validate-library: CLI valida biblioteca completa
- vms-generate-test-project: CLI demo + verificacion roundtrip persistencia

Design System + 8 mockups HTML estaticos:
- docs/design_system.md (paleta Deep Ocean, gradientes, typography, motion)
- docs/brand/ (logo + variantes SVG)
- docs/mockups/splash, studio_main, runtime_overview,
  runtime_mimic_fuel (P&ID animado), runtime_alarms, runtime_trim (panel
  estrella con horizonte artificial), mobile_overview, mobile_trim
- docs/mockups/index.html (galeria)

Firmware (Sprint 12+ implementacion):
- firmware/ar_nmea_io_v1/src/config/pinout.h con macros GPIO

Decisiones autonomas documentadas en docs/decisions_sprint0.md.

Stack: Python 3.11 + uv + Pydantic v2 + SQLite stdlib + hatchling +
pytest 9 + ruff + mypy. Sin PySide6, FastAPI, Flutter ni firmware
funcional (entran en sprints siguientes).

Criterio de aceptacion Sprint 0: cumplido.
- uv sync: OK
- pytest: 99/99 verde
- cov vmssailor.core: 95.17% (objetivo >=80%)
- ruff: clean
- vms-validate-library: OK
- vms-generate-test-project: INTEGRIDAD OK

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 07:26:06 -04:00
commit deb04c9315
96 changed files with 15335 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
"""Tests de Vessel, Deck, Bulkhead."""
from __future__ import annotations
import pytest
from pydantic import ValidationError
from vmssailor.core import Vessel, VesselSubtype, VesselType
from vmssailor.core.vessel import Bulkhead, Deck
def test_deck_height() -> None:
d = Deck(id="main", name="Main", z_bl_bottom=2.0, z_bl_top=4.5)
assert d.height() == pytest.approx(2.5)
def test_deck_polygon_empty_ok() -> None:
Deck(id="main", name="Main", z_bl_bottom=2.0, z_bl_top=4.5)
def test_deck_polygon_too_few_vertices_rejected() -> None:
with pytest.raises(ValidationError):
Deck(
id="main",
name="Main",
z_bl_bottom=2.0,
z_bl_top=4.5,
polygon_xy=[(0.0, 0.0), (1.0, 0.0)],
)
def test_minimal_vessel(minimal_vessel: Vessel) -> None:
assert minimal_vessel.length_overall_m == 24.0
assert minimal_vessel.type == VesselType.YACHT_MOTOR
assert minimal_vessel.subtype == VesselSubtype.PLANING
assert len(minimal_vessel.decks) == 1
def test_vessel_duplicate_deck_ids_rejected() -> None:
with pytest.raises(ValidationError):
Vessel(
id="x",
name="X",
type=VesselType.YACHT_MOTOR,
subtype=VesselSubtype.PLANING,
length_overall_m=20.0,
beam_max_m=5.0,
draft_m=1.5,
decks=[
Deck(id="main", name="A", z_bl_bottom=0.0, z_bl_top=2.0),
Deck(id="main", name="B", z_bl_bottom=2.0, z_bl_top=4.0),
],
)
def test_vessel_position_helpers(minimal_vessel: Vessel) -> None:
bow = minimal_vessel.position_at_bow()
assert bow.x_pp == minimal_vessel.length_overall_m
origin = minimal_vessel.position_at_origin()
assert origin.x_pp == 0.0
def test_vessel_invalid_length_rejected() -> None:
with pytest.raises(ValidationError):
Vessel(
id="x",
name="X",
type=VesselType.YACHT_MOTOR,
subtype=VesselSubtype.PLANING,
length_overall_m=-1.0,
beam_max_m=5.0,
draft_m=1.5,
)
def test_bulkhead_basic() -> None:
b = Bulkhead(id="col", name="Collision", x_pp=21.0)
assert b.x_pp == 21.0
assert b.description == ""