Files
settled-reach/tooling/db/backfill_cultural_corridor.py
T
jpmschweitzerandClaude Fable 5 346d87df7a chore(meta): docs/build sweep + tooling test gate (T-1069, T-1066)
- make test-tooling: planet-gen determinism guard + import_economics
  --dry-run, wired into pre-push on TOOLING_CHANGED; ruff widened to
  E4/E7/E9/F/W (90 safe auto-fixes applied; E402/E702/F841 ignored with
  documented counts)
- one-generator reality fixed in DEVOPS.md, asset-pipeline rule, CLAUDE.md
  (import_economics sole generator since #951/D-223); dead check-protocol
  target deleted; DEVOPS hook/config sections rewritten from the actual
  hook sources; team-patterns gate description updated (client+tooling)
- project.yaml: 0.2.0 → 0.4.0 per the 0.{phase}.{n} scheme, description
  refreshed from the v0.1 Sova narration to cascade reality
- stale comment sweep: voxel.rs stub claims (all 8 families implemented),
  cascade.rs TODO recited to T-1044, main.rs D-192 handshake claim,
  relationships.rs/chunk_streaming.rs version targets → phase language
- gitignore: client/settings.db* e2e-run artifacts

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 16:22:55 +02:00

163 lines
5.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Backfill star_systems.cultural_corridor and bodies.cultural_corridor.
The schema has a `cultural_corridor` column on both tables, but
`wiki_sync.py` never populated it from the wiki index.md files — the
cultural/geographic identity of each system lives in
`star_systems.geographic_sector` instead (values: core, north_reach,
south_reach, east_reach, west_reach, deep_frontier). These two fields
refer to the same concept: which arc of the reach the system belongs
to. Leaving `cultural_corridor` NULL on 99%+ of rows defeats every
downstream consumer that actually wants to filter by corridor
(the atlas UI, narrative tools, and the server generation cascade).
This script treats `geographic_sector` as the source of truth and
copies it into `cultural_corridor`:
star_systems.cultural_corridor := star_systems.geographic_sector
WHERE cultural_corridor IS NULL
bodies.cultural_corridor := parent star_systems.cultural_corridor
WHERE bodies.cultural_corridor IS NULL
It is safe to re-run — idempotent, NULL-only updates, explicit
transaction wrapper so a crash never leaves a half-populated state.
Run it after any `wiki_sync.py` pass that creates fresh systems.db
rows.
Usage:
tooling/db/backfill_cultural_corridor.py
tooling/db/backfill_cultural_corridor.py --db path/to/systems.db
tooling/db/backfill_cultural_corridor.py --dry-run
Decisions: D-191 (atlas pipeline — downstream consumer)
"""
import argparse
import sqlite3
import sys
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
REPO_ROOT = (SCRIPT_DIR / ".." / "..").resolve()
DB_PATH = REPO_ROOT / "server" / "data" / "systems.db"
def main():
parser = argparse.ArgumentParser(
description="Backfill cultural_corridor on star_systems and bodies"
)
parser.add_argument("--db", default=str(DB_PATH), help="Path to systems.db")
parser.add_argument(
"--dry-run",
action="store_true",
help="Report counts without writing",
)
args = parser.parse_args()
db_path = Path(args.db)
if not db_path.exists():
print(f"error: {db_path} not found", file=sys.stderr)
sys.exit(1)
conn = sqlite3.connect(str(db_path))
conn.execute("PRAGMA foreign_keys=ON")
# Counts before.
before_systems_null = conn.execute(
"SELECT COUNT(*) FROM star_systems WHERE cultural_corridor IS NULL"
).fetchone()[0]
before_bodies_null = conn.execute(
"SELECT COUNT(*) FROM bodies WHERE cultural_corridor IS NULL"
).fetchone()[0]
print("\n cultural_corridor backfill")
print(f" DB: {db_path}")
if args.dry_run:
print(" Mode: DRY RUN")
print()
print(" Before:")
print(f" star_systems.cultural_corridor NULL: {before_systems_null}")
print(f" bodies.cultural_corridor NULL: {before_bodies_null}")
conn.execute("BEGIN")
try:
# 1. Star systems — copy geographic_sector into cultural_corridor
# where the latter is still NULL. If geographic_sector is also
# NULL, leave cultural_corridor NULL — there is nothing to
# copy and a bogus placeholder is worse than honest NULL.
sys_rows_updated = conn.execute(
"""
UPDATE star_systems
SET cultural_corridor = geographic_sector
WHERE cultural_corridor IS NULL
AND geographic_sector IS NOT NULL
"""
).rowcount
# 2. Bodies — inherit from the parent star_systems row.
body_rows_updated = conn.execute(
"""
UPDATE bodies
SET cultural_corridor = (
SELECT s.cultural_corridor
FROM star_systems s
WHERE s.system_id = bodies.system_id
)
WHERE cultural_corridor IS NULL
AND EXISTS (
SELECT 1
FROM star_systems s
WHERE s.system_id = bodies.system_id
AND s.cultural_corridor IS NOT NULL
)
"""
).rowcount
if args.dry_run:
conn.rollback()
print()
print(" Would update:")
print(f" star_systems: {sys_rows_updated}")
print(f" bodies: {body_rows_updated}")
print("\n Dry run — no changes written.")
else:
conn.commit()
print()
print(" Updated:")
print(f" star_systems: {sys_rows_updated}")
print(f" bodies: {body_rows_updated}")
# Counts after.
after_systems_null = conn.execute(
"SELECT COUNT(*) FROM star_systems WHERE cultural_corridor IS NULL"
).fetchone()[0]
after_bodies_null = conn.execute(
"SELECT COUNT(*) FROM bodies WHERE cultural_corridor IS NULL"
).fetchone()[0]
print()
print(" After:")
print(f" star_systems.cultural_corridor NULL: {after_systems_null}")
print(f" bodies.cultural_corridor NULL: {after_bodies_null}")
# Show the distribution so the outcome is visible.
print()
print(" star_systems.cultural_corridor distribution:")
for corridor, count in conn.execute(
"SELECT cultural_corridor, COUNT(*) FROM star_systems "
"GROUP BY cultural_corridor ORDER BY COUNT(*) DESC"
).fetchall():
print(f" {corridor!r}: {count}")
except BaseException:
conn.rollback()
conn.close()
raise
conn.close()
print()
if __name__ == "__main__":
main()