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
+421
View File
@@ -0,0 +1,421 @@
# VMS-Sailor · Brief para Claude Code · Parte 3 de 6
## VMS-Sailor Runtime (a bordo del buque)
> Esta parte detalla el Runtime. Asume que ya leíste Partes 1 y 2.
---
## 1. Propósito
El Runtime es lo que se instala en el PC industrial del buque del cliente. Opera 24/7. Es lo que el armador, capitán, jefe de máquinas y tripulación usan día a día.
Dos componentes principales:
- **Servidor**: servicio Windows con motor de tiempo real, drivers, alarm engine, etc.
- **Cliente desktop**: aplicación PySide6 en cada estación de operación (puente, máquinas, eventualmente camarote del owner)
---
## 2. Estructura de carpetas del Runtime
```
vmssailor/runtime/
├── __init__.py
├── server/ # SERVIDOR (servicio Windows)
│ ├── __init__.py
│ ├── service.py # Servicio Windows (pywin32)
│ ├── main_loop.py # Loop asíncrono principal
│ │
│ ├── drivers/ # Drivers de protocolo
│ │ ├── __init__.py
│ │ ├── base_driver.py
│ │ ├── modbus_tcp.py
│ │ ├── modbus_rtu.py # Bus principal AR-NMEA-IO
│ │ ├── nmea2000.py # CAN/NMEA 2000 (publicar y suscribir)
│ │ ├── j1939.py # Para motores que hablan J1939 nativo
│ │ └── card_discovery.py # Auto-descubrimiento plug-and-produce
│ │
│ ├── tag_db/ # Base de tags
│ │ ├── __init__.py
│ │ ├── tag_store.py # En memoria + persistencia periódica
│ │ ├── tag_resolver.py # Resuelve id → valor, calidad, timestamp
│ │ └── historian.py # DuckDB con series temporales
│ │
│ ├── alarm_engine/
│ │ ├── __init__.py
│ │ ├── alarm_evaluator.py # Evalúa límites contra tags
│ │ ├── alarm_priorities.py # Emergency / High / Low / Info
│ │ ├── alarm_state.py # Active / Ack / Cleared
│ │ ├── acknowledgement.py
│ │ ├── escalation.py # Escala si no se ack en X tiempo
│ │ └── notification_dispatcher.py # A clientes desktop y móvil
│ │
│ ├── permissive_engine/ # Pre-condiciones para acciones
│ │ ├── __init__.py
│ │ ├── permissive_evaluator.py
│ │ ├── condition_parser.py # Lee reglas YAML del paquete
│ │ └── override_handler.py # Override consciente del Admin del buque
│ │
│ ├── authority/ # Command Authority
│ │ ├── __init__.py
│ │ ├── authority_manager.py
│ │ ├── transfer.py # Bilateral handshake puente↔máquinas
│ │ └── override.py # Override de emergencia
│ │
│ ├── stability/ # Protección de escora (Parte 1 sec 8)
│ │ ├── __init__.py
│ │ ├── attitude_reader.py # Lee PGN 127257 del backbone NMEA 2000
│ │ ├── roll_monitor.py # Filtros + detección sostenida
│ │ ├── safety_levels.py # Lógica niveles 1/2/3
│ │ ├── envelope_predictor.py # Predice efecto de comando antes de ejecutar
│ │ ├── auto_corrector.py # Corrección suave hacia neutral
│ │ ├── emergency_reset.py # Reset físico + virtual
│ │ └── owner_override.py # Modo manual con safety envelope
│ │
│ ├── config/ # Layer Config Engine (capas)
│ │ ├── __init__.py
│ │ ├── layer_loader.py # Carga capas en orden
│ │ ├── delta_applier.py # Aplica deltas firmados
│ │ ├── snapshot_manager.py # Snapshots automáticos
│ │ ├── rollback_engine.py # Rollback granular
│ │ ├── schema_validator.py
│ │ └── signature_verifier.py # Verifica firma de Álvaro
│ │
│ ├── logbook/ # Log Book naval (Parte 1 sec 6)
│ │ ├── __init__.py
│ │ ├── engine_log_writer.py # Auto-detección arranque/parada motores
│ │ ├── alarm_log_writer.py
│ │ ├── manual_entry_handler.py
│ │ ├── snapshot_periodic.py # Snapshots cada 15 min con motor corriendo
│ │ ├── log_signer.py # Firma digital inmutable
│ │ └── log_exporter.py # Exporta a PDF formato oficial
│ │
│ ├── licensing/ # Activación HWID
│ │ ├── __init__.py
│ │ ├── hwid_generator.py # MAC + Disk Serial + UUID
│ │ ├── activator.py # Activación online inicial
│ │ ├── verifier.py # Verifica firma del paquete vs HWID
│ │ └── license_file.py
│ │
│ ├── vpn/ # Soporte VPN administrativa
│ │ ├── __init__.py
│ │ └── wg_helper.py # Helpers para WireGuard externo
│ │
│ ├── audit/ # Auditoría
│ │ ├── __init__.py
│ │ ├── audit_log.py # Eventos auditados inmutables
│ │ └── vpn_session_tracker.py # Registra cada sesión VPN de Álvaro
│ │
│ ├── telemetry/ # Telemetría de salud técnica
│ │ ├── __init__.py
│ │ ├── health_reporter.py
│ │ └── client_transparency.py # Lo que se envía es visible al owner
│ │
│ └── api/
│ ├── __init__.py
│ ├── fastapi_app.py
│ ├── ws_endpoints.py # WebSockets tiempo real
│ ├── rest_endpoints.py # REST configuración + históricos
│ ├── auth.py # JWT local + TOTP para móvil
│ └── mobile_endpoints.py # Endpoints específicos Mobile
├── client/ # CLIENTE DESKTOP (PySide6)
│ ├── __init__.py
│ ├── app.py
│ ├── login.py # Login Operador/Técnico/Admin
│ ├── main_window.py
│ ├── ws_client.py # Cliente WebSocket
│ │
│ ├── views/ # Vistas principales
│ │ ├── overview.py # Panel general del buque
│ │ ├── mimic_view.py # Mímico de un sistema
│ │ ├── alarm_panel.py # Lista alarmas viva con ack
│ │ ├── trends_panel.py # Gráficas tiempo real
│ │ ├── logbook_view.py # Log book
│ │ ├── audit_view.py # Auditoría (Admin solo)
│ │ ├── support_view.py # Pestaña "Soporte y Auditoría" del Admin
│ │ ├── trim_panel.py # Control trim y maniobra
│ │ └── settings.py # Settings limitados del cliente
│ │
│ └── widgets/ # Widgets reutilizables
│ ├── system_sidebar.py # Menú lateral dinámico
│ ├── tag_widget.py # Display individual con color/alarma
│ ├── gauge.py
│ ├── lamp.py # Indicador on/off
│ ├── bargraph.py
│ ├── valve.py # Símbolo válvula con estado
│ ├── motor.py # Símbolo motor
│ ├── pump.py
│ ├── tank.py
│ └── alarm_badge.py
```
---
## 3. Motor de tiempo real (Server)
### Loop principal
Servicio Windows arranca al boot. Main loop asíncrono con `asyncio`. Múltiples tareas concurrentes:
- **Driver Modbus RTU** poolea las tarjetas AR-NMEA-IO en el bus (cada 100ms por defecto, configurable por tag)
- **Driver NMEA 2000** suscribe a PGNs configurados, lee del bus CAN
- **Tag store** recibe updates de drivers, mantiene en memoria valores actuales con timestamp y calidad
- **Historian** guarda en DuckDB cada N segundos (configurable por tag, default 1s para críticos, 60s para no-críticos)
- **Alarm engine** evalúa cambios de tags contra límites
- **Permissive engine** evalúa cuando se solicita acción
- **Stability monitor** lee PGN 127257 cada 100ms, monitorea roll/pitch
- **API server** atiende WebSockets de clientes desktop y Mobile, REST para configuración
- **VPN listener** atiende endpoints administrativos solo desde IP del túnel
- **Health reporter** envía telemetría técnica si está habilitada
### Tag store
Estructura en memoria optimizada:
- Dict por `tag_id` → objeto `Tag` con: valor, calidad, timestamp, unidad SI, rango normal, alarmas configuradas, `controllable`, `control_mode`, `authority_required`, protocolo, dirección, escalado
- Pub/Sub interno: cuando un valor cambia, se notifica a suscriptores (alarm engine, historian, API clients)
- Calidad: GOOD, BAD, UNCERTAIN. Si un sensor da timeout 3 veces seguidas → calidad BAD, valor último conocido marcado como stale.
### Historian (DuckDB)
DuckDB embebido en archivo `%PROGRAMDATA%/VMS-Sailor/historian.duckdb`. Esquema simple:
```
tag_id (string), timestamp (datetime), value (double), quality (enum)
```
Retención configurable por tipo de tag:
- Tags críticos: alta resolución (1 muestra/s) durante 30 días, downsampled a 1/min después de 30 días, retenidos 5 años
- Tags no críticos: 1 muestra/min durante 90 días, downsampled a 1/hora después
- Eventos discretos (alarmas, comandos): retención completa, todos guardados
### Alarm engine
Cada tag con alarmas configuradas se evalúa cuando cambia su valor:
- Compara contra límites: `low_low`, `low`, `high`, `high_high`
- Cada límite tiene prioridad: Emergency, High, Low, Info
- Histéresis: tag no sale de alarma hasta cruzar el límite +/- N (configurable)
- Retraso: tag no entra en alarma hasta que la condición persiste X segundos (anti-flicker)
- Mensaje configurable por alarma
- Estados: ACTIVE (no ack), ACK (ack pero condición persiste), CLEARED (condición resuelta)
- Escalación: si una alarma Emergency lleva X minutos sin ack, escala (sonido más fuerte, notificación móvil push)
### Authority Manager (puente ↔ máquinas)
Estado: cuál estación tiene autoridad actualmente (BRIDGE, ENGINE, NONE en blackout).
Transferencia bilateral:
1. Estación A solicita autoridad
2. Estación B recibe notificación, debe confirmar o rechazar
3. Si confirma, autoridad cambia
4. Timeout: si B no responde en 30s, autoridad permanece en A
Override de emergencia: botón E-stop físico cablea directo a tarjeta cerca del motor (permissive local). Anula cualquier autoridad. Detiene equipo críticamente. Registra evento crítico en logbook.
### Stability Monitor (detalle Parte 1 sec 8)
Suscribe a PGN 127257 del backbone NMEA 2000 (que publica el AR-ECDIS). Lee roll/pitch cada 100ms. Aplica filtros (mediana 5 muestras + promedio 1s). Evalúa tres niveles:
```python
async def stability_loop():
while running:
attitude = await read_pgn_127257() # del backbone
roll = filter(attitude.roll)
pitch = filter(attitude.pitch)
await level_1_check(roll, pitch) # WARNING
await level_2_check(roll, pitch) # AUTO-OFFER
await level_3_check(roll, pitch) # AUTO-RESET FORZADO
await asyncio.sleep(0.1)
```
---
## 4. Cliente desktop (PySide6)
### Login
Tres perfiles fijos. Login con usuario + password (gestionados por el Admin del buque). En sprint posterior se puede agregar SSO o LDAP.
### Layout principal
- Barra superior: nombre del buque, estado conexión servidor, alarmas activas (badge), autoridad actual, usuario logueado
- Barra lateral: menú dinámico de sistemas (generado a partir del `.vmspack` activo). Solo aparecen los sistemas marcados en el wizard del Studio
- Vista central: mímico del sistema seleccionado, o vista de overview por defecto
- Barra inferior: ticker de alarmas, hora del sistema, estado VPN
### Vistas por sistema
Cada sistema marcado tiene su propio mímico (SVG/JSON empaquetado en `.vmspack`). El mímico se renderiza con PySide6 + QGraphicsView. Los símbolos (motor, válvula, bomba, etc.) muestran:
- Estado en color (verde = OK, amarillo = warning, rojo = alarma, gris = offline)
- Valores en displays digitales sobre el símbolo
- Click → menú contextual con acciones disponibles según permissives + autoridad
### Panel de alarmas
Lista cronológica de alarmas activas y recientes:
- Color por prioridad
- Botón ACK por alarma
- Filtros por sistema, prioridad, estado
- Histórico completo accesible (DuckDB)
### Panel de trends
Gráficas tiempo real (último 1 hora, 24 horas, 7 días). Hasta 8 tags simultáneos en mismo eje. Zoom y pan. Exportable a CSV.
### Panel de Trim y maniobra (caso de uso destacado)
- Sliders visuales para cada actuador de trim (2 motores + 2 trim tabs típicamente)
- Indicador en tiempo real de roll y pitch (desde PGN 127257)
- Botón "Reset Emergencia" muy visible
- Toggle "Modo Manual del Owner" (con PIN/biométrico)
- Indicador visual del envelope de seguridad activo
### Vista de Soporte y Auditoría (solo Admin del buque)
- Log de mis conexiones VPN: fecha, duración, endpoints accedidos, mensaje mío sobre qué hice
- Configuración de telemetría: qué se envía, pausar/desactivar
- Gestión de usuarios del Runtime
- Gestión de dispositivos móviles enrolados (revocar si se pierde)
- Historial de snapshots con botón rollback
---
## 5. API del servidor
### WebSockets (tiempo real)
`ws://localhost:8765/realtime` — todos los clientes desktop y Mobile conectan aquí.
Mensajes que envía el servidor:
- `tag_update`: id, value, quality, timestamp
- `alarm_event`: alarm_id, state, priority
- `authority_change`: new_authority
- `stability_event`: level, roll, pitch
- `permissive_check_result`: action_id, status, reasons
Mensajes que recibe del cliente:
- `subscribe_tags`: lista de IDs para suscribirse
- `request_action`: solicita ejecutar acción de control (genera permissive check)
- `ack_alarm`: ack de alarma
- `request_authority`: solicita autoridad
- `commit_logbook_entry`: entrada manual al logbook
### REST (configuración y consultas)
- `GET /tags` — lista de tags con configuración
- `GET /tags/{id}/history?from=X&to=Y` — históricos
- `GET /alarms?state=active|all` — alarmas
- `GET /logbook?from=X&to=Y` — entradas logbook
- `POST /logbook` — entrada manual (Técnico/Admin)
- `GET /audit/vpn_sessions` — sesiones VPN históricas (Admin solo)
- `POST /telemetry/pause` — owner pausa telemetría
### Endpoints administrativos VPN (acceso solo desde IP del túnel)
- `POST /admin/upload_delta` — Álvaro sube un .vmsdelta firmado
- `POST /admin/apply_delta/{id}` — aplica delta (owner debe haber aprobado en su UI antes)
- `GET /admin/diagnostics` — diagnóstico del sistema
- `POST /admin/rollback/{snapshot_id}` — rollback (registrado en auditoría)
---
## 6. Layer Config Engine
Detalle de cómo se cargan y aplican las capas (Parte 1 sec 5):
```python
async def boot_runtime():
config = empty_config()
# Capa 1
base = load_vmspack(path_base)
verify_signature(base, alvaro_pubkey)
verify_hwid(base, current_hwid)
config.apply(base)
# Capa 2
if exists(commissioning_delta):
config.apply(load_vmsdelta(commissioning_delta))
# Capa 3
if exists(owner_prefs_delta):
config.apply(load_vmsdelta(owner_prefs_delta))
# Capa 4 — expansiones en orden cronológico
for delta in sorted(list_expansion_deltas()):
verify_signature(delta, alvaro_pubkey)
config.apply(delta)
# Configuración efectiva lista
start_services(config)
```
Snapshots automáticos ANTES de aplicar cualquier delta. Rollback granular permite volver a cualquier snapshot (visible al Admin del buque).
---
## 7. Telemetría de salud (transparente al cliente)
Solo se envía estado técnico, NO contenido operativo:
```json
{
"timestamp": "2027-03-15T14:32:00Z",
"runtime_version": "1.4.2",
"package_version": "v2.0",
"cpu_pct": 23,
"ram_mb": 458,
"disk_free_gb": 145,
"uptime_hours": 1284,
"drivers": {
"modbus_rtu": {"status": "ok", "errors_24h": 12},
"nmea2000": {"status": "ok", "errors_24h": 0}
},
"tags_offline": ["TANK_FUEL_2.LEVEL"],
"alarms_unack_critical": 0,
"vpn_status": "available"
}
```
El owner ve en su UI **exactamente** lo que se está enviando en tiempo real, puede pausar o desactivar. Si desactiva, yo solo doy soporte reactivo cuando él me llame.
---
## 8. Log Book naval (módulo básico para Sprint 5/6)
Por decisión del Sprint, arrancamos con módulo básico — regulatorio completo viene después.
**Lo que SÍ implementamos en Sprint 5/6:**
- Auto-detección arranque/parada motores: cuando `Card_02.DO1` se energiza y luego `Card_02.RPM1 > 600` por 10s → entrada automática
- Snapshots periódicos cada 15 min con motor corriendo: RPM, temp aceite, temp agua, presión aceite, voltaje batería, horas acumuladas
- Auto-registro de alarmas con timestamp + ack
- Auto-registro de eventos de seguridad (autoridad, override, escora, trim reset)
- Entradas manuales del Técnico/Admin
- Firma digital inmutable
- Export CSV básico
**Lo que dejamos para Sprint 13+:**
- Formato PDF oficial según regulación IMO MARPOL
- Oil Record Book separado
- Garbage Record Book
- Cumplimiento estricto MARPOL Annex I/V
- Firma con certificado X.509 del capitán
---
## 9. Sprints relacionados con Runtime
- **Sprint 4**: drivers Modbus RTU/TCP + tag_db + historian + alarm engine básico + API base
- **Sprint 5**: driver NMEA 2000 + log book básico + simulador para test bench
- **Sprint 6**: cliente desktop completo (estaciones puente y máquinas) + panel alarmas + trends
- **Sprint 8**: Permissive engine + Authority transfer + Stability monitor (protección escora)
- **Sprint 9-10**: Layer Config Engine + deltas + rollback + activación HWID + telemetría
- **Sprint 11** (parte): endpoints específicos para Mobile
Detalle completo en Parte 6.
---
**Fin de Parte 3 de 6.** Próxima: Parte 4 — Hardware AR-NMEA-IO + Firmware ESP32.