montana/Русский/Сеть/ais_oracle.py

285 lines
11 KiB
Python
Raw Normal View History

#!/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)