RECORDGOD/app/packs.py
type-two db1788bfe5 feat(app): payments/packs/discogs-mp/dealgod modules + admin, shop, sales route work
Batch-committing the in-flight app work (all modules compile clean):
- app/payments.py, app/packs.py, app/discogs_mp.py, app/dealgod.py (new)
- admin_routes, main, navigator/sales/shop routes, site/search.html
- customer_woo_sync + disc_sync tweaks; test_dealgod_supply, test_startup_ddl

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 23:07:28 +10:00

128 lines
6.0 KiB
Python

"""The GODVERSE modules — canonical naming per GODVERSE.md (the brand bible; lanes name to match it).
StoreGod is the BASE store-management app (vanilla: inventory, POS, store ops, universal identify+value over the
BaseGod brain — any goods). Each MODULE (RecordGod, ToolGod, VaultGod…) is a specialist ADD-ON unlocked on top,
deepening the store for one shopper-world. A store unlocks the modules for what it sells — MULTIPLE at once
(a pawnbroker enables records + tools + jewellery). Enabled via `STOREGOD_MODULES` (csv); the key's scope/features
on the DealGod side gate access + billing. Branding: one module → that BrandGod; many/none → "StoreGod".
Umbrella by shopper-world + data source (GODVERSE.md §naming): media (TMDb video), tech (electronics),
vault (graded collectibles = comics+cards+collectible toys), kid (family/kids resale). Cross-cutting FEATURES
(scangod/meltgod/stagegod/comps/lore/history) are capabilities, NOT modules — they ride on a key's `features`.
"""
import os
from dataclasses import dataclass
BASE_BRAND = "StoreGod"
BASE_FEATURES = ("identify", "value", "inventory", "pos", "supply")
@dataclass(frozen=True)
class Module:
key: str # STOREGOD_MODULES token
brand: str # BrandGod name shown when this is the only module unlocked
domains: tuple # the DealGod scope domain(s) it unlocks (umbrellas carry several)
condition_vocab: str # grading vocab
adds: tuple # specialist features layered onto the base
threed: bool
# Roster per GODVERSE.md. domains use DG1's published scope vocab
# (apparel·books·comics·computing·games·instruments·jewellery·music·phones·photo·tcg·tools·toys·video).
MODULES = {
"records": Module("records", "RecordGod", ("music",), "goldmine", ("lore", "discogs_intake", "wishlist", "heal"), True),
"media": Module("media", "MediaGod", ("video",), "disc", ("lore",), False),
"books": Module("books", "BookGod", ("books",), "generic", ("lore",), False),
"tech": Module("tech", "TechGod", ("computing", "phones", "photo"), "grade", ("comps", "lore"), False),
"tools": Module("tools", "ToolGod", ("tools",), "tested", ("lore",), False),
"play": Module("play", "PlayGod", ("instruments",), "gear", ("lore",), True),
"games": Module("games", "GameGod", ("games",), "CIB", ("comps", "lore"), False),
"jewellery": Module("jewellery", "JewelGod", ("jewellery",), "hallmark", ("melt",), False),
"vault": Module("vault", "VaultGod", ("comics", "tcg", "toys"), "graded", ("lore", "comps"), False),
"kids": Module("kids", "KidGod", ("toys", "apparel"), "generic", ("comps",), False),
"kicks": Module("kicks", "KicksGod", ("apparel",), "grade", ("comps",), False),
"drip": Module("drip", "DripGod", ("apparel",), "generic", ("comps",), False),
}
# legacy STOREGOD_PACK / module aliases → canonical key (back-compat for running containers + folded gods)
ALIASES = {"store": None, "": None, "instruments": "play", "computing": "tech", "phones": "tech",
"comics": "vault", "cards": "vault", "tcg": "vault", "toys": "vault", "jewel": "jewellery"}
def _resolve(token: str):
token = ALIASES.get(token, token)
return MODULES.get(token) if token else None
def active_modules() -> list:
"""The unlocked modules. STOREGOD_MODULES (csv) preferred; falls back to legacy single STOREGOD_PACK
('store'/'' = vanilla base, no module). Unknown/folded tokens resolve via ALIASES."""
env = os.getenv("STOREGOD_MODULES")
tokens = ([t.strip() for t in env.split(",")] if env is not None
else [os.getenv("STOREGOD_PACK", "records")])
out, seen = [], set()
for t in tokens:
m = _resolve(t.strip())
if m and m.key not in seen:
out.append(m)
seen.add(m.key)
return out
def brand() -> str:
m = active_modules()
return m[0].brand if len(m) == 1 else BASE_BRAND
def active_scope() -> list:
m = active_modules()
if not m:
return ["*"] # no module = StoreGod base = every domain
return sorted({d for mod in m for d in mod.domains}) # union of all unlocked modules' domains
def features() -> set:
f = set(BASE_FEATURES)
for m in active_modules():
f |= set(m.adds)
return f
def has_module(key: str) -> bool:
mod = _resolve(key)
return bool(mod) and any(m.key == mod.key for m in active_modules())
def has_feature(feat: str) -> bool:
return feat in features()
def threed() -> bool:
return any(m.threed for m in active_modules())
def info() -> dict:
return {"brand": brand(), "base": BASE_BRAND, "modules": [m.key for m in active_modules()],
"scope": active_scope(), "features": sorted(features()), "threed": threed()}
def _selfcheck():
assert "identify" in features() and "value" in features() # base always on
os.environ["STOREGOD_MODULES"] = "records,tools"
assert has_module("records") and has_module("tools")
assert brand() == "StoreGod" and set(active_scope()) == {"music", "tools"}
os.environ["STOREGOD_MODULES"] = "records"
assert brand() == "RecordGod" and "discogs_intake" in features()
os.environ["STOREGOD_MODULES"] = "vault" # umbrella → multi-domain
assert brand() == "VaultGod" and set(active_scope()) == {"comics", "tcg", "toys"}
os.environ["STOREGOD_MODULES"] = "comics" # folded alias → vault
assert has_module("vault")
os.environ["STOREGOD_MODULES"] = ""
assert brand() == "StoreGod" and active_scope() == ["*"]
del os.environ["STOREGOD_MODULES"]
print("ok")
if __name__ == "__main__":
_selfcheck()