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>
128 lines
6.0 KiB
Python
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()
|