#!/usr/bin/env python3 """ atlas_quality_analysis.py — Atlas content quality audit for Sprint 36 (#849/#838). Queries atlas_* tables in systems.db and reports on: 1. Cross-body name collisions (same name, same feature type, different bodies) 2. Cardinal/directional name density per body 3. Generic/lazy name patterns 4. Earth-echo concentration in high-visibility systems 5. Top-stem frequency across all named features Usage: python3 tooling/planet-gen/atlas_quality_analysis.py [--db server/data/systems.db] python3 tooling/planet-gen/atlas_quality_analysis.py --system GJ380 python3 tooling/planet-gen/atlas_quality_analysis.py --top-collisions 20 python3 tooling/planet-gen/atlas_quality_analysis.py --body GJ71c D-191 §8: markers.json is pixel-space [row, col] against 512×256. Re-run after any hand-refine pass to verify improvements. """ import argparse import re import sqlite3 from collections import Counter, defaultdict from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent.parent.parent DEFAULT_DB = REPO_ROOT / "server" / "data" / "systems.db" CARDINAL_RE = re.compile( r"\b(north|south|east|west|eastern|western|northern|southern|" r"upper|lower|new|great|old|central|inner|outer|kita|minami|higashi|nishi)\b", re.I, ) GENERIC_RE = re.compile( r"\b(hilly|sector|zone|district)\b" r"|^(great|the great|hilly)\b" r"|^(valley floor|ridge line|ridge crest|flat ground|riverbend)$", re.I, ) EARTH_ECHO_RE = re.compile( r"\b(manchester|london|paris|berlin|tokyo|beijing|new york|sydney|dubai|" r"route \d+|sector \d+|block \d+)\b", re.I, ) FEATURE_TABLES = [ ("atlas_cities", "city"), ("atlas_rivers", "river"), ("atlas_oceans", "ocean"), ("atlas_mountain_ranges", "mountain"), ] def open_db(path: str) -> sqlite3.Connection: return sqlite3.connect(path) def build_body_index(conn: sqlite3.Connection) -> dict: c = conn.cursor() c.execute( "SELECT body_id, system_id, proper_name, cultural_corridor, population " "FROM bodies WHERE inhabited=1" ) return { r[0]: {"system_id": r[1], "name": r[2], "corridor": r[3], "pop": r[4]} for r in c.fetchall() } def gather_all_names(conn: sqlite3.Connection) -> dict[str, list[tuple[str, str, str]]]: """body_id → [(feature_type, name, local_id), ...]""" c = conn.cursor() result = defaultdict(list) for tbl, feat_type in FEATURE_TABLES: try: c.execute(f"SELECT body_id, name, local_id FROM {tbl} WHERE name IS NOT NULL AND name != ''") for body_id, name, local_id in c.fetchall(): result[body_id].append((feat_type, name, local_id)) except sqlite3.OperationalError: pass return result def cross_body_collisions(conn: sqlite3.Connection, limit: int = 20) -> dict: c = conn.cursor() collisions = {} for tbl, feat_type in FEATURE_TABLES: try: c.execute( f"SELECT name, COUNT(DISTINCT body_id) as cnt, GROUP_CONCAT(DISTINCT body_id) " f"FROM {tbl} WHERE name IS NOT NULL AND name != '' " f"GROUP BY name HAVING cnt > 1 ORDER BY cnt DESC LIMIT ?", (limit,), ) collisions[feat_type] = [(r[0], r[1], r[2]) for r in c.fetchall()] except sqlite3.OperationalError: collisions[feat_type] = [] return collisions def stem_frequency(names: list[str], top_n: int = 30) -> list[tuple[str, int]]: stems = Counter() for name in names: words = name.split() if words: stems[words[0].lower()] += 1 return stems.most_common(top_n) def body_quality_report(body_id: str, names: list[tuple], conn: sqlite3.Connection) -> dict: total = len(names) if total == 0: return {} cardinal = sum(1 for _, n, _ in names if CARDINAL_RE.search(n)) generic = sum(1 for _, n, _ in names if GENERIC_RE.search(n)) earth = sum(1 for _, n, _ in names if EARTH_ECHO_RE.search(n)) c = conn.cursor() # collision count: how many of this body's names appear on other bodies (same type) colliding = 0 for feat_type, name, _ in names: tbl = [t for t, f in FEATURE_TABLES if f == feat_type][0] try: c.execute( f"SELECT COUNT(DISTINCT body_id) FROM {tbl} WHERE name=? AND body_id!=?", (name, body_id), ) others = c.fetchone()[0] if others > 0: colliding += 1 except sqlite3.OperationalError: pass return { "total": total, "cardinal": cardinal, "cardinal_pct": cardinal / total, "generic": generic, "earth_echo": earth, "colliding": colliding, "colliding_pct": colliding / total, } def run_analysis(args): conn = open_db(args.db) body_index = build_body_index(conn) all_names_by_body = gather_all_names(conn) # Filter by system or body if requested if args.system: body_index = {k: v for k, v in body_index.items() if v["system_id"] == args.system} if args.body: body_index = {k: v for k, v in body_index.items() if k == args.body} print("=" * 70) print("ATLAS QUALITY ANALYSIS — The Settled Reach (#849/#838)") print(f"DB: {args.db}") if args.system: print(f"Filter: system={args.system}") if args.body: print(f"Filter: body={args.body}") print("=" * 70) # --- 1. Cross-body collisions --- print("\n[ 1. CROSS-BODY NAME COLLISIONS ]") collisions = cross_body_collisions(conn, limit=args.top_collisions) for feat_type, rows in collisions.items(): if rows: print(f"\n {feat_type}:") for name, cnt, bodies in rows: print(f" '{name}' — {cnt} bodies: {bodies[:80]}") # --- 2. Per-body quality scores --- print("\n[ 2. BODY QUALITY SCORES — ranked by collision % ]") reports = [] for bid, info in body_index.items(): names = all_names_by_body.get(bid, []) if not names: continue report = body_quality_report(bid, names, conn) if not report: continue reports.append((bid, info, report)) reports.sort(key=lambda x: -x[2]["colliding_pct"]) print(f"\n {'Body':<28} {'System':<12} {'Corridor':<15} " f"{'Coll%':>6} {'Card%':>6} {'Gen':>4} {'Echo':>4}") for bid, info, rep in reports[:30]: print( f" {(info['name'] or bid):<28} {info['system_id']:<12} {info['corridor'] or '?':<15} " f"{rep['colliding_pct']:>6.0%} {rep['cardinal_pct']:>6.0%} " f"{rep['generic']:>4} {rep['earth_echo']:>4}" ) # --- 3. Stem frequency --- print("\n[ 3. TOP STEM FREQUENCY (first word of name) ]") all_names_flat = [n for names in all_names_by_body.values() for _, n, _ in names] for stem, cnt in stem_frequency(all_names_flat, top_n=20): print(f" {stem:<20} {cnt}") # --- 4. Detailed body report (if --body specified) --- if args.body and args.body in all_names_by_body: bid = args.body info = body_index.get(bid, {}) names = all_names_by_body[bid] print(f"\n[ 4. DETAILED REPORT: {bid} ({info.get('name', '?')}) ]") c = conn.cursor() for feat_type, name, local_id in sorted(names, key=lambda x: x[0]): tbl = [t for t, f in FEATURE_TABLES if f == feat_type][0] c.execute( f"SELECT COUNT(DISTINCT body_id) FROM {tbl} WHERE name=? AND body_id!=?", (name, bid), ) others = c.fetchone()[0] flag = f" *** COLLISION ×{others}" if others > 0 else "" cardinal = " [cardinal]" if CARDINAL_RE.search(name) else "" generic = " [generic]" if GENERIC_RE.search(name) else "" print(f" {feat_type:<10} {local_id:<12} {name}{flag}{cardinal}{generic}") # --- 5. Sol gap check --- print("\n[ 5. SOL SYSTEM GAP CHECK ]") c = conn.cursor() c.execute("SELECT body_id, proper_name, population FROM bodies WHERE system_id='GJ 0' AND inhabited=1") sol_bodies = c.fetchall() for bid, bname, pop in sol_bodies: has_cities = bid in all_names_by_body and any(f == "city" for f, _, _ in all_names_by_body[bid]) status = "HAS DATA" if has_cities else "*** EMPTY — needs authoring" print(f" {bid:<15} {bname or '?':<20} pop={pop or '?'} {status}") conn.close() print("\nDone.") def main(): parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("--db", default=str(DEFAULT_DB), help="Path to systems.db") parser.add_argument("--system", help="Filter to one system (e.g. GJ380)") parser.add_argument("--body", help="Filter to one body (e.g. GJ71c)") parser.add_argument("--top-collisions", type=int, default=15, help="Collision list limit") args = parser.parse_args() run_analysis(args) if __name__ == "__main__": main()