285 lines
11 KiB
Python
285 lines
11 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
ais_oracle.py — Децентрализованный оракул морских данных Montana
|
|||
|
|
|
|||
|
|
Книга Монтана, Глава 04:
|
|||
|
|
> "Они построили трубу. Мы пускаем по ней время."
|
|||
|
|
> "MarineTraffic + Montana = время судна конвертируется в Ɉ автоматически."
|
|||
|
|
> "AIS-сигналы транслируются открыто на VHF-частотах.
|
|||
|
|
> Любой с AIS-приёмником может их собирать."
|
|||
|
|
|
|||
|
|
AIS (Automatic Identification System) — международный стандарт.
|
|||
|
|
Суда транслируют свою позицию, курс, скорость на VHF-частотах.
|
|||
|
|
Инфраструктура уже построена — миллиарды $ инвестиций.
|
|||
|
|
|
|||
|
|
Montana использует эту инфраструктуру как оракул времени:
|
|||
|
|
время судна в порту → Ɉ
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from dataclasses import dataclass, field
|
|||
|
|
from datetime import datetime, timezone, timedelta
|
|||
|
|
from decimal import Decimal
|
|||
|
|
from typing import Dict, List, Optional, Tuple
|
|||
|
|
from enum import Enum
|
|||
|
|
import hashlib
|
|||
|
|
|
|||
|
|
|
|||
|
|
class VesselStatus(Enum):
|
|||
|
|
"""Статус судна по AIS."""
|
|||
|
|
UNDERWAY = 0 # В пути
|
|||
|
|
AT_ANCHOR = 1 # На якоре
|
|||
|
|
NOT_UNDER_COMMAND = 2 # Без управления
|
|||
|
|
MOORED = 5 # Пришвартовано
|
|||
|
|
AGROUND = 6 # На мели
|
|||
|
|
|
|||
|
|
|
|||
|
|
class OracleSource(Enum):
|
|||
|
|
"""Источники данных AIS."""
|
|||
|
|
MARINE_TRAFFIC = "marinetraffic.com"
|
|||
|
|
VESSEL_FINDER = "vesselfinder.com"
|
|||
|
|
FLEET_MON = "fleetmon.com"
|
|||
|
|
SPIRE = "spire.com"
|
|||
|
|
KPLER = "kpler.com"
|
|||
|
|
AIS_HUB = "aishub.net" # Открытая сеть
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class AISMessage:
|
|||
|
|
"""Сообщение AIS от судна."""
|
|||
|
|
mmsi: str # Maritime Mobile Service Identity (9 цифр)
|
|||
|
|
imo: str # IMO номер судна
|
|||
|
|
name: str # Название судна
|
|||
|
|
latitude: float # Широта
|
|||
|
|
longitude: float # Долгота
|
|||
|
|
speed: float # Скорость (узлы)
|
|||
|
|
course: float # Курс (градусы)
|
|||
|
|
status: VesselStatus # Статус
|
|||
|
|
timestamp: str # Время получения
|
|||
|
|
source: OracleSource # Источник данных
|
|||
|
|
|
|||
|
|
def to_hash(self) -> str:
|
|||
|
|
"""Hash сообщения для верификации."""
|
|||
|
|
data = f"{self.mmsi}:{self.timestamp}:{self.latitude}:{self.longitude}"
|
|||
|
|
return hashlib.sha256(data.encode()).hexdigest()
|
|||
|
|
|
|||
|
|
|
|||
|
|
@dataclass
|
|||
|
|
class PortStay:
|
|||
|
|
"""Пребывание судна в порту."""
|
|||
|
|
vessel_mmsi: str
|
|||
|
|
vessel_name: str
|
|||
|
|
port_name: str
|
|||
|
|
arrival: str # ISO timestamp
|
|||
|
|
departure: Optional[str] = None # None = ещё в порту
|
|||
|
|
messages: List[str] = field(default_factory=list) # Hashes AIS сообщений
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def duration_seconds(self) -> int:
|
|||
|
|
"""Длительность пребывания в секундах."""
|
|||
|
|
arr = datetime.fromisoformat(self.arrival.replace('Z', '+00:00'))
|
|||
|
|
|
|||
|
|
if self.departure:
|
|||
|
|
dep = datetime.fromisoformat(self.departure.replace('Z', '+00:00'))
|
|||
|
|
else:
|
|||
|
|
dep = datetime.now(timezone.utc)
|
|||
|
|
|
|||
|
|
return int((dep - arr).total_seconds())
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def juno_earned(self) -> Decimal:
|
|||
|
|
"""Заработанные Ɉ за присутствие."""
|
|||
|
|
# 1 секунда = 1 Ɉ (при текущем halving)
|
|||
|
|
return Decimal(self.duration_seconds)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AISOracle:
|
|||
|
|
"""
|
|||
|
|
Децентрализованный оракул морских данных.
|
|||
|
|
|
|||
|
|
Книга Монтана:
|
|||
|
|
> "#Сованаглобус может быть децентрализованным оракулом АПИ:
|
|||
|
|
> Маринтрафик и остальных VesselFinder, FleetMon, Spire, Kpler, AISHub."
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
# VHF частоты AIS (международные)
|
|||
|
|
AIS_FREQUENCIES = {
|
|||
|
|
"AIS1": "161.975 MHz", # Channel 87B
|
|||
|
|
"AIS2": "162.025 MHz", # Channel 88B
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def __init__(self):
|
|||
|
|
self.sources: Dict[OracleSource, bool] = {s: False for s in OracleSource}
|
|||
|
|
self.messages: List[AISMessage] = []
|
|||
|
|
self.port_stays: Dict[str, PortStay] = {} # key = mmsi:port
|
|||
|
|
|
|||
|
|
def register_source(self, source: OracleSource) -> None:
|
|||
|
|
"""Зарегистрировать источник данных."""
|
|||
|
|
self.sources[source] = True
|
|||
|
|
|
|||
|
|
def receive_ais(self, message: AISMessage) -> str:
|
|||
|
|
"""
|
|||
|
|
Получить AIS сообщение.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
message: AIS сообщение
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Hash сообщения
|
|||
|
|
"""
|
|||
|
|
msg_hash = message.to_hash()
|
|||
|
|
self.messages.append(message)
|
|||
|
|
|
|||
|
|
# Если судно пришвартовано — начать отсчёт времени
|
|||
|
|
if message.status == VesselStatus.MOORED:
|
|||
|
|
self._track_port_stay(message)
|
|||
|
|
|
|||
|
|
return msg_hash
|
|||
|
|
|
|||
|
|
def _track_port_stay(self, message: AISMessage) -> None:
|
|||
|
|
"""Отслеживать пребывание в порту."""
|
|||
|
|
# Определить порт по координатам (упрощённо)
|
|||
|
|
port = self._detect_port(message.latitude, message.longitude)
|
|||
|
|
if not port:
|
|||
|
|
return
|
|||
|
|
|
|||
|
|
key = f"{message.mmsi}:{port}"
|
|||
|
|
|
|||
|
|
if key not in self.port_stays:
|
|||
|
|
# Новое прибытие
|
|||
|
|
self.port_stays[key] = PortStay(
|
|||
|
|
vessel_mmsi=message.mmsi,
|
|||
|
|
vessel_name=message.name,
|
|||
|
|
port_name=port,
|
|||
|
|
arrival=message.timestamp
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Добавить сообщение как доказательство
|
|||
|
|
self.port_stays[key].messages.append(message.to_hash())
|
|||
|
|
|
|||
|
|
def _detect_port(self, lat: float, lon: float) -> Optional[str]:
|
|||
|
|
"""Определить порт по координатам (упрощённо)."""
|
|||
|
|
# Основные порты мира (примерные координаты)
|
|||
|
|
PORTS = {
|
|||
|
|
"Rotterdam": (51.9, 4.5),
|
|||
|
|
"Singapore": (1.3, 103.8),
|
|||
|
|
"Shanghai": (31.2, 121.5),
|
|||
|
|
"Dubai": (25.0, 55.0),
|
|||
|
|
"Los Angeles": (33.7, -118.3),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for port_name, (port_lat, port_lon) in PORTS.items():
|
|||
|
|
# Простая проверка близости (0.5 градуса)
|
|||
|
|
if abs(lat - port_lat) < 0.5 and abs(lon - port_lon) < 0.5:
|
|||
|
|
return port_name
|
|||
|
|
|
|||
|
|
return None
|
|||
|
|
|
|||
|
|
def finalize_port_stay(self, mmsi: str, port: str, departure: str) -> PortStay:
|
|||
|
|
"""
|
|||
|
|
Завершить пребывание в порту.
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
mmsi: MMSI судна
|
|||
|
|
port: Название порта
|
|||
|
|
departure: Время отхода
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
Завершённый PortStay
|
|||
|
|
"""
|
|||
|
|
key = f"{mmsi}:{port}"
|
|||
|
|
|
|||
|
|
if key not in self.port_stays:
|
|||
|
|
raise ValueError(f"Port stay not found: {key}")
|
|||
|
|
|
|||
|
|
self.port_stays[key].departure = departure
|
|||
|
|
return self.port_stays[key]
|
|||
|
|
|
|||
|
|
def get_oracle_status(self) -> Dict:
|
|||
|
|
"""Статус оракула."""
|
|||
|
|
active_sources = [s.value for s, active in self.sources.items() if active]
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"active_sources": active_sources,
|
|||
|
|
"total_sources": len(OracleSource),
|
|||
|
|
"messages_received": len(self.messages),
|
|||
|
|
"active_port_stays": len([p for p in self.port_stays.values() if not p.departure]),
|
|||
|
|
"completed_port_stays": len([p for p in self.port_stays.values() if p.departure]),
|
|||
|
|
"ais_frequencies": self.AIS_FREQUENCIES,
|
|||
|
|
"protocol": "Decentralized oracle for maritime time"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def calculate_total_juno(self) -> Decimal:
|
|||
|
|
"""Суммарные Ɉ за все пребывания."""
|
|||
|
|
total = Decimal("0")
|
|||
|
|
for stay in self.port_stays.values():
|
|||
|
|
total += stay.juno_earned
|
|||
|
|
return total
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
# DEMO
|
|||
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
oracle = AISOracle()
|
|||
|
|
|
|||
|
|
print("=" * 60)
|
|||
|
|
print("AIS ORACLE — Децентрализованный оракул времени")
|
|||
|
|
print("=" * 60)
|
|||
|
|
print("\n'Они построили трубу. Мы пускаем по ней время.'")
|
|||
|
|
|
|||
|
|
# Регистрируем источники
|
|||
|
|
oracle.register_source(OracleSource.MARINE_TRAFFIC)
|
|||
|
|
oracle.register_source(OracleSource.VESSEL_FINDER)
|
|||
|
|
oracle.register_source(OracleSource.AIS_HUB)
|
|||
|
|
|
|||
|
|
print("\n--- ИСТОЧНИКИ ДАННЫХ ---")
|
|||
|
|
for source in OracleSource:
|
|||
|
|
status = "АКТИВЕН" if oracle.sources[source] else "—"
|
|||
|
|
print(f" {source.value}: {status}")
|
|||
|
|
|
|||
|
|
# Симуляция: судно прибыло в Rotterdam
|
|||
|
|
print("\n--- СИМУЛЯЦИЯ: Судно в Rotterdam ---")
|
|||
|
|
|
|||
|
|
msg = AISMessage(
|
|||
|
|
mmsi="244123456",
|
|||
|
|
imo="9123456",
|
|||
|
|
name="BALTIC TRADER",
|
|||
|
|
latitude=51.9,
|
|||
|
|
longitude=4.5,
|
|||
|
|
speed=0.0,
|
|||
|
|
course=0.0,
|
|||
|
|
status=VesselStatus.MOORED,
|
|||
|
|
timestamp="2026-01-30T10:00:00+00:00",
|
|||
|
|
source=OracleSource.MARINE_TRAFFIC
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
msg_hash = oracle.receive_ais(msg)
|
|||
|
|
print(f"AIS получен: {msg.name}")
|
|||
|
|
print(f"Порт: Rotterdam")
|
|||
|
|
print(f"Статус: ПРИШВАРТОВАНО")
|
|||
|
|
print(f"Hash: {msg_hash[:16]}...")
|
|||
|
|
|
|||
|
|
# Симуляция: прошло 2 дня, судно уходит
|
|||
|
|
stay = oracle.finalize_port_stay(
|
|||
|
|
mmsi="244123456",
|
|||
|
|
port="Rotterdam",
|
|||
|
|
departure="2026-02-01T10:00:00+00:00"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
print(f"\n--- РЕЗУЛЬТАТ ПРЕБЫВАНИЯ ---")
|
|||
|
|
print(f"Судно: {stay.vessel_name}")
|
|||
|
|
print(f"Порт: {stay.port_name}")
|
|||
|
|
print(f"Прибытие: {stay.arrival}")
|
|||
|
|
print(f"Отход: {stay.departure}")
|
|||
|
|
print(f"Длительность: {stay.duration_seconds:,} секунд")
|
|||
|
|
print(f"Заработано: {stay.juno_earned:,} Ɉ")
|
|||
|
|
|
|||
|
|
print("\n--- ЧАСТОТЫ AIS ---")
|
|||
|
|
for name, freq in oracle.AIS_FREQUENCIES.items():
|
|||
|
|
print(f" {name}: {freq}")
|
|||
|
|
|
|||
|
|
print("\n" + "=" * 60)
|
|||
|
|
print("'MarineTraffic + Montana = время → Ɉ'")
|
|||
|
|
print("=" * 60)
|