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

285 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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