- Reference docs: known issues, user journey, location redesign, mission merge plan - Scripts: fill_location_state, merge_missions, migrate_location, migrate_state - Tests: e2e test suite, asset entries unit tests - Demo: location board, hifi prototypes, test screenshots - Data backup: asset log entries and state nodes - Update .gitignore Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
375 lines
14 KiB
Python
375 lines
14 KiB
Python
"""
|
|
Merge Outbound/Operations/Inbound entries into single mission entries.
|
|
Stage 1: Add missing Inbound state nodes
|
|
Stage 2: Merge entries
|
|
|
|
BACKUP already created in data_backup/ before running this script.
|
|
"""
|
|
import os, psycopg, uuid
|
|
from datetime import datetime, timezone
|
|
from dotenv import load_dotenv
|
|
from pathlib import Path
|
|
|
|
load_dotenv(Path(__file__).resolve().parents[1] / ".env")
|
|
conn = psycopg.connect(
|
|
host=os.getenv("PGHOST"), port=os.getenv("PGPORT"),
|
|
dbname=os.getenv("PGDATABASE"), user=os.getenv("PGUSER"),
|
|
password=os.getenv("PGPASSWORD"))
|
|
cur = conn.cursor()
|
|
tz = timezone.utc
|
|
NOW = datetime.now(tz)
|
|
|
|
def ts(y,m,d,h=0,mi=0,s=0):
|
|
return datetime(y,m,d,h,mi,s,tzinfo=tz)
|
|
|
|
def add_node(entry_id, title, detail, at_time, seq=None):
|
|
"""Add a state node to an entry."""
|
|
cur.execute("""
|
|
INSERT INTO asset_state_nodes (id, entry_id, title, detail, at_time, created_at, updated_at)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
|
""", (str(uuid.uuid4()), entry_id, title, detail, at_time, NOW, NOW))
|
|
|
|
def move_nodes(from_entry_id, to_entry_id):
|
|
"""Move all state nodes from one entry to another."""
|
|
cur.execute("""
|
|
UPDATE asset_state_nodes SET entry_id = %s, updated_at = %s
|
|
WHERE entry_id = %s
|
|
""", (to_entry_id, NOW, from_entry_id))
|
|
return cur.rowcount
|
|
|
|
def merge_group(ops_id, outbound_ids, inbound_ids, new_start, new_end, new_title, new_state_label, new_location):
|
|
"""
|
|
Merge outbound + ops + inbound entries.
|
|
- Extend ops entry start/end
|
|
- Move all state nodes from outbound & inbound to ops
|
|
- Delete outbound & inbound entries
|
|
"""
|
|
# Extend ops
|
|
cur.execute("""
|
|
UPDATE asset_log_entries SET start_at = %s, end_at = %s,
|
|
title = %s, state_label = %s, location = %s, updated_at = %s
|
|
WHERE id = %s
|
|
""", (new_start, new_end, new_title, new_state_label, new_location, NOW, ops_id))
|
|
|
|
deleted = []
|
|
total_moved = 0
|
|
for eid in outbound_ids + inbound_ids:
|
|
n = move_nodes(eid, ops_id)
|
|
total_moved += n
|
|
cur.execute("DELETE FROM asset_log_entries WHERE id = %s", (eid,))
|
|
deleted.append(eid[:8])
|
|
print(f" Merged: ops={ops_id[:8]} start={new_start} end={new_end} moved={total_moved} nodes, deleted={deleted}")
|
|
return total_moved
|
|
|
|
print("=" * 60)
|
|
print("STAGE 1: Adding missing Inbound state nodes")
|
|
print("=" * 60)
|
|
|
|
# --- EE-1 (XH-01) Inbound: 92315017 ---
|
|
# User provided: 2059-07-10 Europa Departure, 2059-11-15 Earth Arrival, 2059-11-16 crew return
|
|
add_node('92315017-7011-4930-aabb-d00a6f3f4994',
|
|
'Europa Departure', 'Uses standard trajectory with 112km/s of departure burn',
|
|
ts(2059,7,10,8,0))
|
|
add_node('92315017-7011-4930-aabb-d00a6f3f4994',
|
|
'Earth Arrival', 'Parking to Star Port for the first time',
|
|
ts(2059,11,15,8,0))
|
|
add_node('92315017-7011-4930-aabb-d00a6f3f4994',
|
|
'Europa Expedition 1 crew return',
|
|
'Crew disembark at Star Port after 15-month Jupiter system expedition',
|
|
ts(2059,11,16,8,0))
|
|
print("EE-1 (XH-01): 3 state nodes added")
|
|
|
|
# --- EOCM-2 (ST-02) Inbound: efdb28dd ---
|
|
add_node('efdb28dd-3e02-4b16-80e8-7a2d4df76825',
|
|
'Europa Departure',
|
|
'Departure from Europa after completing outpost construction Phase 2',
|
|
ts(2058,9,6,8,0))
|
|
add_node('efdb28dd-3e02-4b16-80e8-7a2d4df76825',
|
|
'Earth Arrival',
|
|
'Parking to Star Port',
|
|
ts(2058,11,8,8,0))
|
|
add_node('efdb28dd-3e02-4b16-80e8-7a2d4df76825',
|
|
'Europa Outpost Construction Mission 2 crew return',
|
|
'Crew disembark at Star Port',
|
|
ts(2058,11,9,8,0))
|
|
print("EOCM-2 (ST-02): 3 state nodes added")
|
|
|
|
# --- AEM (XH-02) Inbound: d74dfb61 ---
|
|
add_node('d74dfb61-fd3a-492c-b019-253ec9cd826d',
|
|
'Vesta Departure',
|
|
'Departure from Vesta after completing asteroid survey operations',
|
|
ts(2058,1,4,8,0))
|
|
add_node('d74dfb61-fd3a-492c-b019-253ec9cd826d',
|
|
'Earth Arrival',
|
|
'Parking to Star Port',
|
|
ts(2058,4,17,8,0))
|
|
add_node('d74dfb61-fd3a-492c-b019-253ec9cd826d',
|
|
'Asteroid Exploration Mission crew return',
|
|
'Crew disembark at Star Port after Vesta exploration',
|
|
ts(2058,4,18,8,0))
|
|
print("AEM (XH-02): 3 state nodes added")
|
|
|
|
# --- ME-3 crewed Inbound (XH-02): 33a45210 ---
|
|
add_node('33a45210-3497-4826-ba6e-291b384ebe7e',
|
|
'Mars Departure',
|
|
'Departure from Mars with ME-3 crew returning to Earth',
|
|
ts(2059,11,4,8,0))
|
|
add_node('33a45210-3497-4826-ba6e-291b384ebe7e',
|
|
'Earth Arrival',
|
|
'Parking to Star Port',
|
|
ts(2060,1,6,8,0))
|
|
add_node('33a45210-3497-4826-ba6e-291b384ebe7e',
|
|
'Mars Expedition 3 crew return',
|
|
'Crew disembark at Star Port after extended Mars surface expedition',
|
|
ts(2060,1,7,8,0))
|
|
print("ME-3 crewed (XH-02): 3 state nodes added")
|
|
|
|
# --- ME-3 uncrewed Inbound (XH-02): f43aa249 ---
|
|
add_node('f43aa249-80d1-426c-9aca-a341411be98d',
|
|
'Mars Departure (uncrewed)',
|
|
'XH-02 returns to Earth uncrewed; ME-3 crew remains at Mars for ME-4 handoff',
|
|
ts(2059,5,4,8,0))
|
|
add_node('f43aa249-80d1-426c-9aca-a341411be98d',
|
|
'Earth Arrival (uncrewed)',
|
|
'Uncrewed parking to Star Port for maintenance rotation',
|
|
ts(2059,7,6,8,0))
|
|
print("ME-3 uncrewed (XH-02): 2 state nodes added")
|
|
|
|
# Commit stage 1
|
|
conn.commit()
|
|
print("\nStage 1 complete. Starting Stage 2...\n")
|
|
|
|
print("=" * 60)
|
|
print("STAGE 2: Merging mission entries")
|
|
print("=" * 60)
|
|
|
|
total_deleted = 0
|
|
total_moved = 0
|
|
|
|
# === XH-01 ===
|
|
print("\n--- XH-01 羲和号 ---")
|
|
|
|
# MMD
|
|
n = merge_group(
|
|
'23b4304f-8fca-4d78-8a93-c9f78fab51e0', # Ops
|
|
['e54817e0-6809-404d-be62-c6017d143362'], # Outbound
|
|
['ded06cd6-f9a4-4646-9be4-2f01cf067334'], # Inbound
|
|
ts(2054,7,10,8,0), ts(2055,2,28,8,0),
|
|
'Mars Mission Demo', 'Transfer; Planetary Operations; Transfer', 'Mars')
|
|
total_moved += n
|
|
|
|
# MOCM-1
|
|
n = merge_group(
|
|
'ec34d68b-4728-42eb-af79-b3e8805aa253',
|
|
['aab3125c-c99e-4af5-ab64-ffe7fda48ad7'],
|
|
['bdf99335-d329-4bd2-8e17-27ac1f097ca9'],
|
|
ts(2055,6,15,8,0), ts(2056,3,18,8,0),
|
|
'Mars One Base Construction Mission 1', 'Transfer; Base Construction; Transfer', 'Mars')
|
|
total_moved += n
|
|
|
|
# MOCM-3
|
|
n = merge_group(
|
|
'fd22c4b2-4938-4c01-ae92-e84f14d8b8dd',
|
|
['ea2024c5-95e6-4acc-a88d-998183a35a5a'],
|
|
['e5f549b9-0446-48be-a606-d9c1ada664b7'],
|
|
ts(2056,7,30,8,0), ts(2057,3,25,8,0),
|
|
'Mars One Base Construction Mission 3', 'Transfer; Base Construction; Transfer', 'Mars')
|
|
total_moved += n
|
|
|
|
# ME-2
|
|
n = merge_group(
|
|
'7f5da9b3-94fa-4bd5-a6bd-94f6841c71a2',
|
|
['30c84287-e5d1-473c-93fb-9d36d426516e'],
|
|
['058bbf6d-5c51-49c4-8516-8b6931db53c1'],
|
|
ts(2057,5,5,8,0), ts(2058,5,20,8,0),
|
|
'Mars Expedition 2', 'Transfer; Planetary Operations; Transfer', 'Mars')
|
|
total_moved += n
|
|
|
|
# EE-1
|
|
n = merge_group(
|
|
'f01bfb99-f981-45f6-808b-7c5222503715',
|
|
['4ff368c4-503b-42ce-9e82-b4da291d3203'],
|
|
['92315017-7011-4930-aabb-d00a6f3f4994'],
|
|
ts(2058,7,10,8,0), ts(2059,11,15,8,0),
|
|
'Europa Expedition 1', 'Transfer; Jupiter Exploration; Transfer', 'Jupiter System')
|
|
total_moved += n
|
|
|
|
# EE-3 (ongoing, no inbound)
|
|
cur.execute("""
|
|
UPDATE asset_log_entries SET start_at = %s, title = %s,
|
|
state_label = %s, updated_at = %s
|
|
WHERE id = %s
|
|
""", (ts(2060,2,20,8,0), 'Europa Expedition 3',
|
|
'Transfer; Jupiter Exploration', NOW,
|
|
'91d4948a-ae04-43a8-ac3b-def9caecc8e5'))
|
|
n = move_nodes('ee52cc53-7d9c-4134-ba1b-4f7cfb8fdce9', '91d4948a-ae04-43a8-ac3b-def9caecc8e5')
|
|
cur.execute("DELETE FROM asset_log_entries WHERE id = 'ee52cc53-7d9c-4134-ba1b-4f7cfb8fdce9'")
|
|
print(f" Merged (ongoing): ops=91d4948a start=2060-02-20 end=ongoing moved={n} nodes")
|
|
|
|
# === ST-01 ===
|
|
print("\n--- ST-01 万星源号 ---")
|
|
|
|
# ISSEM-1 (5 entries → 1)
|
|
# Keep Venus Ops as main (longest ops span), merge Mercury Transfer + Mercury Ops + Venus Transfer + Inbound
|
|
n1 = move_nodes('4bfddc84-dfec-43e3-b534-4fb798bcc73f', 'f28469ee-1dbc-4232-98ff-91f7abcb9d59') # Mercury Transfer → Venus Ops
|
|
n2 = move_nodes('42698c8c-a1cc-4742-9885-7b9e650f9867', 'f28469ee-1dbc-4232-98ff-91f7abcb9d59') # Mercury Ops → Venus Ops
|
|
n3 = move_nodes('2adfa016-b23a-4459-af6e-a84da2aa2469', 'f28469ee-1dbc-4232-98ff-91f7abcb9d59') # Venus Transfer → Venus Ops
|
|
n4 = move_nodes('1f6e85aa-121a-4f47-95b6-35f040da055c', 'f28469ee-1dbc-4232-98ff-91f7abcb9d59') # Inbound → Venus Ops
|
|
cur.execute("""
|
|
UPDATE asset_log_entries SET start_at = %s, end_at = %s, title = %s,
|
|
state_label = %s, location = %s, updated_at = %s
|
|
WHERE id = %s
|
|
""", (ts(2055,3,20,8,0), ts(2056,4,12,8,0),
|
|
'Inner Solar System Exploration Mission 1',
|
|
'Transfer; Planetary Operations; Transfer', 'Mercury; Venus',
|
|
NOW, 'f28469ee-1dbc-4232-98ff-91f7abcb9d59'))
|
|
for eid in ['4bfddc84-dfec-43e3-b534-4fb798bcc73f','42698c8c-a1cc-4742-9885-7b9e650f9867',
|
|
'2adfa016-b23a-4459-af6e-a84da2aa2469','1f6e85aa-121a-4f47-95b6-35f040da055c']:
|
|
cur.execute("DELETE FROM asset_log_entries WHERE id = %s", (eid,))
|
|
print(f" Merged ISSEM-1 (5→1): moved={n1+n2+n3+n4} nodes, deleted 4 entries")
|
|
|
|
# MOCM-2
|
|
n = merge_group(
|
|
'12059276-d10a-4a37-9b8c-6a6183768504',
|
|
['ffd625b0-0de4-4f47-8c8d-36669a1675f6'],
|
|
['cf351aa5-4149-4938-939b-edcb846f4b14'],
|
|
ts(2056,6,2,8,0), ts(2056,11,3,8,0),
|
|
'Mars One Construction Mission 2', 'Transfer; Base Construction; Transfer', 'Mars')
|
|
total_moved += n
|
|
|
|
# EOCM-1
|
|
n = merge_group(
|
|
'545f687e-bd2c-409a-8fb8-186a6dead67e',
|
|
['7747339c-f602-4ddf-8511-6862006f789b'],
|
|
['43f2ece9-283b-4484-b838-d684b9ada4d6'],
|
|
ts(2056,12,15,8,0), ts(2057,12,20,8,0),
|
|
'Europa Outpost Construction Mission 1', 'Transfer; Outpost Construction; Transfer', 'Europa')
|
|
total_moved += n
|
|
|
|
# SEM-1
|
|
n = merge_group(
|
|
'022b0296-5c1a-41f8-8634-97dc663b05d9',
|
|
['cad8537e-1966-4b3c-abed-35c511950ac8'],
|
|
['f536e950-a024-45e3-95ff-da5f77772119'],
|
|
ts(2058,6,1,8,0), ts(2059,11,24,8,0),
|
|
'Saturn Exploration Mission 1', 'Transfer; Planetary Operations; Transfer', 'Saturn System')
|
|
total_moved += n
|
|
|
|
# OSSEM-1 (ongoing)
|
|
cur.execute("""
|
|
UPDATE asset_log_entries SET start_at = %s, title = %s,
|
|
state_label = %s, updated_at = %s
|
|
WHERE id = %s
|
|
""", (ts(2060,2,11,8,0), 'Outer Solar System Exploration Mission 1',
|
|
'Transfer; Exploration', NOW,
|
|
'18b3429e-28d3-489c-b65c-35e0e9775a0d'))
|
|
n = move_nodes('8a9de074-6348-4190-bd27-e3bdeddb70f7', '18b3429e-28d3-489c-b65c-35e0e9775a0d')
|
|
cur.execute("DELETE FROM asset_log_entries WHERE id = '8a9de074-6348-4190-bd27-e3bdeddb70f7'")
|
|
print(f" Merged OSSEM-1 (ongoing): moved={n} nodes")
|
|
|
|
# === ST-02 ===
|
|
print("\n--- ST-02 克里斯滕号 ---")
|
|
|
|
# EOCM-2
|
|
n = merge_group(
|
|
'fb17cab1-dcbc-4814-83e9-5fb81a73f869',
|
|
['a61ee6fc-5495-4b5b-a505-968669b81b18'],
|
|
['efdb28dd-3e02-4b16-80e8-7a2d4df76825'],
|
|
ts(2057,10,6,8,0), ts(2058,11,8,8,0),
|
|
'Europa Outpost Construction Mission 2', 'Transfer; Outpost Construction; Transfer', 'Europa')
|
|
total_moved += n
|
|
|
|
# EE-2
|
|
n = merge_group(
|
|
'65910288-0c2a-4800-bfce-39495efafce2',
|
|
['fe21560f-60f4-48bb-bbdb-e2c1ab6fe3af'],
|
|
['e77cd6b5-a2e2-479b-817b-67c1c59bfdee'],
|
|
ts(2059,4,6,8,0), ts(2060,8,30,6,11),
|
|
'Europa Expedition 2', 'Transfer; Planetary Operations; Transfer', 'Europa')
|
|
total_moved += n
|
|
|
|
# === XH-02 ===
|
|
print("\n--- XH-02 太白号 ---")
|
|
|
|
# AEM
|
|
n = merge_group(
|
|
'99eb75ba-e02b-4cdc-a5f1-3ea547cc7d4a',
|
|
['7c166ff7-df3b-495d-a7df-9b99949e23f0'],
|
|
['d74dfb61-fd3a-492c-b019-253ec9cd826d'],
|
|
ts(2057,5,4,8,0), ts(2058,4,17,8,0),
|
|
'Asteroid Exploration Mission', 'Transfer; Asteroid Operations; Transfer', 'Vesta')
|
|
total_moved += n
|
|
|
|
# ME-3 (merge Ops + Uncrewed Inbound + Crewed Inbound)
|
|
n1 = move_nodes('f43aa249-80d1-426c-9aca-a341411be98d', '956cc319-668e-45d1-ab7c-9f0aa408c5f7')
|
|
n2 = move_nodes('33a45210-3497-4826-ba6e-291b384ebe7e', '956cc319-668e-45d1-ab7c-9f0aa408c5f7')
|
|
cur.execute("""
|
|
UPDATE asset_log_entries SET start_at = %s, end_at = %s, title = %s,
|
|
state_label = %s, location = %s, updated_at = %s
|
|
WHERE id = %s
|
|
""", (ts(2058,4,17,8,0), ts(2060,1,6,8,0),
|
|
'Mars Expedition 3', 'Transfer; Planetary Operations; Transfer', 'Mars',
|
|
NOW, '956cc319-668e-45d1-ab7c-9f0aa408c5f7'))
|
|
for eid in ['f43aa249-80d1-426c-9aca-a341411be98d','33a45210-3497-4826-ba6e-291b384ebe7e']:
|
|
cur.execute("DELETE FROM asset_log_entries WHERE id = %s", (eid,))
|
|
print(f" Merged ME-3 (3→1): moved={n1+n2} nodes, deleted 2 entries")
|
|
total_moved += n1 + n2
|
|
|
|
# ME-4 — SKIP (单独处理)
|
|
print(" ME-4: SKIPPED (单独处理)")
|
|
|
|
# === JW-01 ===
|
|
print("\n--- JW-01 金乌号 ---")
|
|
n = merge_group(
|
|
'30930d1b-d32b-4ef6-97ae-6d11c777ad53',
|
|
['b99062ea-4547-4cdc-8bf1-3b1f0c28a4be'],
|
|
['00c8b5c6-e363-4aee-a768-2fc3fa7a0138'],
|
|
ts(2059,8,6,8,0), ts(2060,10,26,10,3),
|
|
'Mars One Expansion', 'Transfer; Cargo Delivery; Transfer', 'Mars')
|
|
total_moved += n
|
|
|
|
# === Solar Shuttle 2 ===
|
|
print("\n--- Solar Shuttle 2 ---")
|
|
n = merge_group(
|
|
'c0271f19-e604-4c15-9c00-7118fec1928e',
|
|
['5a2092a8-ddad-41b3-9819-fa52557332ed'],
|
|
['687615b6-144f-4fd5-b3ee-709e4eaea6aa'],
|
|
ts(2059,12,9,8,0), ts(2060,6,26,8,0),
|
|
'Europa Outpost Expansion Mission', 'Transfer; Outpost Expansion; Transfer', 'Europa')
|
|
total_moved += n
|
|
|
|
# === Commit ===
|
|
conn.commit()
|
|
print(f"\n{'='*60}")
|
|
print(f"COMPLETE. Total state nodes moved: {total_moved}")
|
|
print(f"{'='*60}")
|
|
|
|
# Verify
|
|
cur.execute("""
|
|
SELECT a.name, COUNT(*) as entries, COUNT(le.mission_label) as with_mission
|
|
FROM asset_log_entries le JOIN assets a ON le.asset_id = a.id
|
|
WHERE a.name ILIKE '%XH-01%' OR a.name ILIKE '%XH-02%' OR a.name ILIKE '%ST-01%'
|
|
OR a.name ILIKE '%ST-02%' OR a.name ILIKE '%JW-01%' OR a.name ILIKE '%Solar Shuttle 2%'
|
|
GROUP BY a.name ORDER BY a.name
|
|
""")
|
|
print("\nPost-merge entry counts:")
|
|
for r in cur.fetchall():
|
|
print(f" {r[0]:20s}: {r[1]} entries")
|
|
|
|
# Count state nodes per asset
|
|
cur.execute("""
|
|
SELECT a.name, COUNT(sn.id) as nodes
|
|
FROM asset_state_nodes sn
|
|
JOIN asset_log_entries le ON sn.entry_id = le.id
|
|
JOIN assets a ON le.asset_id = a.id
|
|
WHERE a.name ILIKE '%XH-01%' OR a.name ILIKE '%XH-02%' OR a.name ILIKE '%ST-01%'
|
|
OR a.name ILIKE '%ST-02%' OR a.name ILIKE '%JW-01%' OR a.name ILIKE '%Solar Shuttle 2%'
|
|
GROUP BY a.name ORDER BY a.name
|
|
""")
|
|
print("\nPost-merge state node counts (should match pre-merge total):")
|
|
for r in cur.fetchall():
|
|
print(f" {r[0]:20s}: {r[1]} nodes")
|
|
|
|
conn.close()
|
|
print("\nDone.")
|