# Модель данных

## 1. `weigh_session`

Назначение: карточка полного или частичного цикла взвешивания.

Ключевые поля:

- `id`
- `tenant_id`, `site_id`, `industry_code`
- `lane_id`, `direction`
- `vehicle_plate`
- `trip_token`
- `status` (`detected|stabilizing|tare_recorded|gross_recorded|completed|rejected|manual_review`)
- `operation_mode` (`automatic|assisted|manual_review`)
- `phase_sequence` (`single_partial|tare_then_gross|gross_then_tare`)
- `active_phase` (`tare|gross|none`)
- `tare_kg`, `gross_kg`, `net_kg`
- `tare_captured_at`, `gross_captured_at`
- `tare_direction`, `gross_direction`
- `tare_lane_id`, `gross_lane_id`
- `stability_state` (`moving|stabilizing|stable|unstable`)
- `stability_window_ms`
- `driver_identity_id`
- `cargo_media_id`
- `plate_media_in_id`, `plate_media_out_id`
- `scale_indicator_id`
- `anpr_source`
- `review_reason`
- `close_reason`
- `created_at`, `updated_at`

### 1.1. Правила состояния

- `detected`:
  - сессия создана по факту въезда или ANPR, но вес еще не признан стабильным;
- `stabilizing`:
  - ТС находится на платформе, идет ожидание стабильного веса;
- `tare_recorded`:
  - записана фаза `tare`, вторая фаза еще не завершена;
- `gross_recorded`:
  - записана фаза `gross`, вторая фаза еще не завершена;
- `completed`:
  - есть обе фазы и рассчитан `net_kg`;
- `rejected`:
  - цикл признан невалидным;
- `manual_review`:
  - цикл не может быть автоматически завершен и требует разбора.

### 1.2. State machine phase-1

`detected -> stabilizing -> tare_recorded|gross_recorded -> completed`

Ветка исключений:

`detected|stabilizing|tare_recorded|gross_recorded -> manual_review|rejected`

### 1.3. Инварианты

- `vehicle_plate` обязателен для automatic flow;
- `net_kg` вычисляется только если одновременно заданы `tare_kg` и `gross_kg`;
- `completed` запрещен без двух фаз;
- `manual_review` допускает частичную карточку и пропуск identity enrichment;
- `manual_review` обязателен для `anpr_mismatch`, `missing_required_media`, `missing_phase` и неразрешенного `duplicate_session`;
- `rejected` не используется как silent fallback и задается только явным review-решением после exception flow;
- повторный `detect` в той же фазе должен переиспользовать существующий `session_id`, а не создавать дубль карточки;
- `tare_direction` и `gross_direction` могут отличаться на двусторонних весах.

## 2. `driver_identity`

Назначение: контур идентификации водителя и сопроводительных данных рейса.

Ключевые поля:

- `id`
- `tenant_id`, `site_id`
- `driver_id`
- `driver_name`
- `carrier_id`
- `vehicle_plate`
- `trailer_plate`
- `rfid_uid`
- `qr_payload`
- `trip_reference`
- `cargo_reference`
- `source` (`rfid|qr|mixed|manual_override`)
- `resolved_at`

Правило:

- identity contour может отсутствовать в automatic baseline и не должен блокировать карточку;
- если QR и RFID оба присутствуют, `source=mixed`.

## 3. `media_evidence`

Назначение: фото и кадры, прикрепленные к карточке взвешивания.

Ключевые поля:

- `id`
- `tenant_id`, `site_id`
- `session_id`
- `kind` (`plate_in|plate_out|cargo_top|overview`)
- `phase` (`tare|gross|unknown`)
- `camera_id`
- `storage_ref`
- `preview_ref`
- `captured_at`
- `classifier_payload`
- `hash`
- `required_for_close`

Правило media policy:

- `cargo_top` с `phase=gross` обязателен для automatic close;
- `cargo_top` с `phase=tare` обязателен только для спорных и непустых кейсов;
- отсутствие обязательного evidence переводит сессию в `manual_review`;
- `storage_ref` хранит оригинальный evidence path и не должен заменяться preview-ссылкой;
- `preview_ref` допустим только как пользовательская копия для UI/handover и не является основанием для automatic close;
- для `manual_review` и `rejected` storage refs сохраняются вместе с alarm/evidence bundle до завершенного handover и ERP sync.

## 4. `weigh_alarm_event`

Назначение: ошибки цикла, отказ оборудования и антифрод.

Ключевые поля:

- `id`
- `tenant_id`, `site_id`
- `session_id`
- `alarm_code`
- `severity`, `priority`
- `status` (`open|acknowledged|resolved|closed`)
- `message`
- `source`
- `correlation_id`
- `payload`
- `triggered_at`, `acked_at`, `resolved_at`

Базовые alarm codes phase-1:

- `weighbridge_weight_unstable`
- `weighbridge_duplicate_entry`
- `weighbridge_partial_exit`
- `weighbridge_anpr_mismatch`
- `weighbridge_camera_unavailable`
- `weighbridge_scale_timeout`
- `weighbridge_gate_interlock_fault`

## 5. Связи

- `weigh_session.driver_identity_id -> driver_identity.id`
- `media_evidence.session_id -> weigh_session.id`
- `weigh_alarm_event.session_id -> weigh_session.id`

## 6. Duplicate session policy

- duplicate определяется по сочетанию `vehicle_plate`, `lane_id`, активной фазы и текущего открытого cycle context;
- runtime должен вернуть уже существующую карточку и поднять `weighbridge_duplicate_entry` вместо создания второй `weigh_session`;
- если duplicate возникает на фоне failover или рассинхронизации панелей, authoritative session определяется по последнему подтвержденному `correlation_id`.

## 7. Минимальный phase-1 storage scope

В первую реализационную очередь обязательно входят:

- `weigh_session`
- `driver_identity`
- `media_evidence`
- `weigh_alarm_event`

Во phase-1 не требуется отдельный реестр ERP-документов и номенклатуры; они могут приходить как refs в `driver_identity`.
