chore: add reference docs, scripts, tests, demo, prototypes
- 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>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
Migrate location and state_label from asset_log_entries to asset_state_nodes.
|
||||
1. Add columns to asset_state_nodes
|
||||
2. For each interval: migrate location/state_label to first state node (or create one)
|
||||
3. Drop columns from asset_log_entries
|
||||
"""
|
||||
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)
|
||||
|
||||
# Step 1: Add columns
|
||||
print("Step 1: Adding location and state_label to asset_state_nodes...")
|
||||
try:
|
||||
cur.execute("ALTER TABLE asset_state_nodes ADD COLUMN location VARCHAR(255)")
|
||||
print(" Added location column")
|
||||
except Exception as e:
|
||||
if 'already exists' in str(e):
|
||||
print(" location column already exists")
|
||||
else:
|
||||
raise
|
||||
try:
|
||||
cur.execute("ALTER TABLE asset_state_nodes ADD COLUMN state_label VARCHAR(120)")
|
||||
print(" Added state_label column")
|
||||
except Exception as e:
|
||||
if 'already exists' in str(e):
|
||||
print(" state_label column already exists")
|
||||
else:
|
||||
raise
|
||||
|
||||
# Step 2: Migrate data
|
||||
print("\nStep 2: Migrating location and state_label to state nodes...")
|
||||
cur.execute("""
|
||||
SELECT le.id, le.location, le.state_label, le.title, le.start_at,
|
||||
(SELECT COUNT(*) FROM asset_state_nodes sn WHERE sn.entry_id = le.id) as nc
|
||||
FROM asset_log_entries le
|
||||
WHERE le.location IS NOT NULL OR le.state_label IS NOT NULL
|
||||
""")
|
||||
rows = cur.fetchall()
|
||||
print(f" Found {len(rows)} entries with location/state_label")
|
||||
|
||||
created = 0
|
||||
updated = 0
|
||||
for row in rows:
|
||||
entry_id, loc, st_lbl, title, start_at, nc = row
|
||||
if nc > 0:
|
||||
# Update the first (earliest) state node of this interval
|
||||
cur.execute("""
|
||||
UPDATE asset_state_nodes
|
||||
SET location = COALESCE(%s, location),
|
||||
state_label = COALESCE(%s, state_label),
|
||||
updated_at = %s
|
||||
WHERE id = (
|
||||
SELECT id FROM asset_state_nodes
|
||||
WHERE entry_id = %s
|
||||
ORDER BY at_time ASC LIMIT 1
|
||||
)
|
||||
""", (loc, st_lbl, NOW, entry_id))
|
||||
updated += 1
|
||||
else:
|
||||
# No state nodes — create one at the interval's start_at
|
||||
cur.execute("""
|
||||
INSERT INTO asset_state_nodes (id, entry_id, title, detail, at_time, location, state_label, created_at, updated_at)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
""", (str(uuid.uuid4()), entry_id, title or '(Start)',
|
||||
f'Auto-created during location migration. Original interval: {title}',
|
||||
start_at, loc, st_lbl, NOW, NOW))
|
||||
created += 1
|
||||
print(f" Updated {updated} existing state nodes, created {created} new ones")
|
||||
|
||||
# Step 3: Drop columns
|
||||
print("\nStep 3: Dropping location and state_label from asset_log_entries...")
|
||||
cur.execute("ALTER TABLE asset_log_entries DROP COLUMN location")
|
||||
print(" Dropped location")
|
||||
cur.execute("ALTER TABLE asset_log_entries DROP COLUMN state_label")
|
||||
print(" Dropped state_label")
|
||||
|
||||
conn.commit()
|
||||
print("\nMigration complete.")
|
||||
conn.close()
|
||||
Reference in New Issue
Block a user