- 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>
90 lines
3.2 KiB
Python
90 lines
3.2 KiB
Python
"""
|
|
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()
|