Files
settled-reach/tooling/db/backfill_cultural_corridor.py
T
jpmschweitzerandClaude Opus 4.7 b11e847337 chore(tooling): retire atlas geometry generator + LLM naming cluster (D-223 #951)
The procedural server cascade (Phase 4) and the frozen names-only pool
supersede the Python atlas geometry generator and the LLM namer. Retire:

- generate_atlas.py (geometry production — cities/roads/rivers placement)
- gemma_naming.py, naming_core.py + tests (test_batch_naming,
  test_register_selection, qa_naming) and run-atlas-naming.sh (the LLM
  place-namer; its output is now the frozen pool)
- apply_name_fixes.py (name-field patches), fix_fewshot_bleed.py /
  prune_atlas_features.py (geometry tools)
- import_city_names.py (redundant with import_economics name-pool path)

Pipeline updates: drop the generate_atlas step + atlas-generate /
test-atlas-determinism targets from the Makefile; remove generate_atlas
from the stamp registry (import_economics is the sole regen-db generator);
drop run-atlas-determinism from tests/run-all; refresh stale references in
schema_version, backfill_cultural_corridor, earth_blocklist (kept as
reference data), populate_terrain_reference, and heightmap.rs.

The Gemma prompting methodology is preserved in
docs/gemma-naming-methodology.md (separate commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 23:29:08 +02:00

163 lines
5.7 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(f"\n cultural_corridor backfill")
print(f" DB: {db_path}")
if args.dry_run:
print(f" Mode: DRY RUN")
print()
print(f" 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(f" Would update:")
print(f" star_systems: {sys_rows_updated}")
print(f" bodies: {body_rows_updated}")
print(f"\n Dry run — no changes written.")
else:
conn.commit()
print()
print(f" 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(f" 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(f" 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()