#!/usr/bin/env python3 """Generate minimal wiki stub pages for corps in brands.toml that don't yet have wiki pages. Usage: python3 tooling/generate_corp_stubs.py [--dry-run] In --dry-run mode, prints which stubs would be created without writing any files. """ import argparse from pathlib import Path REPO = Path(__file__).parent.parent BRANDS_TOML = REPO / "wiki/economics/corporations/brands.toml" CORPS_DIR = REPO / "wiki/corporations" # Star system ID -> (proper_name, corridor) SYSTEMS = { "GJ 0": ("Sol", "sol-gateway-axis"), "GJ 1": ("Grünfeld", "west_reach"), "GJ 1002": ("Caparica", "south_reach"), "GJ 1005A": ("Port Desrochers", "east_reach"), "GJ 103": ("Bestevaer", "west_reach"), "GJ 105A": ("Nová Tržnice", "east_reach"), "GJ 1061": ("Crux Station", "south_reach"), "GJ 1075": ("Espinho", "south_reach"), "GJ 1073": ("Mabuhay", "east_reach"), "GJ 107A": ("Tình Yên", "east_reach"), "GJ 1111": ("Zenzele", "south_reach"), "GJ 1116A": ("Mabaso Junction", "north_reach"), "GJ 1116B": ("Kraków Junction", "west_reach"), "GJ 1125": ("Hwaseong", "east_reach"), "GJ 1128": ("Devereux Point", "east_reach"), "GJ 1156": ("Travessia", "south_reach"), "GJ 1230A": ("Uusi Vantaa", "north_reach"), "GJ 1232": ("Gammelstad", "west_reach"), "GJ 1235": ("Knotenpunkt", "deep_frontier"), "GJ 124": ("Hạ Long", "east_reach"), "GJ 1245A": ("Okoro's Claim", "north_reach"), "GJ 1245B": ("Koeberg", "south_reach"), "GJ 1248": ("Haltefenn", "west_reach"), "GJ 127A": ("Dalim", "deep_frontier"), "GJ 127B": ("Echternach", "west_reach"), "GJ 1286": ("Kensho", "core"), "GJ 1289": ("Athabasca", "north_reach"), "GJ 1292": ("Moyale", "north_reach"), "GJ 136": ("Dài Lộ", "east_reach"), "GJ 137": ("Novaya Zarya", "east_reach"), "GJ 138": ("Sertão", "deep_frontier"), "GJ 139": ("Okafor Station", "north_reach"), "GJ 142": ("Leuchtturm", "deep_frontier"), "GJ 144": ("Ran", "core"), "GJ 15A": ("Lu Ban", "core"), "GJ 166A": ("Keid", "core"), "GJ 172": ("Kaelong", "east_reach"), "GJ 17": ("Binh Trang", "east_reach"), "GJ 178": ("Ashfield", "north_reach"), "GJ 183": ("Waterkant", "north_reach"), "GJ 19": ("Solheim", "west_reach"), "GJ 191": ("Kapteyn's Star", "west_reach"), "GJ 194B": ("Pemberton", "north_reach"), "GJ 197": ("Puerto Último", "south_reach"), "GJ 201": ("GJ 201", "deep_frontier"), "GJ 204": ("Pedra Funda", "deep_frontier"), "GJ 205": ("Kurashiki", "east_reach"), "GJ 208": ("Pereira's Rest", "west_reach"), "GJ 2097": ("Nascente", "south_reach"), "GJ 211": ("Encruzilhada", "deep_frontier"), "GJ 213": ("Kaapse Baai", "south_reach"), "GJ 216A": ("Kelburn Crossing", "north_reach"), "GJ 216B": ("Gemini Point", "north_reach"), "GJ 222A": ("Karoo", "south_reach"), "GJ 229": ("Port Mäkelä", "west_reach"), "GJ 231": ("Thorvaldsen's Circuit", "west_reach"), "GJ 234A": ("Carvalhais", "south_reach"), "GJ 244A": ("Sirius", "core"), "GJ 250A": ("Carrington's Post", "north_reach"), "GJ 251": ("Renaissance", "core"), "GJ 268": ("Yeongwol", "east_reach"), "GJ 27": ("Okahandja", "south_reach"), "GJ 273": ("Fremantle", "north_reach"), "GJ 280A": ("Parallax", "core"), "GJ 282A": ("Bout du Chemin", "west_reach"), "GJ 285": ("Fenwick's Lantern", "north_reach"), "GJ 286": ("Weitblick", "deep_frontier"), "GJ 299": ("Várzea", "south_reach"), "GJ 300": ("Dunmore Stillwater", "north_reach"), "GJ 302": ("Lichtbrücke", "west_reach"), "GJ 309": ("Vorposten", "deep_frontier"), "GJ 3135": ("Whitfield Crossing", "east_reach"), "GJ 320": ("Sonnwend", "west_reach"), "GJ 3250": ("Rionegro", "south_reach"), "GJ 325A": ("Purnima", "east_reach"), "GJ 33": ("96 Piscium", "south_reach"), "GJ 3323": ("Entroncamento", "south_reach"), "GJ 3325": ("Calloway Reach", "north_reach"), "GJ 3333": ("Groenland", "west_reach"), "GJ 3379": ("Shenwei", "east_reach"), "GJ 338B": ("Arbour", "core"), "GJ 3454": ("Eisentor", "west_reach"), "GJ 349": ("Randgebiet", "deep_frontier"), "GJ 34A": ("Tanegashima", "north_reach"), "GJ 34B": ("Yongjin", "east_reach"), "GJ 35": ("Vuurkloof", "south_reach"), "GJ 3522": ("Sindri", "core"), "GJ 3533": ("Trung's Crossing", "east_reach"), "GJ 3586": ("Eisfeld", "west_reach"), "GJ 3612": ("Xa Vời", "east_reach"), "GJ 3618": ("Pemberton's Loop", "north_reach"), "GJ 3622": ("Havnfjord", "north_reach"), "GJ 370": ("Traagwater", "west_reach"), "GJ 3728B": ("Jeonnam", "east_reach"), "GJ 3737": ("Nyrheim", "west_reach"), "GJ 380": ("Groombridge", "core"), "GJ 3820": ("Uusi Vantaa", "north_reach"), "GJ 3877": ("Tessera", "core"), "GJ 388": ("The Lantern", "south_reach"), "GJ 393": ("Encrucijada", "south_reach"), "GJ 395": ("Confluent", "west_reach"), "GJ 3991": ("Dunbar", "north_reach"), "GJ 402": ("Prospect Hill", "north_reach"), "GJ 4053": ("Dzimbahwe", "south_reach"), "GJ 406": ("Wolf 359", "north_reach"), "GJ 408": ("Oude Kraal", "north_reach"), "GJ 410": ("Wagtoring", "north_reach"), "GJ 411": ("Lalande", "south_reach"), "GJ 412A": ("Morales' End", "south_reach"), "GJ 412B": ("Korhonen", "east_reach"), "GJ 414A": ("Vindkast", "west_reach"), "GJ 42": ("Langemark", "west_reach"), "GJ 423A": ("Marunong", "east_reach"), "GJ 423B": ("Quilombo", "south_reach"), "GJ 4274": ("Suzhou", "east_reach"), "GJ 4285": ("Lam's Prospect", "east_reach"), "GJ 432A": ("Kaur's Observatory", "east_reach"), "GJ 435": ("Mirante", "south_reach"), "GJ 438": ("Relay 7", "east_reach"), "GJ 442A": ("Torsby", "west_reach"), "GJ 445": ("Jinghu", "east_reach"), "GJ 447": ("Ross 128", "core"), "GJ 449": ("Murakami's Lantern", "east_reach"), "GJ 451A": ("Groombridge 1830", "south_reach"), "GJ 453": ("Freiholt", "west_reach"), "GJ 454": ("Steinfeld", "west_reach"), "GJ 473A": ("Passagem", "south_reach"), "GJ 473B": ("Nkosi's Bridge", "north_reach"), "GJ 475": ("Braemar", "north_reach"), "GJ 482A": ("Dagat", "east_reach"), "GJ 49": ("Dernier Quai", "east_reach"), "GJ 4A": ("Velha Guarda", "south_reach"), "GJ 501B": ("Candeia", "deep_frontier"), "GJ 502": ("Meridian", "west_reach"), "GJ 505A": ("Bergtor", "west_reach"), "GJ 506": ("Dokkum", "west_reach"), "GJ 508A": ("Stilwater", "north_reach"), "GJ 526": ("Neustadt", "west_reach"), "GJ 529": ("Yttermark", "west_reach"), "GJ 532": ("Kettenschmied", "deep_frontier"), "GJ 534": ("Stillvakt", "west_reach"), "GJ 536": ("Retiro", "south_reach"), "GJ 53A": ("Bridgepoint", "east_reach"), "GJ 54": ("Inhambane", "south_reach"), "GJ 54.1": ("Ribeiro's Star", "south_reach"), "GJ 541": ("Chandra Deep", "east_reach"), "GJ 542": ("Kopparhytta", "west_reach"), "GJ 546": ("Eerste Wacht", "deep_frontier"), "GJ 551": ("Proxima", "core"), "GJ 555": ("Tanaka Station", "east_reach"), "GJ 556": ("Recanto", "deep_frontier"), "GJ 559B": ("ACB", "core"), "GJ 566A": ("Haodu", "east_reach"), "GJ 566B": ("Confluência", "south_reach"), "GJ 567": ("Fragua", "south_reach"), "GJ 570A": ("Bastion", "core"), "GJ 575A": ("Ratnagiri", "north_reach"), "GJ 581": ("Rødvik", "west_reach"), "GJ 588": ("Harrowfield", "north_reach"), "GJ 598": ("Helle Wacht", "deep_frontier"), "GJ 601A": ("Ostmark", "west_reach"), "GJ 603": ("Mossbank", "north_reach"), "GJ 615": ("Dunkelholz", "west_reach"), "GJ 624": ("Halvøy", "west_reach"), "GJ 625": ("Songjiang", "east_reach"), "GJ 628": ("Brennan's Drift", "west_reach"), "GJ 631": ("Bakshi Spur", "west_reach"), "GJ 635A": ("Stilmark", "west_reach"), "GJ 635B": ("Clearwater Station", "east_reach"), "GJ 638": ("Greymantle", "north_reach"), "GJ 643": ("Marktfeld", "west_reach"), "GJ 644A": ("Mavela", "south_reach"), "GJ 644C": ("Moçâmedes", "south_reach"), "GJ 653": ("Dunmore", "north_reach"), "GJ 660A": ("Seongho", "east_reach"), "GJ 661A": ("Crown's Hollow", "north_reach"), "GJ 661B": ("Breëvlei", "north_reach"), "GJ 663A": ("Johannsen's Star", "north_reach"), "GJ 664": ("Caldwell Point", "west_reach"), "GJ 667A": ("New Ballarat", "north_reach"), "GJ 667B": ("Kampala Gate", "north_reach"), "GJ 667C": ("Gliese 667", "east_reach"), "GJ 66A": ("p Eridani", "east_reach"), "GJ 66B": ("Voss", "west_reach"), "GJ 67": ("Roça Nova", "south_reach"), "GJ 672": ("Nova Estrada", "deep_frontier"), "GJ 673": ("Lindström Station", "north_reach"), "GJ 674": ("Meridian", "core"), "GJ 675": ("Neugrund", "deep_frontier"), "GJ 680": ("Carnarvon", "north_reach"), "GJ 682": ("Taishan", "east_reach"), "GJ 684A": ("Sonnenkap", "deep_frontier"), "GJ 684B": ("Paso Largo", "deep_frontier"), "GJ 687": ("Matsue", "east_reach"), "GJ 688": ("Aguas Bravas", "south_reach"), "GJ 693": ("Mwangaza", "south_reach"), "GJ 699": ("Barnard's Star", "core"), "GJ 702B": ("Prometheus", "core"), "GJ 706": ("Ngata's Star", "north_reach"), "GJ 716": ("Murchison", "north_reach"), "GJ 71": ("Tau Ceti", "core"), "GJ 713A": ("Samuels Reach", "east_reach"), "GJ 722": ("Skuilplek", "deep_frontier"), "GJ 725B": ("Struve", "core"), "GJ 729": ("Ross 154", "west_reach"), "GJ 737B": ("Punta del Sol", "south_reach"), "GJ 75": ("Enugu", "north_reach"), "GJ 752A": ("Ribeirão", "south_reach"), "GJ 754": ("Miryang", "east_reach"), "GJ 764": ("Nova Roma", "core"), "GJ 768": ("Altair", "south_reach"), "GJ 770": ("Rekohu", "north_reach"), "GJ 771A": ("Alshain", "west_reach"), "GJ 775": ("Randsholm", "west_reach"), "GJ 780": ("Puerto Rivera", "north_reach"), "GJ 783A": ("Aurelius", "core"), "GJ 784": ("Kovács Loop", "east_reach"), "GJ 785": ("Porto Fechado", "south_reach"), "GJ 79": ("Oshima", "east_reach"), "GJ 798": ("Brückenau", "west_reach"), "GJ 807": ("Grenzstein", "west_reach"), "GJ 809": ("Seiðrmaðr", "west_reach"), "GJ 820B": ("Cygni B", "core"), "GJ 825": ("Lacaille", "north_reach"), "GJ 827": ("Solandar", "south_reach"), "GJ 829": ("Posto Avançado", "south_reach"), "GJ 83.1": ("Farol", "south_reach"), "GJ 832": ("Hansestadt", "west_reach"), "GJ 845": ("Cairn", "north_reach"), "GJ 848": ("Shimanami", "east_reach"), "GJ 86": ("Kumasi", "south_reach"), "GJ 860A": ("Kruger 60", "south_reach"), "GJ 860B": ("Changwon", "east_reach"), "GJ 866A": ("EZ Aqr", "east_reach"), "GJ 873": ("Ashworth Junction", "north_reach"), "GJ 875": ("The Narrows", "deep_frontier"), "GJ 876": ("Koskinen's Reach", "north_reach"), "GJ 877": ("Holmgren's Passage", "north_reach"), "GJ 879": ("Singh's Landing", "north_reach"), "GJ 880": ("Holgersen's Landing", "north_reach"), "GJ 884": ("Matamba", "south_reach"), "GJ 887": ("Lacaille", "core"), "GJ 892": ("Cairnside", "deep_frontier"), "GJ 896A": ("Nowa Huta", "south_reach"), "GJ 905": ("Isibaya", "south_reach"), "GJ 908": ("Thornfield Station", "east_reach"), "GJ 909A": ("Tam Giang", "east_reach"), "GJ 92": ("Pollux", "south_reach"), "GJ 95": ("Caledonia's End", "north_reach"), } # Brand category -> description template suffix and tags hint CATEGORY_META = { "terroir": ("regional specialty producer", ["terroir", "scarcity_class_rare"]), "heritage_craft": ("heritage craft manufacturer", ["heritage_craft", "artisan"]), "tech_premium": ("precision technology manufacturer", ["tech_premium", "electronics"]), "cultural": ("cultural content producer", ["cultural", "entertainment"]), "service_premium": ("premium service provider", ["service_premium"]), "commodity_branded": ("consumer goods manufacturer", ["commodity_branded", "consumer_goods"]), "design_heritage": ("design and furniture manufacturer", ["design_heritage", "consumer_goods"]), "platform_catalogue": ("media platform and catalogue service", ["platform_catalogue", "entertainment"]), } def slug_to_title(slug: str) -> str: """Convert a slug like 'my-corp-name' to 'My Corp Name'.""" return " ".join(w.capitalize() for w in slug.replace("-", " ").split()) def parse_brands_toml(path: Path) -> dict: """Parse brands.toml and return dict of corp_id -> {origin_system, brand_category}.""" corps = {} current_corp = None current_system = None current_category = None with open(path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if line == "[[brand_products]]": # Save previous if current_corp and current_system: if current_corp not in corps: corps[current_corp] = { "origin_system": current_system, "brand_category": current_category, } current_corp = None current_system = None current_category = None elif line.startswith("corp_id = "): current_corp = line.split("=", 1)[1].strip().strip('"') elif line.startswith("origin_system = "): current_system = line.split("=", 1)[1].strip().strip('"') elif line.startswith("brand_category = "): current_category = line.split("=", 1)[1].strip().strip('"') # Save last if current_corp and current_system: if current_corp not in corps: corps[current_corp] = { "origin_system": current_system, "brand_category": current_category, } return corps def generate_stub(slug: str, origin_system: str, brand_category: str) -> str: title = slug_to_title(slug) sys_info = SYSTEMS.get(origin_system, (origin_system, "unknown")) sys_name, corridor = sys_info cat_meta = CATEGORY_META.get(brand_category, ("commercial enterprise", [])) cat_desc, cat_tags = cat_meta corridor_currency = "tractus" if corridor in ("west_reach",): corridor_currency = "mark" elif corridor == "core": corridor_currency = "tractus" headquarters = f"{sys_name} ({origin_system})" tags_list = [brand_category, corridor, corridor_currency] + cat_tags # Deduplicate while preserving order seen = set() tags = [] for t in tags_list: if t not in seen: seen.add(t) tags.append(t) tags_str = "[" + ", ".join(tags) + "]" description = f"{title} — {cat_desc} based in {sys_name}, {corridor.replace('_', ' ')}" return f"""--- title: "{title}" description: "{description}" slug: {slug} category: corporation status: canonical created: 2026-04-19 updated: 2026-04-19 scope: regional faction_type: economic headquarters: {headquarters} tags: {tags_str} decision_refs: [D-189] cross_refs: [] --- # {title} **Type:** Corporation — {cat_desc.title()} **Status:** Canonical **Scope:** Regional — {corridor.replace('_', ' ')} operations **Headquarters:** {headquarters} {title} is a {cat_desc} operating out of {sys_name} ({origin_system}). Details to be expanded. """ def main(): parser = argparse.ArgumentParser( description="Generate minimal wiki stub pages for corps in brands.toml." ) parser.add_argument( "--dry-run", action="store_true", help="Print which stubs would be created without writing any files.", ) args = parser.parse_args() corps = parse_brands_toml(BRANDS_TOML) print(f"Found {len(corps)} unique corp_ids in brands.toml") if args.dry_run: print("(dry-run — no files will be written)\n") created = 0 skipped = 0 for slug, meta in sorted(corps.items()): stub_path = CORPS_DIR / f"{slug}.md" if stub_path.exists(): skipped += 1 continue if args.dry_run: print(f" (dry-run) Would create: {slug}.md") else: content = generate_stub(slug, meta["origin_system"], meta["brand_category"]) stub_path.write_text(content, encoding="utf-8") print(f" created: {slug}.md") created += 1 action = "Would create" if args.dry_run else "Created" print(f"\nDone. {action} {created} stubs, skipped {skipped} existing.") if __name__ == "__main__": main()