Модуль "ContainerHub" — Контейнерний Термінал¶
Django app name:
containerhubDOP Section code:containerhubApp Store code:appContainerHubPlugin key:container_hubRequires:appEssentials,appLogisticТип: module (subscription)
1. Загальний опис¶
Модуль ContainerHub забезпечує повний цикл управління контейнерним терміналом (сухий порт, морський порт, залізничний хаб):
| Функціональний блок | Опис |
|---|---|
| Пообʼєктний облік контейнерів | Кожен контейнер — окремий обʼєкт обліку з номером ISO 6346, типом, власником, станом, історією переміщень |
| Управління двором (Yard) | Термінали → зони → позиції (row/bay/tier). 3D ізометрична карта двору зі штабелюванням |
| Stacking Optimization | Алгоритми оптимального розміщення (first-to-leave on top), мінімізація перестановок, pre-marshalling |
| Gate Control | Оформлення вʼїзду/виїзду контейнерів (gate-in / gate-out) з фіксацією стану, пломб, VGM |
| Інвентаризація | Три режими: ручна (мобільний додаток), AI-дрон (Jetson + YOLO + OCR), комбінована. План/факт аналіз |
| Демередж / Детеншен | Автоматичний розрахунок за тарифами shipping line. Alerts при наближенні до deadline |
| Контейнерна логістика | Інтеграція з модулем Logistic: shipment legs, vessel bookings, multimodal transport |
| Мобільний додаток | Розширення Driver App для yard-менеджера: сканування, gate-in/out, inventory walk |
2. Залежності від існуючих модулів¶
┌──────────────────────────────────────────────────────────┐
│ ContainerHub (новий) │
│ Container, ContainerHub, YardZone, GateTransaction, │
│ ContainerMovement, YardInventory, DemurrageCalc │
├──────────────────────────────────────────────────────────┤
│ uses ↓ │
├──────────────┬───────────────┬───────────────────────────┤
│ Essentials │ Fleet │ Logistic │
│ Client │ Vehicle │ ContainerType │
│ Currency │ Driver │ ShippingLine (new here) │
│ Organization│ LocationPoint│ Shipment + Legs │
│ Contract │ Route │ VesselBooking │
│ │ │ LogisticLocation │
└──────────────┴───────────────┴───────────────────────────┘
Перевикористання:
- fleet.Vehicle — шасі / тягач / платформа для перевезення контейнера
- fleet.Driver — водій автомобіля на gate-in/gate-out
- fleet.LocationPoint — геокоординати терміналу
- essentials.Client — контрагенти: shipping lines, перевізники, клієнти
- essentials.Currency — валюта тарифів і нарахувань
- logistic.ContainerType — вже задизайнений (20DC, 40HC тощо)
- logistic.Shipment + ShipmentLeg — мультимодальні відправки
- logistic.VesselBooking — бронювання морського фрахту
3. Моделі — Довідники (Master Data)¶
3.1 Container (Контейнер) — пообʼєктний облік¶
Base: MasterDataModel
| Поле | Тип | Опис |
|---|---|---|
container_number |
CharField(11), unique per tenant | Номер ISO 6346: ABCU1234567 |
container_type |
FK → logistic.ContainerType | 20DC, 40DC, 40HC, 45HC, OT, FR, RF |
owner_type |
Enum: own, leased, shipping_line, client | Тип власності |
owner |
FK → essentials.Client (nullable) | Власник / орендодавець / лінія |
shipping_line |
FK → ShippingLine (nullable) | Лінія (для лінійних контейнерів) |
manufacturing_year |
IntegerField (nullable) | Рік виробництва |
tare_weight |
DecimalField | Вага тари (кг) |
max_payload |
DecimalField | Максимальна вантажопідйомність (кг) |
is_reefer |
BooleanField | Рефрижератор |
condition |
Enum: new, good, damaged, needs_repair, scrapped | Поточний стан |
current_containerhub |
FK → ContainerHub (nullable) | Де зараз знаходиться |
current_zone |
FK → YardZone (nullable) | В якій зоні |
current_position |
CharField (nullable) | Позиція: "A-05-03-2" (zone-row-bay-tier) |
last_gate_in |
DateTimeField (nullable) | Останній вʼїзд |
last_inspection_date |
DateField (nullable) | Дата останнього огляду |
csc_plate_date |
DateField (nullable) | CSC табличка — дата наступної перевірки |
image |
ImageField (nullable) | Фото контейнера |
Subtables:
- ContainerInspection (related) — історія оглядів/пошкоджень
- ContainerSeal (related) — історія пломб
_subtables:
_subtables = [
{'code': 'inspections', 'name': 'Inspection History', 'name_ua': 'Історія оглядів',
'type': 'related', 'autoload': False},
{'code': 'seals', 'name': 'Seal History', 'name_ua': 'Історія пломб',
'type': 'related', 'autoload': False},
]
3.2 ContainerInspection (Огляд контейнера)¶
Base: TenantAwareModel
| Поле | Тип | Опис |
|---|---|---|
container |
FK → Container | Контейнер |
inspection_date |
DateTimeField | Дата/час огляду |
inspector |
FK → User (nullable) | Хто оглядав |
condition_before |
Enum | Стан до |
condition_after |
Enum | Стан після |
damage_type |
Enum: none, dent, hole, rust, floor, door, roof, wall | Тип пошкодження |
damage_location |
CharField | Місце пошкодження (front, back, left, right, top, bottom) |
damage_description |
TextField | Опис |
photo |
ImageField (nullable) | Фото пошкодження |
repair_required |
BooleanField | Потребує ремонту |
repair_cost_estimate |
DecimalField (nullable) | Оцінка вартості ремонту |
3.3 ContainerSeal (Пломба)¶
Base: TenantAwareModel
| Поле | Тип | Опис |
|---|---|---|
container |
FK → Container | Контейнер |
seal_number |
CharField | Номер пломби |
seal_type |
Enum: bolt, cable, padlock, electronic | Тип пломби |
action |
Enum: applied, removed, verified, broken | Дія |
action_date |
DateTimeField | Дата/час |
action_by |
FK → User (nullable) | Хто виконав |
gate_transaction |
FK → GateTransaction (nullable) | При якій операції |
3.4 ContainerHub (Термінал / Порт)¶
Base: MasterDataModel
| Поле | Тип | Опис |
|---|---|---|
containerhub_type |
Enum: sea_port, dry_port, rail_containerhub, inland_depot, warehouse | Тип терміналу |
location |
FK → fleet.LocationPoint | Географія (lat/lon, адреса, місто) |
capacity_teu |
IntegerField | Максимальна місткість (TEU) |
current_occupancy_teu |
IntegerField | Поточне заповнення (авто-обчислюється) |
contact_phone |
CharField (nullable) | Телефон |
contact_email |
EmailField (nullable) | |
working_hours |
CharField (nullable) | Режим роботи |
Subtables:
- YardZone (inline) — зони двору
_subtables:
_subtables = [
{'code': 'zones', 'name': 'Yard Zones', 'name_ua': 'Зони двору',
'type': 'inline', 'autoload': True},
]
3.5 YardZone (Зона двору)¶
Base: TenantAwareModel
| Поле | Тип | Опис |
|---|---|---|
containerhub |
FK → ContainerHub | Термінал |
zone_code |
CharField | Код зони: "A", "B1", "REEFER-1" |
zone_type |
Enum: import, export, empty, reefer, dangerous, transshipment, inspection, repair | Призначення |
rows |
IntegerField | Кількість рядів |
bays |
IntegerField | Кількість місць в ряду |
max_tiers |
IntegerField | Максимальна висота штабелювання |
capacity_teu |
IntegerField | Авто = rows × bays × max_tiers (для 20ft) |
is_active |
BooleanField | Активна |
has_power |
BooleanField | Є підключення електрики (для рефрижераторів) |
has_rail_access |
BooleanField | Є залізничний доступ |
3.6 ShippingLine (Судноплавна лінія)¶
Base: MasterDataModel
| Поле | Тип | Опис |
|---|---|---|
client |
FK → essentials.Client | Звʼязок з карткою клієнта |
scac_code |
CharField(4) | Standard Carrier Alpha Code (MAEU, HLCU, CMDU) |
free_days_import |
IntegerField, default=14 | Безкоштовних днів (імпорт) |
free_days_export |
IntegerField, default=7 | Безкоштовних днів (експорт) |
detention_free_days |
IntegerField, default=7 | Безкоштовних днів детеншен |
demurrage_rate_20 |
DecimalField (nullable) | Ставка демереджу 20ft/день |
demurrage_rate_40 |
DecimalField (nullable) | Ставка демереджу 40ft/день |
detention_rate_20 |
DecimalField (nullable) | Ставка детеншену 20ft/день |
detention_rate_40 |
DecimalField (nullable) | Ставка детеншену 40ft/день |
currency |
FK → essentials.Currency | Валюта ставок |
Примітка: ShippingLine може бути частиною logistic модуля (спільна для Logistic і ContainerHub). Фінальне рішення — при імплементації.
3.7 ContainerTariff (Тарифи терміналу)¶
Base: MasterDataModel
| Поле | Тип | Опис |
|---|---|---|
containerhub |
FK → ContainerHub | Термінал |
tariff_type |
Enum: storage, handling_in, handling_out, reefer_monitoring, gate_fee, weighing, inspection | Тип послуги |
container_size |
Enum: 20, 40, 45 | Розмір контейнера |
loaded_status |
Enum: loaded, empty, any | Навантажений/порожній |
rate |
DecimalField | Ставка |
rate_unit |
Enum: per_day, per_operation, per_hour | Одиниця |
currency |
FK → essentials.Currency | Валюта |
effective_from |
DateField | Діє з |
effective_to |
DateField (nullable) | Діє до |
free_days |
IntegerField, default=0 | Безкоштовних днів зберігання |
4. Моделі — Документи (Transactions)¶
4.1 GateTransaction (Gate-In / Gate-Out)¶
Base: TransactionModel
Основний документ оформлення вʼїзду/виїзду контейнера на/з території терміналу.
| Поле | Тип | Опис |
|---|---|---|
transaction_type |
Enum: gate_in, gate_out | Тип операції |
container |
FK → Container | Контейнер |
containerhub |
FK → ContainerHub | Термінал |
transport_mode |
Enum: truck, rail, internal, crane | Спосіб доставки |
vehicle |
FK → fleet.Vehicle (nullable) | Автомобіль |
driver |
FK → fleet.Driver (nullable) | Водій |
vehicle_plate |
CharField (nullable) | Номерний знак (якщо зовнішній) |
driver_name |
CharField (nullable) | ПІБ водія (якщо зовнішній) |
booking |
FK → ContainerBooking (nullable) | Посилання на бронювання слоту |
shipping_line |
FK → ShippingLine (nullable) | Лінія |
bill_of_lading |
CharField (nullable) | Номер коносаменту |
seal_number |
CharField (nullable) | Номер пломби |
seal_intact |
BooleanField (nullable) | Пломба ціла (при gate-in) |
container_condition |
Enum: good, damaged, needs_inspection | Стан контейнера |
damage_notes |
TextField (nullable) | Опис пошкоджень |
is_loaded |
BooleanField | Навантажений / порожній |
cargo_description |
TextField (nullable) | Опис вантажу |
cargo_weight |
DecimalField (nullable) | Вага вантажу |
vgm_weight |
DecimalField (nullable) | Verified Gross Mass |
is_dangerous |
BooleanField, default=False | Небезпечний вантаж |
imo_class |
CharField (nullable) | Клас IMO (якщо небезпечний) |
un_number |
CharField (nullable) | UN номер (якщо небезпечний) |
yard_zone |
FK → YardZone (nullable) | Зона розміщення (при gate-in) |
yard_position |
CharField (nullable) | Позиція: row-bay-tier |
temperature_set |
DecimalField (nullable) | Встановлена температура (рефрижератор) |
gate_timestamp |
DateTimeField | Фактичний час проїзду через gate |
Subtables:
- GatePhoto (related) — фото при gate-in/out
Бізнес-логіка при проведенні (post_document):
- Gate-In: оновити Container.current_containerhub, current_zone, current_position, last_gate_in
- Gate-Out: обнулити current_containerhub/zone/position; автоматичний розрахунок демереджу
- Запис у ContainerLedger
4.2 ContainerMovement (Внутрішнє переміщення)¶
Base: TransactionModel
| Поле | Тип | Опис |
|---|---|---|
container |
FK → Container | Контейнер |
containerhub |
FK → ContainerHub | Термінал |
from_zone |
FK → YardZone | Звідки |
from_position |
CharField | Позиція (row-bay-tier) |
to_zone |
FK → YardZone | Куди |
to_position |
CharField | Нова позиція |
reason |
Enum: restack, consolidation, inspection, reefer_connect, loading_prep, reposition | Причина |
equipment_used |
Enum: rtg, reach_stacker, forklift, side_loader, manual | Техніка |
moved_at |
DateTimeField | Фактичний час переміщення |
Бізнес-логіка при проведенні:
- Оновити Container.current_zone, current_position
- Запис у ContainerLedger
4.3 ContainerBooking (Бронювання слоту)¶
Base: TransactionModel
Truck Appointment System — бронювання часового вікна для gate-in / gate-out.
| Поле | Тип | Опис |
|---|---|---|
booking_type |
Enum: gate_in, gate_out | Тип |
container |
FK → Container (nullable) | Контейнер (якщо відомий) |
container_number |
CharField (nullable) | Номер (якщо ще не в системі) |
container_type |
FK → logistic.ContainerType (nullable) | Тип контейнера |
containerhub |
FK → ContainerHub | Термінал |
scheduled_date |
DateField | Планова дата |
time_slot_from |
TimeField | Вікно: початок |
time_slot_to |
TimeField | Вікно: кінець |
transport_company |
FK → essentials.Client (nullable) | Перевізник |
vehicle_plate |
CharField (nullable) | Номер авто |
driver_name |
CharField (nullable) | Водій |
driver_phone |
CharField (nullable) | Телефон водія |
is_loaded |
BooleanField | Навантажений |
cargo_description |
TextField (nullable) | Вантаж |
is_dangerous |
BooleanField, default=False | Небезпечний |
shipping_line |
FK → ShippingLine (nullable) | Лінія |
bill_of_lading |
CharField (nullable) | Коносамент |
actual_arrival |
DateTimeField (nullable) | Фактичний час прибуття |
gate_transaction |
FK → GateTransaction (nullable) | Створений gate-документ |
4.4 YardInventory (Інвентаризація двору)¶
Base: TransactionModel
| Поле | Тип | Опис |
|---|---|---|
containerhub |
FK → ContainerHub | Термінал |
inventory_type |
Enum: full, zone, spot_check | Повна / зонна / вибіркова |
zone |
FK → YardZone (nullable) | Зона (для зонної) |
source |
Enum: manual, mobile_app, drone_ai, combined | Джерело даних |
started_at |
DateTimeField (nullable) | Час початку |
completed_at |
DateTimeField (nullable) | Час завершення |
expected_count |
IntegerField | Очікувана кількість (з облікових даних) |
actual_count |
IntegerField, default=0 | Фактична кількість |
confirmed_count |
IntegerField, default=0 | Підтверджено |
missing_count |
IntegerField, default=0 | Відсутні |
unexpected_count |
IntegerField, default=0 | Не в обліку |
misplaced_count |
IntegerField, default=0 | Не на місці |
Subtables:
- InventoryLine (inline) — рядки інвентаризації
- DroneFlightResult (related) — результати дрон-обльоту
_subtables:
_subtables = [
{'code': 'lines', 'name': 'Inventory Lines', 'name_ua': 'Рядки інвентаризації',
'type': 'inline', 'autoload': True},
{'code': 'droneResults', 'name': 'Drone Flight Results', 'name_ua': 'Результати дрону',
'type': 'related', 'autoload': False},
]
4.5 InventoryLine (Рядок інвентаризації)¶
Base: TenantAwareModel
| Поле | Тип | Опис |
|---|---|---|
inventory |
FK → YardInventory | Документ |
sequence |
IntegerField | Порядковий номер |
container |
FK → Container (nullable) | Контейнер (з облікових даних) |
container_number |
CharField | Номер (сканований / розпізнаний) |
expected_zone |
FK → YardZone (nullable) | Очікувана зона |
expected_position |
CharField (nullable) | Очікувана позиція |
actual_zone |
FK → YardZone (nullable) | Фактична зона |
actual_position |
CharField (nullable) | Фактична позиція |
line_status |
Enum: confirmed, missing, unexpected, misplaced | Статус |
detected_by |
Enum: manual, mobile_scan, drone_ai | Хто виявив |
ai_confidence |
DecimalField (nullable) | Впевненість AI (0.0–1.0) |
photo_url |
URLField (nullable) | Фото / кадр з дрону |
notes |
TextField (nullable) | Коментар |
4.6 DemurrageCalculation (Розрахунок демереджу / детеншену)¶
Base: TransactionModel
| Поле | Тип | Опис |
|---|---|---|
calculation_type |
Enum: demurrage, detention | Тип нарахування |
container |
FK → Container | Контейнер |
shipping_line |
FK → ShippingLine | Лінія |
gate_in |
FK → GateTransaction | Gate-in документ |
gate_out |
FK → GateTransaction (nullable) | Gate-out (nullable = ще на терміналі) |
period_from |
DateField | Початок періоду |
period_to |
DateField | Кінець (або today якщо не виїхав) |
total_days |
IntegerField | Загальна кількість днів |
free_days |
IntegerField | Безкоштовних днів |
chargeable_days |
IntegerField | Платних днів |
rate_per_day |
DecimalField | Ставка за день |
currency |
FK → essentials.Currency | Валюта |
total_amount |
DecimalField | Загальна сума |
client |
FK → essentials.Client (nullable) | На кого нараховано |
invoice |
FK → essentials.Invoice (nullable) | Повʼязаний рахунок |
notes |
TextField (nullable) | Коментар |
Бізнес-логіка:
total_days = (period_to - period_from).days
chargeable_days = max(0, total_days - free_days)
total_amount = chargeable_days × rate_per_day
Автоматичне створення при gate-out (якщо chargeable_days > 0).
Підтримка перерахунку для контейнерів, що ще на терміналі.
5. Моделі — Регістри¶
5.1 ContainerLedger (Регістр руху контейнерів)¶
Base: RegisterModel
| Поле | Тип | Опис |
|---|---|---|
container |
FK → Container | Контейнер |
containerhub |
FK → ContainerHub | Термінал |
event_type |
Enum: gate_in, gate_out, movement, inventory_adjustment | Тип події |
zone |
FK → YardZone (nullable) | Зона |
position |
CharField (nullable) | Позиція |
is_loaded |
BooleanField | Навантажений |
reference_type |
CharField | Тип документа-підстави |
reference_id |
IntegerField | ID документа-підстави |
5.2 StorageChargeLedger (Регістр нарахувань за зберігання)¶
Base: RegisterModel
| Поле | Тип | Опис |
|---|---|---|
container |
FK → Container | Контейнер |
containerhub |
FK → ContainerHub | Термінал |
charge_type |
Enum: storage, handling, reefer, gate_fee, demurrage, detention | Тип нарахування |
days |
IntegerField (nullable) | Кількість днів |
rate |
DecimalField | Ставка |
amount |
DecimalField | Сума |
currency |
FK → essentials.Currency | Валюта |
client |
FK → essentials.Client (nullable) | Контрагент |
6. AI Drone — Інтеграція з Jetson + YOLO¶
6.1 Архітектура¶
┌─────────────────────────────────────────────────────┐
│ Дрон + NVIDIA Jetson │
│ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │
│ │ Камера │→ │ YOLO v8 │→ │ Container OCR │ │
│ │ 4K/8K │ │ Detection│ │ (ISO 6346) │ │
│ └──────────┘ └──────────┘ └───────┬────────┘ │
│ │ │
│ ┌────────────────────────────────────┘ │
│ │ JSON: container_number, bbox, GPS, confidence │
│ └──────────┬──────────────────────────────────┘ │
│ │ Batch upload (WiFi/4G) │
└─────────────┼───────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────┐
│ Backend: POST /api/v1/containerhub/drone/upload/ │
│ │
│ 1. Валідація payload │
│ 2. Matching: container_number → Container record │
│ 3. Mapping: GPS coords → YardZone + position │
│ 4. Створення InventoryLine records │
│ 5. Порівняння з обліковими даними (expected vs AI) │
│ 6. Генерація discrepancy report │
└─────────────────────────────────────────────────────┘
6.2 DroneFlightResult (Результат обльоту)¶
Base: TenantAwareModel
| Поле | Тип | Опис |
|---|---|---|
inventory |
FK → YardInventory | Інвентаризація |
flight_id |
CharField, unique | ID обльоту |
containerhub |
FK → ContainerHub | Термінал |
started_at |
DateTimeField | Початок обльоту |
ended_at |
DateTimeField (nullable) | Кінець обльоту |
total_detections |
IntegerField, default=0 | Кількість детекцій |
matched_count |
IntegerField, default=0 | Знайдено в обліку |
unmatched_count |
IntegerField, default=0 | Не знайдено |
avg_confidence |
DecimalField (nullable) | Середня впевненість |
flight_path_json |
JSONField (nullable) | Маршрут обльоту (GeoJSON) |
processing_status |
Enum: uploading, processing, completed, failed | Статус обробки |
6.3 DroneDetection (Окрема детекція)¶
Base: TenantAwareModel
| Поле | Тип | Опис |
|---|---|---|
flight_result |
FK → DroneFlightResult | Обліт |
container_number_raw |
CharField | OCR-результат (як розпізнав) |
container_number_normalized |
CharField (nullable) | Нормалізований номер |
container |
FK → Container (nullable) | Знайдений запис (nullable = не знайдено) |
confidence |
DecimalField | Впевненість (0.0–1.0) |
latitude |
DecimalField | GPS широта |
longitude |
DecimalField | GPS довгота |
estimated_zone |
FK → YardZone (nullable) | Зона (обчислена з GPS) |
estimated_position |
CharField (nullable) | Позиція (обчислена) |
image_url |
URLField (nullable) | Кадр з камери |
bbox_json |
JSONField (nullable) | Bounding box [x, y, w, h] |
match_status |
Enum: matched, new_container, not_found, low_confidence | Результат матчингу |
6.4 API Endpoint — Drone Upload¶
POST /api/v1/containerhub/drone/upload/
{
"flight_id": "FLIGHT-2026-02-28-001",
"containerhub_id": 1,
"started_at": "2026-02-28T08:00:00+06:00",
"ended_at": "2026-02-28T08:45:00+06:00",
"detections": [
{
"container_number": "MAEU1234567",
"confidence": 0.97,
"latitude": 43.2375,
"longitude": 76.9457,
"image_url": "https://storage.example.com/flights/001/frame_0042.jpg",
"bbox": [120, 340, 480, 210]
},
...
]
}
Response:
{
"flight_result_id": 42,
"total_detections": 2487,
"matched": 2451,
"unmatched": 36,
"avg_confidence": 0.94,
"inventory_id": 15,
"status": "processing"
}
7. Процеси (UI компоненти)¶
7.1 YardMap — 3D Ізометрична карта двору¶
Тип: ItemType.PROCESS, processCode: yardMap
Проблема: Контейнери стоять один на одному (до 5 тірів). 2D-карта не відображає висоту штабеля і не показує, який контейнер "заблокований" зверху.
Рішення — два режими візуалізації:
Режим 1: Plan View (вид зверху)¶
- Grid-карта зон терміналу: row × bay
- Кожна клітинка показує верхній контейнер + badge з кількістю тірів ("×3")
- Клік на клітинку → Stack Detail Panel (бічна панель)
Режим 2: Isometric 3D View¶
- Ізометрична проекція (CSS transforms або Canvas/WebGL)
- Контейнери — кольорові паралелепіпеди, складені один на одного
- Видно повну висоту штабеля
- Hover → tooltip з номером контейнера та departure date
- Zoom / pan / rotate контроли
Stack Detail Panel (бічна панель при кліку на bay)¶
Bay A-05-03 [×4 containers]
┌─────────────────────────────────┐
│ Tier 4 (top): MAEU1234567 │ ← departure: Mar 2 (first!)
│ Tier 3: HLCU8901234 │ ← departure: Mar 5
│ Tier 2: CMDU5678901 │ ← departure: Mar 3 ⚠️ blocked!
│ Tier 1 (bot): TCKU3455621 │ ← departure: Mar 10
└─────────────────────────────────┘
⚠️ CMDU5678901 departs Mar 3 but blocked by 2 containers above!
[Generate Rehandling Plan]
Колірне кодування:¶
- Зелений — departure > 7 днів (не терміновий)
- Жовтий — departure 3-7 днів
- Оранжевий — departure 1-3 дні
- Червоний — departure сьогодні / прострочений
- Синій — порожній контейнер
- Фіолетовий — reefer (з індикатором температури)
- Штрихований — dangerous goods (IMO)
- Сірий з іконкою — damaged / needs repair
Стекування-індикатори:¶
- Зелена рамка навколо стека — стек "здоровий" (ранні departure зверху)
- Червона рамка — стек "хворий" (потрібен restack: ранній departure заблокований)
- Badge "⚠️ N" — кількість заблокованих контейнерів у стеку
Фільтри:¶
- За зоною, типом контейнера, shipping line, днями на терміналі
- "Show problems only" — показати лише стеки з блокованими контейнерами
- За departure window (сьогодні, завтра, цей тиждень)
Дії:¶
- Drag-and-drop контейнера між позиціями → створює ContainerMovement
- Кнопка "Optimize Zone" → запуск алгоритму pre-marshalling
- Кнопка "Generate Rehandling Plan" → для конкретного заблокованого контейнера
7.2 GateControl — Панель контролю воріт¶
Тип: ItemType.PROCESS, processCode: gateControl
- Два стовпці: Gate-In (зліва) | Gate-Out (справа)
- Зверху: черга бронювань на сьогодні (ContainerBooking зі статусом approved)
- Кнопка "Швидке оформлення" → форма GateTransaction
- Live-лічильники: вʼїхало / виїхало за сьогодні
- Сканер: поле для введення/сканування номера контейнера
7.3 InventoryDashboard — Дашборд інвентаризації¶
Тип: ItemType.PROCESS, processCode: inventoryDashboard
- Кнопка "Нова інвентаризація" → вибір типу (повна / зонна / spot-check)
- Кнопка "Запустити дрон-обліт" → інтеграція з drone API
- Активна інвентаризація: progress bar (очікувано vs перевірено)
- Таблиця відхилень: missing / unexpected / misplaced
- AI detection gallery: кадри з дрону з bounding boxes
- Кнопка "Прийняти результати" → проведення інвентаризації
7.4 DemurrageTracker — Трекер демереджу¶
Тип: ItemType.PROCESS, processCode: demurrageTracker
- Три зони:
- В зоні ризику — free days закінчуються через 1-3 дні
- Нараховується — chargeable days > 0, контейнер ще на терміналі
- Завершено — gate-out виконано, фінальна сума зафіксована
- Кольорове кодування за сумою нарахувань
- Фільтр по shipping line, клієнту, періоду
- Кнопка "Перерахувати" — масовий перерахунок для всіх активних
- Export в Excel/CSV
8. Stacking Optimization Engine — Оптимізація штабелювання¶
8.1 Три задачі оптимізації¶
┌─────────────────────────────────────────────────────────────────────┐
│ STACKING OPTIMIZATION │
│ │
│ 1. PLACEMENT 2. RETRIEVAL 3. PRE-MARSHALLING │
│ (куди поставити?) (як дістати?) (переставити заздалегідь) │
│ │
│ Потяг прибуває з 80 Потрібен контейнер Ніч / простій: │
│ контейнерами. Куди CMDU5678901, а він переставити так, │
│ поставити кожен, щоб під 3 іншими. щоб завтра все │
│ ті що їдуть першими Мінімум переміщень? їхало без restacks │
│ були зверху? │
└─────────────────────────────────────────────────────────────────────┘
8.2 Задача 1 — Placement Optimization (Оптимальне розміщення)¶
Коли: Gate-in або розвантаження потяга/судна.
Вхідні дані: - Контейнер: тип, розмір, shipping line, planned departure date, is_dangerous, is_reefer - Стан двору: поточний стан всіх зон/позицій/стеків (з ContainerLedger) - Обмеження: max_tiers зони, reefer → тільки зони з електрикою, dangerous → окрема зона
Алгоритм — Greedy з Look-Ahead (практичний, <1 сек):
def find_optimal_slot(container, yard_state, zones):
"""
Знайти найкращу позицію для нового контейнера.
Правило: контейнер з РАНІШИМ departure НЕ МОЖЕ бути ПІД контейнером з ПІЗНІШИМ departure.
"""
candidates = []
for zone in zones:
if not zone_accepts(zone, container): # reefer, dangerous, size checks
continue
for row, bay in zone.available_slots():
stack = yard_state.get_stack(zone, row, bay)
if stack.height >= zone.max_tiers:
continue # стек повний
# Scoring:
score = 0
# 1. Пріоритет однорідності: контейнери з тим же departure window
if stack.height > 0:
top_departure = stack.top().planned_departure
if same_window(top_departure, container.planned_departure):
score += 100 # ідеально — той же тиждень
elif container.planned_departure >= top_departure:
score += 50 # OK — новий їде пізніше (буде знизу логічно)
else:
score -= 200 # ПОГАНО — новий їде раніше але буде зверху заблокований
# 2. Група по shipping line (легше відвантажувати разом)
if stack.height > 0 and stack.top().shipping_line == container.shipping_line:
score += 30
# 3. Менша висота стека — менше ризику
score -= stack.height * 10
# 4. Близькість до gate-out (для контейнерів що їдуть скоро)
if container.days_to_departure < 3:
score += zone.proximity_to_gate * 20
candidates.append((zone, row, bay, score))
candidates.sort(key=lambda x: -x[3])
return candidates[0] if candidates else None
Результат: Для кожного контейнера gate-in отримуємо рекомендовану позицію. Оператор бачить на YardMap підсвічену клітинку "РЕКОМЕНДОВАНО" і може прийняти або обрати іншу.
8.3 Задача 2 — Retrieval (Rehandling Plan)¶
Коли: Потрібно дістати конкретний контейнер для gate-out, а він заблокований.
Вхідні дані: - Цільовий контейнер і його позиція (zone, row, bay, tier) - Усі контейнери зверху нього (blocking containers) - Вільні позиції в зоні/сусідніх зонах - Departure dates blocking контейнерів (враховуємо при переміщенні)
Алгоритм — Beam Search з мінімізацією:
def generate_rehandling_plan(target_container, yard_state):
"""
Генерує послідовність переміщень для витягнення target_container
з мінімальною кількістю операцій.
Block Relocation Problem (BRP) — NP-hard, але для стеків висотою ≤5
точне рішення знаходиться за <100 мс (beam search, depth ≤5).
"""
stack = yard_state.get_stack_for(target_container)
blocking = stack.containers_above(target_container)
if not blocking:
return [] # Контейнер зверху — просто забрати
plan = []
for blocker in reversed(blocking): # зверху вниз
# Знайти найкращу позицію для blocker (щоб НЕ створити нових блокувань)
dest = find_optimal_slot(blocker, yard_state, prefer_low_stacks=True)
plan.append({
'step': len(plan) + 1,
'action': 'relocate',
'container': blocker.container_number,
'from': f'{stack.zone.code}-{stack.row:02d}-{stack.bay:02d}-{blocker.tier}',
'to': f'{dest.zone.code}-{dest.row:02d}-{dest.bay:02d}-{dest.tier}',
'reason': f'Unblock {target_container.container_number}',
})
yard_state.apply_move(blocker, dest) # оновити стан для наступного кроку
plan.append({
'step': len(plan) + 1,
'action': 'retrieve',
'container': target_container.container_number,
'from': f'{stack.zone.code}-{stack.row:02d}-{stack.bay:02d}-{target_container.tier}',
'to': 'GATE-OUT',
})
return plan
UI — Rehandling Plan Viewer:
🔧 Rehandling Plan for CMDU5678901 (Gate-Out)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Step 1: Move MAEU1234567 A-05-03-4 → A-05-07-2 (unblock)
Step 2: Move HLCU8901234 A-05-03-3 → A-05-07-3 (unblock)
Step 3: Retrieve CMDU5678901 A-05-03-2 → GATE-OUT ✅
Total moves: 3 (2 relocations + 1 retrieval)
Estimated time: ~12 min (4 min per move)
Equipment: Reach Stacker RS-01
[Execute Plan] [Modify] [Cancel]
Кнопка "Execute Plan" → автоматично створює N документів ContainerMovement + 1 GateTransaction.
8.4 Задача 3 — Pre-Marshalling (Нічна оптимізація)¶
Коли: Простій (ніч, вихідні). Завтра очікується багато gate-out — переставити контейнери заздалегідь.
Вхідні дані: - Усі контейнери на терміналі з planned_departure - Gate-out розклад на завтра (ContainerBooking зі статусом approved) - Поточний стан двору
Алгоритм — Simulated Annealing (офлайн, хвилини):
def pre_marshalling_plan(containerhub, target_date, yard_state):
"""
Перебудувати стеки так, щоб контейнери з departure ≤ target_date
були на верху своїх стеків. Мінімізуємо кількість переміщень.
Результат: 60-80% зниження кількості restacks при завтрашніх gate-out.
"""
# 1. Визначити "хворі" стеки (departure order порушений)
sick_stacks = find_sick_stacks(yard_state, target_date)
# 2. Для кожного хворого стека — цільовий стан (відсортований за departure)
target_config = compute_target_configuration(sick_stacks)
# 3. Simulated Annealing — знайти мінімальну послідовність переміщень
# current → target з урахуванням обмежень (max_tiers, зони, тощо)
plan = simulated_annealing_solve(
current=yard_state,
target=target_config,
max_iterations=10000,
cooling_rate=0.995,
)
return plan # List of ContainerMovement records
UI — Pre-Marshalling Dashboard (в InventoryDashboard або окремий process):
🌙 Pre-Marshalling Plan — Night Shift
ContainerHub: Almaty Dry Port
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Tomorrow's gate-outs: 47 containers
Blocked containers: 12 (need restacking)
Estimated restacks without optimization: 31 moves
After pre-marshalling: 8 moves (74% reduction ✅)
Pre-marshalling moves needed: 18 moves (~72 min)
Net savings: 13 moves (~52 min of crane time tomorrow)
Plan:
1. MAEU1234567 A-05-03-4 → A-05-07-2
2. HLCU8901234 A-05-03-3 → A-05-08-1
... (18 moves total)
[Execute Plan] [Schedule for Night Shift] [Cancel]
8.5 Train Unloading Optimization (Специфіка ЖД)¶
Сценарій: Прибуває потяг з 80 контейнерами на 40 платформах. Їх потрібно розвантажити краном у двір. Порядок розвантаження фіксований (від голови до хвоста потяга або навпаки). Але позицію в дворі ми обираємо.
Додатковий документ:
TrainArrival (Прибуття потяга) — extends TransactionModel¶
| Поле | Тип | Опис |
|---|---|---|
containerhub |
FK → ContainerHub | Термінал |
train_number |
CharField | Номер потяга |
origin |
FK → fleet.LocationPoint | Звідки |
arrival_time |
DateTimeField | Час прибуття |
platform_count |
IntegerField | Кількість платформ |
container_count |
IntegerField | Кількість контейнерів |
unloading_order |
Enum: head_first, tail_first | Порядок розвантаження |
Subtables:
- TrainManifest (inline) — список контейнерів на потязі
TrainManifest (Маніфест потяга)¶
| Поле | Тип | Опис |
|---|---|---|
train_arrival |
FK → TrainArrival | Потяг |
sequence |
IntegerField | Порядок на потязі (від голови) |
platform_number |
CharField | Номер платформи |
container |
FK → Container (nullable) | Контейнер (якщо вже в системі) |
container_number |
CharField | Номер контейнера |
container_type |
FK → logistic.ContainerType | Тип |
shipping_line |
FK → ShippingLine (nullable) | Лінія |
planned_departure |
DateField (nullable) | Планова дата відправки з терміналу |
consignee |
FK → essentials.Client (nullable) | Одержувач |
is_loaded |
BooleanField | Навантажений |
is_dangerous |
BooleanField | Небезпечний |
weight |
DecimalField (nullable) | Вага |
assigned_zone |
FK → YardZone (nullable) | Рекомендована зона (алгоритмом) |
assigned_position |
CharField (nullable) | Рекомендована позиція (алгоритмом) |
Алгоритм розвантаження потяга:
def optimize_train_unloading(train_arrival, yard_state):
"""
Для кожного контейнера в маніфесті (в порядку розвантаження)
визначити оптимальну позицію в дворі.
Ключове правило: контейнери що їдуть РАНІШЕ — ставити ВИЩЕ в стеку.
Оскільки порядок розвантаження фіксований (потяг), алгоритм працює
жадібно в порядку розвантаження.
"""
manifest = train_arrival.manifest.order_by('sequence')
# Сортуємо контейнери за departure date (для grouping)
departure_groups = group_by_departure_window(manifest)
placement_plan = []
for container_info in manifest:
slot = find_optimal_slot(
container=container_info,
yard_state=yard_state,
# Додатковий пріоритет: групувати контейнери з того ж потяга
# з тим же departure window в сусідні позиції
prefer_group=departure_groups[container_info.departure_window],
)
placement_plan.append({
'sequence': container_info.sequence,
'container': container_info.container_number,
'platform': container_info.platform_number,
'assigned_zone': slot.zone.code,
'assigned_position': f'{slot.row:02d}-{slot.bay:02d}-{slot.tier}',
'departure': container_info.planned_departure,
})
yard_state.apply_placement(container_info, slot)
return placement_plan
UI — Train Unloading Planner:
🚂 Train Unloading Plan — Train #4521 from Khorgos
Containers: 80 | Platforms: 40 | Unload: Head First
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# Platform Container Departure → Zone Position
1 PL-001 MAEU1234567 Mar 2 → A 01-05-3
2 PL-001 HLCU8901234 Mar 2 → A 01-05-4 (same stack!)
3 PL-002 CMDU5678901 Mar 10 → B 03-02-1
4 PL-002 TCKU3455621 Mar 5 → A 02-01-2
...
Stacking quality score: 94/100 ✅
Expected restacks at gate-out: 3 (of 80)
Estimated unloading time: 4h 20min
[Print Plan for Crane Operator] [Execute] [Modify]
8.6 Stacking KPIs (Метрики якості штабелювання)¶
| KPI | Формула | Ціль |
|---|---|---|
| Rehandle Ratio | total_moves / productive_moves | < 1.2 (ідеал: 1.0) |
| Sick Stack % | stacks_with_wrong_order / total_stacks × 100% | < 10% |
| Average Stack Height | total_tiers_used / occupied_bays | < 3.0 (для max_tiers=5) |
| Placement Acceptance Rate | accepted_suggestions / total_suggestions × 100% | > 80% |
| Pre-Marshall Efficiency | restacks_saved / moves_spent × 100% | > 150% |
8.7 Backend API — Stacking Optimization¶
POST /api/v1/containerhub/stacking/suggest-placement/
Body: { container_id, containerhub_id }
Response: { zone, row, bay, tier, score, alternatives[] }
POST /api/v1/containerhub/stacking/rehandling-plan/
Body: { container_id }
Response: { steps[], total_moves, estimated_minutes, equipment }
POST /api/v1/containerhub/stacking/pre-marshalling/
Body: { containerhub_id, target_date, zones[]? }
Response: { moves[], sick_stacks_before, sick_stacks_after, savings }
POST /api/v1/containerhub/stacking/train-unloading-plan/
Body: { train_arrival_id }
Response: { placements[], quality_score, expected_restacks }
9. Railway Operations — Специфіка залізниці (Dry Port)¶
9.1 Gauge Change (Хоргос)¶
Хоргос — точка зміни колії (1435mm Китай → 1520mm Казахстан/СНД). Контейнери перевантажуються з одних платформ на інші. Для dry port у Алмати залізничний вʼїзд працює на широкій колії 1520mm.
9.2 Процес розвантаження ЖД¶
Потяг прибуває на під'їзну колію терміналу
│
▼
┌─────────────────────────────────┐
│ 1. Реєстрація TrainArrival │ ← Маніфест від ЖД оператора
│ (ручне введення або EDI) │ або ручне введення
└──────────┬──────────────────────┘
▼
┌─────────────────────────────────┐
│ 2. Алгоритм Placement │ ← Автоматичне призначення
│ Optimization для кожного │ позицій у дворі
│ контейнера в маніфесті │
└──────────┬──────────────────────┘
▼
┌─────────────────────────────────┐
│ 3. Друк плану для кранівника │ ← Порядок: платформа → позиція
│ (або відображення на планшеті│ Crane operator бачить куди
│ кранівника) │ ставити кожен контейнер
└──────────┬──────────────────────┘
▼
┌─────────────────────────────────┐
│ 4. Розвантаження краном (RTG / │ ← Кожне зняття = GateTransaction
│ Reach Stacker / Gantry) │ type=gate_in, transport_mode=rail
└──────────┬──────────────────────┘
▼
┌─────────────────────────────────┐
│ 5. Підтвердження кожної │ ← Mobile app або сканер
│ позиції (scan → confirm) │
└─────────────────────────────────┘
9.3 Особливості ЖД vs Авто¶
| Аспект | Автомобіль (gate-in) | Залізниця (train unloading) |
|---|---|---|
| Одиниця | 1 контейнер за раз | 40-80 контейнерів пакетом |
| Порядок | Довільний (черга) | Фіксований (порядок платформ) |
| Обладнання | Reach stacker | RTG / Gantry crane |
| Швидкість | ~5 хв/контейнер | ~3 хв/контейнер (кран швидший) |
| Оптимізація | Placement per container | Batch placement для всього потяга |
| Документ | GateTransaction | TrainArrival → GateTransaction[] |
10. Мобільний додаток — Розширення¶
8.1 Нові екрани для yard-менеджера¶
| Екран | Опис |
|---|---|
ContainerScanScreen |
Камера для сканування номера контейнера (OCR) + ручне введення |
GateFormScreen |
Швидка форма gate-in / gate-out: номер, стан, пломба, фото, зона |
InventoryWalkScreen |
Режим обходу: зона → рядки → підтвердження наявності кожного контейнера |
ZoneBrowserScreen |
Список зон + вміст кожної зони (контейнери) |
ContainerDetailScreen |
Картка контейнера: всі дані + історія переміщень |
8.2 WatermelonDB — Нові таблиці¶
// containers — sync pull only (reference + current state)
containers: {
columns: [
{ name: 'container_number', type: 'string' },
{ name: 'container_type_name', type: 'string' },
{ name: 'owner_name', type: 'string' },
{ name: 'condition', type: 'string' },
{ name: 'current_zone_code', type: 'string' },
{ name: 'current_position', type: 'string' },
{ name: 'is_loaded', type: 'boolean' },
{ name: 'is_reefer', type: 'boolean' },
{ name: 'last_gate_in', type: 'number' }, // timestamp
]
},
// gate_transactions — sync pull + push
gate_transactions: {
columns: [
{ name: 'number', type: 'string' },
{ name: 'transaction_type', type: 'string' },
{ name: 'container_number', type: 'string' },
{ name: 'container_condition', type: 'string' },
{ name: 'seal_number', type: 'string' },
{ name: 'seal_intact', type: 'boolean' },
{ name: 'is_loaded', type: 'boolean' },
{ name: 'yard_zone_code', type: 'string' },
{ name: 'yard_position', type: 'string' },
{ name: 'gate_timestamp', type: 'number' },
{ name: 'status', type: 'string' },
{ name: 'photo_uri', type: 'string' }, // local URI
]
},
// inventory_tasks — sync pull + push (assigned lines for current user)
inventory_tasks: {
columns: [
{ name: 'inventory_server_id', type: 'string' },
{ name: 'container_number', type: 'string' },
{ name: 'expected_zone_code', type: 'string' },
{ name: 'expected_position', type: 'string' },
{ name: 'line_status', type: 'string' }, // pending → confirmed/missing/misplaced
{ name: 'actual_zone_code', type: 'string' },
{ name: 'actual_position', type: 'string' },
{ name: 'notes', type: 'string' },
{ name: 'photo_uri', type: 'string' },
]
}
9. Backend URL Structure¶
/api/v1/containerhub/
# Master Data (CRUD через UniversalViewSet або router)
containers/ # Container
containers/<pk>/inspections/ # ContainerInspection (nested)
containers/<pk>/seals/ # ContainerSeal (nested)
containerhubs/ # ContainerHub
containerhubs/<pk>/zones/ # YardZone (nested)
shipping-lines/ # ShippingLine
container-tariffs/ # ContainerTariff
# Transactions
gate-transactions/ # GateTransaction
gate-transactions/<pk>/photos/ # GatePhoto (nested)
container-movements/ # ContainerMovement
container-bookings/ # ContainerBooking
yard-inventories/ # YardInventory
yard-inventories/<pk>/lines/ # InventoryLine (nested)
yard-inventories/<pk>/drone-results/ # DroneFlightResult (nested)
demurrage-calculations/ # DemurrageCalculation
# Special endpoints
drone/upload/ # POST — drone batch upload
drone/detections/<flight_id>/ # GET — detections by flight
yard-map/<containerhub_id>/ # GET — aggregated yard data for map
demurrage/recalculate/ # POST — mass recalculation
gate/today-schedule/<containerhub_id>/ # GET — bookings for today
# Stacking Optimization
stacking/suggest-placement/ # POST — optimal slot for new container
stacking/rehandling-plan/ # POST — how to retrieve blocked container
stacking/pre-marshalling/ # POST — night shift optimization plan
stacking/train-unloading-plan/ # POST — batch placement for train
# Railway
train-arrivals/ # TrainArrival CRUD
train-arrivals/<pk>/manifest/ # TrainManifest (nested)
10. Frontend Config Structure¶
containerhub (Section)
├── containerhubMasterData (Group)
│ ├── container_registry (Subgroup)
│ │ ├── containers (MASTERDATA) — Контейнери
│ │ ├── containerTypes (MASTERDATA) → logistic (reuse)
│ │ └── shippingLines (MASTERDATA) — Судноплавні лінії
│ ├── containerhub_infrastructure (Subgroup)
│ │ ├── containerhubs (MASTERDATA) — Термінали
│ │ └── containerTariffs (MASTERDATA) — Тарифи
│ └── (reuse logistic locations, cargo types, freight contractors)
│
├── containerhubTransactions (Group)
│ ├── gate_operations (Subgroup)
│ │ ├── gateTransactions (TRANSACTIONDATA) — Gate-In / Gate-Out
│ │ └── containerBookings (TRANSACTIONDATA) — Бронювання слотів
│ ├── yard_operations (Subgroup)
│ │ ├── containerMovements (TRANSACTIONDATA) — Переміщення
│ │ └── yardInventories (TRANSACTIONDATA) — Інвентаризація
│ └── financial (Subgroup)
│ └── demurrageCalculations (TRANSACTIONDATA) — Демередж / Детеншен
│
├── containerhubProcesses (Group)
│ ├── yard_operations (Subgroup)
│ │ ├── yardMap (PROCESS) — 3D Карта двору (ізометрія + plan view)
│ │ ├── stackingOptimizer (PROCESS) — Оптимізація штабелювання
│ │ └── trainUnloading (PROCESS) — Планувальник розвантаження потяга
│ ├── gate_operations (Subgroup)
│ │ └── gateControl (PROCESS) — Контроль воріт
│ ├── inventory (Subgroup)
│ │ └── inventoryDashboard (PROCESS) — Дашборд інвентаризації
│ └── financial (Subgroup)
│ └── demurrageTracker (PROCESS) — Трекер демереджу
│
└── containerhubAnalytics (Group)
└── reports (Subgroup)
├── containerhubOccupancy (REPORT) — Заповненість терміналу
├── containerTurnover (REPORT) — Оборотність контейнерів
├── demurrageReport (REPORT) — Звіт по демереджу
└── inventoryAccuracy (REPORT) — Точність обліку
11. Демередж vs Детеншен — Бізнес-логіка¶
ДЕМЕРЕДЖ (Demurrage)
= Плата shipping line за зберігання контейнера в порту/терміналі
понад вільний період (free days).
Нараховується ПОКИ контейнер НА ТЕРМІНАЛІ.
Формула:
chargeable_days = max(0, (gate_out_date - gate_in_date) - free_days_import)
demurrage = chargeable_days × rate_per_day
ДЕТЕНШЕН (Detention)
= Плата shipping line за використання контейнера ПОЗА ТЕРМІНАЛОМ
(від gate-out до повернення порожнього контейнера).
Нараховується ПОКИ контейнер ПОЗА ТЕРМІНАЛОМ.
Формула:
chargeable_days = max(0, (return_date - gate_out_date) - detention_free_days)
detention = chargeable_days × detention_rate_per_day
Таймлайн (Import):
┌─────────────┬──────────────┬──────────────┬──────────────┐
│ Vessel │ Free Days │ DEMURRAGE │ DETENTION │
│ Discharge │ (in port) │ (in port) │ (outside) │
│ │ │ │ │
├─── Gate-In ─┤ │ │ │
│ ├── Free ──────┤ │ │
│ │ ├── Paid ──────┤ │
│ │ │ ├── Gate-Out ──┤
│ │ │ │ Free/Paid │
│ │ │ │ → Return │
└─────────────┴──────────────┴──────────────┴──────────────┘
12. Рішення по уточнюючих питаннях¶
| # | Питання | Рішення |
|---|---|---|
| 1 | EDI / COPARN | ✅ Так — Phase 7 |
| 2 | Рефрижераторний моніторинг | ✅ Так — Phase 6 |
| 3 | Weighbridge / VGM | ✅ Так — Phase 6 |
| 4 | Митний контроль | ✅ Так — Phase 6 |
| 5 | Container Pooling | ✅ Так — Phase 6 |
| 6 | Залізничний вʼїзд | ✅ Так — Phase 2 (TrainArrival + unloading optimizer) |
| 7 | Мульти-термінал | ✅ Так — один тенант = N терміналів (Алмати + Хоргос + Актау) |
| 8 | Scope | Поетапно — Phase 1 MVP → Phase 7 Advanced |
13. Дорожня карта імплементації¶
Phase 1 — Core (MVP)¶
- [ ] Django app
containerhub+ models: Container, ContainerHub, YardZone, ShippingLine, ContainerTariff - [ ] ContainerInspection, ContainerSeal (subtables)
- [ ] GateTransaction (gate-in / gate-out) з проведенням
- [ ] ContainerMovement
- [ ] ContainerLedger register
- [ ] EntityRegistry registration + serializers + views
- [ ] Frontend config: containerhub.ts section
- [ ] Базові CRUD для всіх довідників і документів
- [ ] App Store entry: appContainerHub
Phase 2 — Yard & Railway¶
- [ ] ContainerBooking (truck appointment)
- [ ] TrainArrival + TrainManifest (ЖД operations)
- [ ] YardMap process — Plan View (2D, grid з tier badges)
- [ ] Stack Detail Panel (перегляд вмісту стека по тірах)
- [ ] GateControl process panel
- [ ] StorageChargeLedger register
Phase 3 — Stacking Optimization¶
- [ ] Stacking engine: Placement Optimization (greedy з look-ahead)
- [ ] Stacking engine: Rehandling Plan (beam search, BRP solver)
- [ ] Stacking engine: Pre-Marshalling (simulated annealing)
- [ ] Stacking engine: Train Unloading Optimizer (batch placement)
- [ ] YardMap — Isometric 3D View (Canvas/WebGL)
- [ ] Stacking Optimizer process UI
- [ ] Train Unloading Planner process UI
- [ ] Stacking KPIs dashboard
Phase 4 — Inventory¶
- [ ] YardInventory + InventoryLine
- [ ] InventoryDashboard process
- [ ] Mobile app: InventoryWalkScreen, ContainerScanScreen, GateFormScreen
- [ ] Mobile app: ZoneBrowserScreen, ContainerDetailScreen
- [ ] Mobile sync: containers, gate_transactions, inventory_tasks
Phase 5 — AI Drone¶
- [ ] DroneFlightResult + DroneDetection models
- [ ] Drone upload API endpoint
- [ ] GPS → Zone mapping algorithm
- [ ] Container number OCR matching pipeline (YOLO + TrOCR)
- [ ] AI detection gallery UI
- [ ] Drone + Manual combined inventory workflow
Phase 6 — Financial & Advanced¶
- [ ] DemurrageCalculation with auto-compute
- [ ] DemurrageTracker process
- [ ] Detention tracking (gate-out → container return cycle)
- [ ] Integration with essentials.Invoice (автоматичне виставлення рахунків)
- [ ] Reports: ContainerHub Occupancy, Container Turnover, Demurrage Report, Inventory Accuracy
- [ ] Container condition/inspection workflow
- [ ] Seal management (full lifecycle)
- [ ] Dangerous goods (IMO/UN classification, зони)
- [ ] Reefer monitoring (IoT датчики, temperature alerts)
- [ ] Container Pooling (own/lease/line cost analysis)
- [ ] Weighbridge / VGM integration
- [ ] Customs hold/release workflow
Phase 7 — Integrations¶
- [ ] EDI COPARN (container release/booking from shipping lines)
- [ ] EDI BAPLIE (vessel bay plan)
- [ ] Kazakhstan customs EDI (if applicable)
- [ ] Shipping line portals integration
- [ ] ЖД оператор EDI (вагонний лист)
14. Аналіз ринку та актуальності¶
14.1 Ринок TOS (ContainerHub Operating System)¶
| Показник | Значення |
|---|---|
| Глобальний ринок TOS (2024) | $1.2–3.2 млрд |
| Прогноз (2033) | $2.0–7.4 млрд |
| CAGR | 6.5–9.7% |
Enterprise гравці: Navis N4 (350+ терміналів), TOPS Expert, CyberLogitec OPUS, Tideworks Mainsail — впровадження коштує мільйони $ + роки інтеграції.
Mid-range: Navis Octopi (SaaS для < 100K TEU/рік), RBS TOS+, ContPark — операційний workflow, але слабка оптимізація стекування.
Ринкова ніша: Доступного TOS з вбудованою оптимізацією штабелювання + AI інвентаризацією для ICD/dry port на 50K–200K TEU/рік в emerging markets — практично не існує.
14.2 Казахстан — Середній Коридор (Middle Corridor)¶
| Метрика | 2023 | 2024 | 2025 |
|---|---|---|---|
| Фрахт через Middle Corridor | ~2 млн тон | ~4.5 млн тон (+70%) | 6+ млн тон |
| Контейнерний транзит (Казахстан) | ~13K TEU | ~50.5K TEU | 58K+ TEU (+15%) |
| Китай-Європа-Китай TEU | ~1.1K | ~27.6K (×25!) | — |
| Контейнерні потяги через Актау | ~10 | 300+ (×30!) | 600 (план) |
| Хоргос throughput | — | 365K TEU | 372K TEU |
Ключові факти: - Хоргос Gateway — найбільший dry port у світі, 372K TEU/рік (ємність 500K) - Порт Актау — новий контейнерний хаб за $38 млн, ємність зростає з 92K до 300K контейнерів/рік - Після 2022 Середній Коридор став стратегічним маршрутом Китай-Європа в обхід РФ - Цифровізація Хоргосу скоротила час обробки контейнера з 5 годин до 1 години
14.3 Стекування — чому це критично¶
| Показник | Без оптимізації | З оптимізацією |
|---|---|---|
| Rehandle Ratio | 1.3–1.8 | < 1.1 |
| % непродуктивних рухів крана | 20–40% | < 10% |
| Вартість зайвого переміщення | $50–150 / рух | — |
| Для 2500 контейнерів/міс | тисячі зайвих рухів | сотні (економія ~$100K+/рік) |
| Час на gate-out | 15–25 хв (avg) | 5–8 хв |
Pre-marshalling знижує кількість restacks при gate-out на 60–80%.
14.4 Drone / AI Inventory — стан технології¶
| Технологія | Зрілість | Точність |
|---|---|---|
| Warehouse drone inventory (indoor) | ✅ Production | 99.9% (Gather AI) |
| Container gate OCR (fixed camera) | ✅ Production | 98–99.5% (Visy, commercial) |
| YOLO + TrOCR container OCR (research) | ✅ Ready | 99.1–99.4% |
| Container yard drone inventory | ⚠️ Emerging | ~95% (estimated) |
| Tesseract on containers | ❌ Unusable | 0.4% |
Висновок: YOLO v8 для детекції + TrOCR для OCR номерів — оптимальний стек. Jetson Orin здатний обробляти в реальному часі. Повна автономна інвентаризація дроном — emerging, але технічно реалізовуєма вже зараз.
14.5 Конкурентна перевага модуля ContainerHub¶
ESWF ContainerHub vs Enterprise TOS vs Mid-Range SaaS
Navis N4 Octopi ESWF ContainerHub
Ціна впровадження $$$$$ $$$ $$
Час впровадження 1-2 роки 3-6 міс 1-3 міс
Stacking optimization ✅ ❌ ✅
AI drone inventory ❌ ❌ ✅
Demurrage auto-calc ✅ ✅ ✅
Mobile app (offline) ⚠️ limited ⚠️ ✅ (WatermelonDB)
Railway operations ✅ ⚠️ ✅
Multi-containerhub ✅ ✅ ✅
Інтеграція з DOP ❌ окремо ❌ окремо ✅ native (same DB)
Open source ❌ ❌ ✅ (framework)
Emerging markets focus ❌ ⚠️ ✅