Files
alro65 deb04c9315 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>
2026-05-17 07:26:06 -04:00

81 lines
2.7 KiB
Python

"""Tests de integridad de la biblioteca seed."""
from __future__ import annotations
from vmssailor.core.enums import EquipmentCategory, SystemId
from vmssailor.library import load_library, load_systems_catalog
def test_systems_catalog_loads() -> None:
cat = load_systems_catalog()
assert "categories" in cat
assert len(cat["categories"]) >= 10 # 11 categorías mínimo
def test_systems_catalog_covers_all_enum_values() -> None:
cat = load_systems_catalog()
json_ids = set()
for c in cat["categories"]:
for s in c["systems"]:
json_ids.add(s["id"])
enum_ids = {s.value for s in SystemId}
missing_in_json = enum_ids - json_ids
missing_in_enum = json_ids - enum_ids
assert not missing_in_json, f"SystemId enum tiene valores no en catálogo: {missing_in_json}"
assert not missing_in_enum, f"Catálogo tiene IDs no en SystemId enum: {missing_in_enum}"
def test_library_loads_without_errors() -> None:
result = load_library()
assert result.ok(), f"Biblioteca con errores: {result.errors}"
def test_library_has_minimum_seed() -> None:
result = load_library()
vessel_ids = {v.id for v in result.vessels}
assert "sunseeker_76" in vessel_ids
assert "ferretti_850" in vessel_ids
eq_ids = {e.id for e in result.equipment_models}
assert "mtu_12v_2000_m96" in eq_ids
assert "volvo_d13_900hp" in eq_ids
assert "northern_lights_m65c13" in eq_ids
def test_seed_engines_are_engine_main() -> None:
result = load_library()
for em in result.equipment_models:
if em.id.startswith("mtu_") or em.id.startswith("volvo_"):
assert em.category == EquipmentCategory.ENGINE_MAIN, em.id
def test_seed_genset_category() -> None:
result = load_library()
nl = next(e for e in result.equipment_models if e.id == "northern_lights_m65c13")
assert nl.category == EquipmentCategory.GENSET
def test_rules_yacht_motor_planeo_present() -> None:
result = load_library()
assert "yacht_motor_planeo" in result.rules
def test_rules_reference_existing_equipment_models() -> None:
result = load_library()
# Filtrar warnings que no son del cross-ref
cross_ref_warns = [
w
for w in result.warnings
if "no existe en equipment_models" in w.message
]
assert not cross_ref_warns, f"Rules con model_ref roto: {cross_ref_warns}"
def test_seed_marked_as_estimate() -> None:
"""Todos los archivos seed deben venir marcados como seed_estimate."""
result = load_library()
for v in result.vessels:
assert v.data_source == "seed_estimate", v.id
for em in result.equipment_models:
assert em.data_source == "seed_estimate", em.id