diff --git a/server/data/systems-schema.sql b/server/data/systems-schema.sql index 4e45a8d3b..cca51818f 100644 --- a/server/data/systems-schema.sql +++ b/server/data/systems-schema.sql @@ -276,6 +276,60 @@ CREATE TABLE IF NOT EXISTS corp_presence ( PRIMARY KEY (corp_id, location_id) ); +-- Brand layer (D-189) — administered-price layer above commodity tâtonnement. +-- Brands consume commodities as demand nodes; they are NOT commodities (D-185). + +CREATE TABLE IF NOT EXISTS brand_products ( + brand_product_id TEXT PRIMARY KEY, + corp_id TEXT NOT NULL REFERENCES corporations(corp_id), + product_name TEXT NOT NULL, + brand_category TEXT NOT NULL, -- terroir|heritage_craft|tech_premium|cultural|service_premium|commodity_branded|design_heritage|platform_catalogue + value_trajectory TEXT NOT NULL, -- appreciating|depreciating|timeless + scarcity_class TEXT NOT NULL, -- capped|constrained|scalable|unlimited + product_subcategory TEXT, + base_premium_multiplier REAL NOT NULL DEFAULT 1.0, + premium_floor REAL NOT NULL DEFAULT 0.0, + origin_system TEXT REFERENCES star_systems(system_id), + terroir_locked INTEGER NOT NULL DEFAULT 0, -- boolean: production bound to origin_system + currency_denomination TEXT NOT NULL DEFAULT 'tractus', -- tractus|mark|mixed|sol_adjacent + shadow_viable INTEGER NOT NULL DEFAULT 0, -- boolean: circulates in shadow economy + brand_tier TEXT NOT NULL, -- halo|volume + halo_brand_id TEXT REFERENCES brand_products(brand_product_id), -- NULL for halo tier + updated_at TEXT DEFAULT (datetime('now')) +); + +CREATE TABLE IF NOT EXISTS brand_inputs ( + brand_product_id TEXT NOT NULL REFERENCES brand_products(brand_product_id), + commodity_id TEXT NOT NULL REFERENCES commodities(commodity_id), + quantity REAL NOT NULL, + PRIMARY KEY (brand_product_id, commodity_id) +); + +-- Fiscal parameters per star system (D-189 section 5 + section 6) +CREATE TABLE IF NOT EXISTS system_fiscal ( + system_id TEXT PRIMARY KEY REFERENCES star_systems(system_id), + corp_tax_rate REAL NOT NULL DEFAULT 0.22, -- 0.0–1.0 + collection_efficiency REAL NOT NULL DEFAULT 1.0, -- derived: 1.0 - shadow_intensity × 0.6 + updated_at TEXT DEFAULT (datetime('now')) +); + +-- Phase 2: passive corp health tracking (D-189 section 8) +CREATE TABLE IF NOT EXISTS corp_financial_state ( + corp_id TEXT PRIMARY KEY REFERENCES corporations(corp_id), + health_metric REAL NOT NULL DEFAULT 1.0, -- 0.0 (distressed) to 1.0 (healthy) + updated_at TEXT DEFAULT (datetime('now')) +); + +-- Phase 3 lifecycle state machine stub (D-189 section 8) — schema correct, not driven yet +CREATE TABLE IF NOT EXISTS corp_lifecycle_events ( + event_id INTEGER PRIMARY KEY AUTOINCREMENT, + corp_id TEXT NOT NULL REFERENCES corporations(corp_id), + event_type TEXT NOT NULL, -- Founded|Growing|Active|Distressed|Acquired|Dissolved + event_tick INTEGER NOT NULL DEFAULT 0, + event_data TEXT, -- JSON blob for future lifecycle detail + created_at TEXT DEFAULT (datetime('now')) +); + -- Indexes -- astronomical_id removed: system_id IS the GJ catalog number CREATE INDEX IF NOT EXISTS idx_star_systems_sector ON star_systems(geographic_sector); @@ -304,3 +358,9 @@ CREATE INDEX IF NOT EXISTS idx_production_chains_output ON production_chains(out CREATE INDEX IF NOT EXISTS idx_chain_inputs_commodity ON chain_inputs(input_commodity_id); CREATE INDEX IF NOT EXISTS idx_corp_presence_corp ON corp_presence(corp_id); CREATE INDEX IF NOT EXISTS idx_corp_presence_location ON corp_presence(location_id); +-- Brand layer indexes (D-189 section 5 — composite for UI queries) +CREATE INDEX IF NOT EXISTS idx_brand_products_corp_category ON brand_products(corp_id, brand_category); +CREATE INDEX IF NOT EXISTS idx_brand_products_origin ON brand_products(origin_system); +CREATE INDEX IF NOT EXISTS idx_brand_products_tier ON brand_products(brand_tier); +CREATE INDEX IF NOT EXISTS idx_brand_inputs_commodity ON brand_inputs(commodity_id); +CREATE INDEX IF NOT EXISTS idx_corp_lifecycle_events_corp ON corp_lifecycle_events(corp_id); diff --git a/server/data/systems.db b/server/data/systems.db index 178080bf0..95151e106 100644 Binary files a/server/data/systems.db and b/server/data/systems.db differ diff --git a/tooling/economy-db/import_economics.py b/tooling/economy-db/import_economics.py index 5b318a707..2ef3d697c 100755 --- a/tooling/economy-db/import_economics.py +++ b/tooling/economy-db/import_economics.py @@ -39,6 +39,7 @@ COMMODITIES_TOML = REPO_ROOT / "wiki" / "economics" / "commodities.toml" CHAINS_TOML = REPO_ROOT / "wiki" / "economics" / "production_chains.toml" SCHEMA_SQL = REPO_ROOT / "server" / "data" / "systems-schema.sql" CORPORATIONS_DIR = REPO_ROOT / "wiki" / "corporations" +BRANDS_TOML = REPO_ROOT / "wiki" / "economics" / "corporations" / "brands.toml" # --------------------------------------------------------------------------- @@ -48,6 +49,61 @@ CORPORATIONS_DIR = REPO_ROOT / "wiki" / "corporations" MIGRATION_SQL = """ -- Economics tables (idempotent — safe to re-run) +-- Brand layer tables (D-189, #827) +CREATE TABLE IF NOT EXISTS brand_products ( + brand_product_id TEXT PRIMARY KEY, + corp_id TEXT NOT NULL REFERENCES corporations(corp_id), + product_name TEXT NOT NULL, + brand_category TEXT NOT NULL, + value_trajectory TEXT NOT NULL, + scarcity_class TEXT NOT NULL, + product_subcategory TEXT, + base_premium_multiplier REAL NOT NULL DEFAULT 1.0, + premium_floor REAL NOT NULL DEFAULT 0.0, + origin_system TEXT REFERENCES star_systems(system_id), + terroir_locked INTEGER NOT NULL DEFAULT 0, + currency_denomination TEXT NOT NULL DEFAULT 'tractus', + shadow_viable INTEGER NOT NULL DEFAULT 0, + brand_tier TEXT NOT NULL, + halo_brand_id TEXT REFERENCES brand_products(brand_product_id), + updated_at TEXT DEFAULT (datetime('now')) +); + +CREATE TABLE IF NOT EXISTS brand_inputs ( + brand_product_id TEXT NOT NULL REFERENCES brand_products(brand_product_id), + commodity_id TEXT NOT NULL REFERENCES commodities(commodity_id), + quantity REAL NOT NULL, + PRIMARY KEY (brand_product_id, commodity_id) +); + +CREATE TABLE IF NOT EXISTS system_fiscal ( + system_id TEXT PRIMARY KEY REFERENCES star_systems(system_id), + corp_tax_rate REAL NOT NULL DEFAULT 0.22, + collection_efficiency REAL NOT NULL DEFAULT 1.0, + updated_at TEXT DEFAULT (datetime('now')) +); + +CREATE TABLE IF NOT EXISTS corp_financial_state ( + corp_id TEXT PRIMARY KEY REFERENCES corporations(corp_id), + health_metric REAL NOT NULL DEFAULT 1.0, + updated_at TEXT DEFAULT (datetime('now')) +); + +CREATE TABLE IF NOT EXISTS corp_lifecycle_events ( + event_id INTEGER PRIMARY KEY AUTOINCREMENT, + corp_id TEXT NOT NULL REFERENCES corporations(corp_id), + event_type TEXT NOT NULL, + event_tick INTEGER NOT NULL DEFAULT 0, + event_data TEXT, + created_at TEXT DEFAULT (datetime('now')) +); + +CREATE INDEX IF NOT EXISTS idx_brand_products_corp_category ON brand_products(corp_id, brand_category); +CREATE INDEX IF NOT EXISTS idx_brand_products_origin ON brand_products(origin_system); +CREATE INDEX IF NOT EXISTS idx_brand_products_tier ON brand_products(brand_tier); +CREATE INDEX IF NOT EXISTS idx_brand_inputs_commodity ON brand_inputs(commodity_id); +CREATE INDEX IF NOT EXISTS idx_corp_lifecycle_events_corp ON corp_lifecycle_events(corp_id); + CREATE TABLE IF NOT EXISTS gate_links ( from_system_id TEXT NOT NULL REFERENCES star_systems(system_id), to_system_id TEXT NOT NULL REFERENCES star_systems(system_id), @@ -669,6 +725,206 @@ def _validate_system_coverage( ] +# --------------------------------------------------------------------------- +# Brand layer import (D-189, #827) +# --------------------------------------------------------------------------- + +VALID_BRAND_CATEGORIES = { + "terroir", "heritage_craft", "tech_premium", "cultural", + "service_premium", "commodity_branded", "design_heritage", "platform_catalogue", +} +VALID_VALUE_TRAJECTORIES = {"appreciating", "depreciating", "timeless"} +VALID_SCARCITY_CLASSES = {"capped", "constrained", "scalable", "unlimited"} +VALID_BRAND_TIERS = {"halo", "volume"} +VALID_CURRENCY_DENOMINATIONS = {"tractus", "mark", "mixed", "sol_adjacent"} + + +def import_brands( + conn: sqlite3.Connection, dry_run: bool +) -> tuple[int, int]: + """Import brand_products and brand_inputs from wiki/economics/corporations/brands.toml. + + Returns (n_products, n_inputs). + """ + if not BRANDS_TOML.exists(): + print(" warning: brands.toml not found — brand layer skipped") + return 0, 0 + + with open(BRANDS_TOML, "rb") as f: + data = tomllib.load(f) + + products = data.get("brand_products", []) + inputs = data.get("brand_inputs", []) + + product_rows = [] + for p in products: + product_rows.append(( + p["brand_product_id"], + p["corp_id"], + p["product_name"], + p["brand_category"], + p["value_trajectory"], + p["scarcity_class"], + p.get("product_subcategory"), + p.get("base_premium_multiplier", 1.0), + p.get("premium_floor", 0.0), + p.get("origin_system"), + int(p.get("terroir_locked", False)), + p.get("currency_denomination", "tractus"), + int(p.get("shadow_viable", False)), + p["brand_tier"], + p.get("halo_brand_id"), + )) + + input_rows = [] + for inp in inputs: + input_rows.append(( + inp["brand_product_id"], + inp["commodity_id"], + inp["quantity"], + )) + + if not dry_run: + conn.executemany( + """INSERT OR REPLACE INTO brand_products ( + brand_product_id, corp_id, product_name, brand_category, + value_trajectory, scarcity_class, product_subcategory, + base_premium_multiplier, premium_floor, origin_system, + terroir_locked, currency_denomination, shadow_viable, + brand_tier, halo_brand_id + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", + product_rows, + ) + conn.executemany( + """INSERT OR REPLACE INTO brand_inputs + (brand_product_id, commodity_id, quantity) VALUES (?, ?, ?)""", + input_rows, + ) + + return len(product_rows), len(input_rows) + + +def import_system_fiscal(conn: sqlite3.Connection, dry_run: bool) -> int: + """Populate system_fiscal from star_systems shadow_economy data. + + collection_efficiency = 1.0 - shadow_economy_intensity × 0.6 (D-189 section 6). + shadow_economy_intensity comes from the shadow_economy.toml pipeline or defaults to 0. + Uses corp_tax_rate = 0.22 (default) for all systems in Phase 2. + """ + inhabited = conn.execute(""" + SELECT ss.system_id, COALESCE(se.population, 0) + FROM star_systems ss + LEFT JOIN system_economy se ON ss.system_id = se.system_id + WHERE ss.inhabited_planet_count > 0 OR se.population > 0 + ORDER BY ss.system_id + """).fetchall() + + rows = [] + for system_id, _pop in inhabited: + # Phase 2: shadow_economy_intensity not yet per-system in DB. + # Default collection_efficiency to 0.85 (mid-reach average) until + # shadow_economy.toml pipeline is wired (Phase 3). + rows.append((system_id, 0.22, 0.85)) + + if not dry_run: + conn.executemany( + """INSERT OR IGNORE INTO system_fiscal + (system_id, corp_tax_rate, collection_efficiency) VALUES (?, ?, ?)""", + rows, + ) + + return len(rows) + + +def validate_brands(conn: sqlite3.Connection) -> list[str]: + """Brand layer structural validation rules V-B01 through V-B05. + + V-B01: Every brand_products row has a valid corp_id (FK to corporations). + V-B02: Every brand_inputs row has valid brand_product_id and commodity_id FKs. + V-B03: Every halo brand has at least one brand_inputs entry (demand stub must consume). + V-B04: Every volume tier must reference an existing halo brand_product_id. + V-B05: No brand_product_id is used as halo_brand_id by a non-volume-tier product. + """ + errors: list[str] = [] + + # V-B01: brand_products → corporations FK + orphan_corps = conn.execute(""" + SELECT bp.brand_product_id, bp.corp_id + FROM brand_products bp + LEFT JOIN corporations c ON bp.corp_id = c.corp_id + WHERE c.corp_id IS NULL + """).fetchall() + for pid, corp_id in orphan_corps: + errors.append( + f"V-B01: brand_product '{pid}' references unknown corp_id '{corp_id}'" + ) + + # V-B02: brand_inputs → brand_products and brand_inputs → commodities FKs + orphan_inputs_bp = conn.execute(""" + SELECT bi.brand_product_id, bi.commodity_id + FROM brand_inputs bi + LEFT JOIN brand_products bp ON bi.brand_product_id = bp.brand_product_id + WHERE bp.brand_product_id IS NULL + """).fetchall() + for pid, cid in orphan_inputs_bp: + errors.append( + f"V-B02: brand_inputs row ({pid}, {cid}) references unknown brand_product_id" + ) + + orphan_inputs_comm = conn.execute(""" + SELECT bi.brand_product_id, bi.commodity_id + FROM brand_inputs bi + LEFT JOIN commodities c ON bi.commodity_id = c.commodity_id + WHERE c.commodity_id IS NULL + """).fetchall() + for pid, cid in orphan_inputs_comm: + errors.append( + f"V-B02: brand_inputs row ({pid}, {cid}) references unknown commodity_id '{cid}'" + ) + + # V-B03: every halo brand has at least one brand_inputs entry + halo_no_inputs = conn.execute(""" + SELECT bp.brand_product_id + FROM brand_products bp + WHERE bp.brand_tier = 'halo' + AND bp.brand_product_id NOT IN (SELECT brand_product_id FROM brand_inputs) + """).fetchall() + for (pid,) in halo_no_inputs: + errors.append( + f"V-B03: halo brand '{pid}' has no brand_inputs entries " + f"(must consume at least one commodity as a demand node)" + ) + + # V-B04: volume tiers reference valid halo_brand_id + volume_bad_halo = conn.execute(""" + SELECT bp.brand_product_id, bp.halo_brand_id + FROM brand_products bp + WHERE bp.brand_tier = 'volume' + AND (bp.halo_brand_id IS NULL + OR bp.halo_brand_id NOT IN (SELECT brand_product_id FROM brand_products)) + """).fetchall() + for pid, halo_id in volume_bad_halo: + errors.append( + f"V-B04: volume brand '{pid}' has invalid halo_brand_id '{halo_id}'" + ) + + # V-B05: halo_brand_id must only point to halo-tier products + halo_points_to_non_halo = conn.execute(""" + SELECT child.brand_product_id, child.halo_brand_id, parent.brand_tier + FROM brand_products child + JOIN brand_products parent ON child.halo_brand_id = parent.brand_product_id + WHERE child.brand_tier = 'volume' + AND parent.brand_tier != 'halo' + """).fetchall() + for child_id, halo_id, parent_tier in halo_points_to_non_halo: + errors.append( + f"V-B05: volume brand '{child_id}' points to '{halo_id}' " + f"which has brand_tier='{parent_tier}', not 'halo'" + ) + + return errors + + # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- @@ -699,7 +955,7 @@ def main(): conn.execute("PRAGMA foreign_keys=ON") # 1. Migrate schema - print(" [1/8] Schema migration...") + print(" [1/10] Schema migration...") for table, col, col_type in COLUMN_MIGRATIONS: _add_column(conn, table, col, col_type) conn.executescript(MIGRATION_SQL) @@ -709,40 +965,45 @@ def main(): # corp_presence cleared here; corporations table is append-only (never cleared) if not args.dry_run: conn.execute("DELETE FROM corp_presence") + conn.execute("DELETE FROM brand_inputs") + conn.execute("DELETE FROM brand_products") + conn.execute("DELETE FROM system_fiscal") + conn.execute("DELETE FROM corp_financial_state") + conn.execute("DELETE FROM corp_lifecycle_events") conn.execute("DELETE FROM chain_inputs") conn.execute("DELETE FROM production_chains") conn.execute("DELETE FROM commodities") conn.execute("DELETE FROM gate_links") # 2. Gate links - print(" [2/8] Importing gate links...") + print(" [2/10] Importing gate links...") n_links = import_gate_links(conn, args.dry_run) print(f" {n_links} rows (bidirectional)") # 3. Commodities - print(" [3/8] Importing commodities...") + print(" [3/10] Importing commodities...") n_commodities = import_commodities(conn, args.dry_run) print(f" {n_commodities} commodities") # 4. Production chains - print(" [4/8] Importing production chains...") + print(" [4/10] Importing production chains...") n_chains, n_inputs = import_chains(conn, args.dry_run) print(f" {n_chains} chains, {n_inputs} inputs") # 5. Currency zones - print(" [5/8] Setting currency zones...") + print(" [5/10] Setting currency zones...") zones = set_currency_zones(conn, args.dry_run) for zone, count in sorted(zones.items()): print(f" {zone}: {count}") # 6. Gate energy connectivity (D-186) — must run after currency zones - print(" [6/8] Setting gate energy connectivity...") + print(" [6/10] Setting gate energy connectivity...") energy = set_gate_energy(conn, args.dry_run) for label, count in sorted(energy.items()): print(f" {label}: {count}") # 7. Sync corporations from wiki (D-182: hard error on name divergence) - print(" [7/8] Syncing corporations...") + print(" [7/10] Syncing corporations...") corp_errors = sync_corporations(conn, wiki_corps, args.dry_run) if corp_errors: print(f" ERRORS ({len(corp_errors)}) — name divergence detected (D-182):") @@ -755,17 +1016,28 @@ def main(): print(f" {n_db_corps} corporations in DB ({len(wiki_corps)} from wiki)") # 8. Corp presence from wiki headquarters data - print(" [8/8] Importing corp presence...") + print(" [8/10] Importing corp presence...") commodity_ids = { r[0] for r in conn.execute("SELECT commodity_id FROM commodities").fetchall() } n_presence = import_corp_presence(conn, wiki_corps, commodity_ids, args.dry_run) print(f" {n_presence} corp_presence rows") + # 9. Brand products and inputs (D-189, #827) + print(" [9/10] Importing brand products and inputs...") + n_brands, n_brand_inputs = import_brands(conn, args.dry_run) + print(f" {n_brands} brand_products, {n_brand_inputs} brand_inputs") + + # 10. System fiscal parameters (D-189 section 6) + print(" [10/10] Populating system_fiscal...") + n_fiscal = import_system_fiscal(conn, args.dry_run) + print(f" {n_fiscal} system_fiscal rows") + # Validate structural integrity (FK, chain refs, chain completeness). # These errors indicate broken imported data — do NOT commit. print("\n Validating structural integrity...") struct_errors = validate(conn) + struct_errors.extend(validate_brands(conn)) if struct_errors: print(f" STRUCTURAL ERRORS ({len(struct_errors)}) — rolling back:") for e in struct_errors: @@ -773,7 +1045,7 @@ def main(): conn.close() sys.exit(1) else: - print(" FK integrity and chain completeness OK") + print(" FK integrity, chain completeness, and brand layer (V-B01–V-B05) OK") # Commit all imported data (corps, presence, etc.) before coverage check. # Coverage validation is a Phase 2 gate (D-175) — data should be persisted @@ -809,7 +1081,9 @@ def main(): conn.close() print(f"\n Done: {n_links} gate_links, {n_commodities} commodities, " - f"{n_chains} chains, {n_inputs} inputs, {n_presence} corp_presence\n") + f"{n_chains} chains, {n_inputs} inputs, {n_presence} corp_presence, " + f"{n_brands} brand_products, {n_brand_inputs} brand_inputs, " + f"{n_fiscal} system_fiscal\n") if __name__ == "__main__": diff --git a/wiki/economics/corporations/brands.toml b/wiki/economics/corporations/brands.toml new file mode 100644 index 000000000..40034a74e --- /dev/null +++ b/wiki/economics/corporations/brands.toml @@ -0,0 +1,276 @@ +# ========================================================================== +# Settled Reach — Brand Product Records (Phase 2 Demand Stubs) +# Source of truth for brand_products and brand_inputs tables. +# Compiled to systems.db via `make economy-db`. +# +# These 4 canonical brand corps register as commodity demand nodes in the +# tâtonnement simulation (D-185: brands consume commodities, not the reverse). +# Pricing model and cultural premium curves are Phase 3+ deliverables (D-189). +# +# Fields per [[brand_products]] entry: +# brand_product_id Unique slug (corp_id + product slug) +# corp_id Must match a slug in wiki/corporations/ +# product_name Display name +# brand_category terroir|heritage_craft|tech_premium|cultural| +# service_premium|commodity_branded|design_heritage|platform_catalogue +# value_trajectory appreciating|depreciating|timeless +# scarcity_class capped|constrained|scalable|unlimited +# product_subcategory Readable product type descriptor +# base_premium_multiplier Multiplier over raw commodity input cost (Phase 3 pricing) +# premium_floor Minimum administered price floor (Phase 3 pricing) +# origin_system GJ catalog ID — must match star_systems.system_id +# terroir_locked true: production cannot move from origin_system +# currency_denomination tractus|mark|mixed|sol_adjacent +# shadow_viable true: circulates in shadow economy +# brand_tier halo|volume +# halo_brand_id For volume tiers: brand_product_id of parent halo product +# +# Fields per [[brand_inputs]] entry: +# brand_product_id References a brand_products entry above +# commodity_id Must match commodities.toml +# quantity Demand coefficient (annual units consumed per production run) +# +# Decisions: D-185 (brands as demand nodes), D-189 (brand layer architecture), +# D-190 (volume calibration), D-182 (TOML pipeline) +# ========================================================================== + + +# ========================================================================== +# CALLOWAY DISTILLERY (north_reach — terroir, appreciating) +# Eleven distilleries, 400 years of production. GJ 3325. +# Inputs: grain (agricultural_produce) + water for single malt whisky. +# ========================================================================== + +[[brand_products]] +brand_product_id = "calloway-single-malt-halo" +corp_id = "calloway-distillery" +product_name = "Calloway Single Malt 25yr" +brand_category = "terroir" +value_trajectory = "appreciating" +scarcity_class = "capped" +product_subcategory = "aged_spirits" +base_premium_multiplier = 18.0 +premium_floor = 0.85 +origin_system = "GJ 3325" +terroir_locked = true +currency_denomination = "tractus" +shadow_viable = true +brand_tier = "halo" + +[[brand_inputs]] +brand_product_id = "calloway-single-malt-halo" +commodity_id = "agricultural_produce" +quantity = 0.12 # grain — ~12% of annual produce demand per distillery output unit + +[[brand_inputs]] +brand_product_id = "calloway-single-malt-halo" +commodity_id = "water" +quantity = 0.08 # distillation water + +[[brand_products]] +brand_product_id = "calloway-reserve-volume" +corp_id = "calloway-distillery" +product_name = "Calloway Reserve" +brand_category = "terroir" +value_trajectory = "appreciating" +scarcity_class = "constrained" +product_subcategory = "aged_spirits" +base_premium_multiplier = 4.5 +premium_floor = 0.30 +origin_system = "GJ 3325" +terroir_locked = true +currency_denomination = "tractus" +shadow_viable = true +brand_tier = "volume" +halo_brand_id = "calloway-single-malt-halo" + +[[brand_inputs]] +brand_product_id = "calloway-reserve-volume" +commodity_id = "agricultural_produce" +quantity = 0.55 # larger grain demand — volume tier drives commodity draw + +[[brand_inputs]] +brand_product_id = "calloway-reserve-volume" +commodity_id = "water" +quantity = 0.35 + + +# ========================================================================== +# VINS DE GRAND VIDE (west_reach — terroir, appreciating / timeless) +# Négociant cooperative, GJ 395. Wine production from châteaux network. +# Inputs: agricultural_produce (grapes) + water. +# ========================================================================== + +[[brand_products]] +brand_product_id = "vgv-premier-cru-halo" +corp_id = "vins-de-grand-vide" +product_name = "Grand Vide Premier Cru" +brand_category = "terroir" +value_trajectory = "appreciating" +scarcity_class = "capped" +product_subcategory = "fine_wine" +base_premium_multiplier = 12.0 +premium_floor = 0.70 +origin_system = "GJ 395" +terroir_locked = true +currency_denomination = "tractus" +shadow_viable = true +brand_tier = "halo" + +[[brand_inputs]] +brand_product_id = "vgv-premier-cru-halo" +commodity_id = "agricultural_produce" +quantity = 0.18 # estate grapes — limited châteaux harvest + +[[brand_inputs]] +brand_product_id = "vgv-premier-cru-halo" +commodity_id = "water" +quantity = 0.04 + +[[brand_products]] +brand_product_id = "vgv-standard-volume" +corp_id = "vins-de-grand-vide" +product_name = "Grand Vide Standard" +brand_category = "terroir" +value_trajectory = "timeless" +scarcity_class = "scalable" +product_subcategory = "wine" +base_premium_multiplier = 2.2 +premium_floor = 0.15 +origin_system = "GJ 395" +terroir_locked = false +currency_denomination = "tractus" +shadow_viable = false +brand_tier = "volume" +halo_brand_id = "vgv-premier-cru-halo" + +[[brand_inputs]] +brand_product_id = "vgv-standard-volume" +commodity_id = "agricultural_produce" +quantity = 0.80 # négociant aggregation — largest agricultural demand node + +[[brand_inputs]] +brand_product_id = "vgv-standard-volume" +commodity_id = "water" +quantity = 0.20 + + +# ========================================================================== +# THRDS (north_reach — heritage_craft, timeless) +# Cold-weather technical clothing cooperative, GJ 475. +# Inputs: textiles (brach fiber) + organic_compounds (dye, finish). +# ========================================================================== + +[[brand_products]] +brand_product_id = "thrds-origin-halo" +corp_id = "thrds" +product_name = "thrds Origin" +brand_category = "heritage_craft" +value_trajectory = "timeless" +scarcity_class = "constrained" +product_subcategory = "technical_clothing" +base_premium_multiplier = 6.0 +premium_floor = 0.50 +origin_system = "GJ 475" +terroir_locked = true +currency_denomination = "tractus" +shadow_viable = false +brand_tier = "halo" + +[[brand_inputs]] +brand_product_id = "thrds-origin-halo" +commodity_id = "textiles" +quantity = 0.30 # brach fiber — origin-specific weave + +[[brand_inputs]] +brand_product_id = "thrds-origin-halo" +commodity_id = "organic_compounds" +quantity = 0.08 # plant-derived dyes and finishing compounds + +[[brand_products]] +brand_product_id = "thrds-standard-volume" +corp_id = "thrds" +product_name = "thrds Standard" +brand_category = "heritage_craft" +value_trajectory = "timeless" +scarcity_class = "scalable" +product_subcategory = "technical_clothing" +base_premium_multiplier = 2.8 +premium_floor = 0.20 +origin_system = "GJ 475" +terroir_locked = false +currency_denomination = "tractus" +shadow_viable = false +brand_tier = "volume" +halo_brand_id = "thrds-origin-halo" + +[[brand_inputs]] +brand_product_id = "thrds-standard-volume" +commodity_id = "textiles" +quantity = 1.20 # primary textile demand driver (volume tier production) + +[[brand_inputs]] +brand_product_id = "thrds-standard-volume" +commodity_id = "organic_compounds" +quantity = 0.25 + + +# ========================================================================== +# BÍFRÖST MARMOR (Compact / north_reach — terroir, appreciating) +# Kvitfjell moon marble quarry, Nyrheim (GJ 3737). Nyrheim Cooperative subsidiary. +# Inputs: stone (raw marble) + chemicals (polishing, finishing agents). +# Shadow viable: Compact-adjacent, some Sol trade on interior luxury markets. +# ========================================================================== + +[[brand_products]] +brand_product_id = "bifrost-grade-a-halo" +corp_id = "bifrost-marmor" +product_name = "Kvitfjell Grade A" +brand_category = "terroir" +value_trajectory = "appreciating" +scarcity_class = "capped" +product_subcategory = "architectural_stone" +base_premium_multiplier = 22.0 +premium_floor = 1.20 +origin_system = "GJ 3737" +terroir_locked = true +currency_denomination = "mark" +shadow_viable = true +brand_tier = "halo" + +[[brand_inputs]] +brand_product_id = "bifrost-grade-a-halo" +commodity_id = "stone" +quantity = 0.40 # high-purity calcite marble extraction — geological scarcity + +[[brand_inputs]] +brand_product_id = "bifrost-grade-a-halo" +commodity_id = "chemicals" +quantity = 0.10 # precision polishing compounds + +[[brand_products]] +brand_product_id = "bifrost-commercial-volume" +corp_id = "bifrost-marmor" +product_name = "Kvitfjell Commercial" +brand_category = "terroir" +value_trajectory = "appreciating" +scarcity_class = "constrained" +product_subcategory = "architectural_stone" +base_premium_multiplier = 5.5 +premium_floor = 0.40 +origin_system = "GJ 3737" +terroir_locked = true +currency_denomination = "mixed" +shadow_viable = true +brand_tier = "volume" +halo_brand_id = "bifrost-grade-a-halo" + +[[brand_inputs]] +brand_product_id = "bifrost-commercial-volume" +commodity_id = "stone" +quantity = 1.50 # commercial-grade quarrying — volume demand node + +[[brand_inputs]] +brand_product_id = "bifrost-commercial-volume" +commodity_id = "chemicals" +quantity = 0.30