Step 4 (partial): drop dead pick_points table + game-system DB methods
Migration 020 drops cauldron_pick_points (game system stripped from /plan 2026-04-30 — all award_pick_points calls were already removed from server.py). The DB methods household_scoreboard, household_streak, and award_pick_points are deleted now too — they were unused dead code since the strip. delete_plan_slots no longer DELETEs from the dropped table (kept it minimal: just wipe the meal_plan_slots). DEFERRED from Step 4: dropping cauldron_foods. The 229 Sonnet-curated density rows + 2462 USDA seed rows are still useful raw material for a fuzzy backfill into cauldron_food_metadata (only 128 of Mealie's 2895 foods got densities matched on the first exact-name pass). Until we run a fuzzy backfill, holding onto cauldron_foods as a cold archive is cheaper than losing the data. Will revisit once the metadata catalog is more complete. Recovery from 020: revert this migration, restore the CREATE TABLE position-014 entry from db.py history. The points data was never meaningfully populated (jobs 1+3 award attempts were folded into the strip), so loss is essentially zero.
This commit is contained in:
parent
35d188a743
commit
950d245777
1 changed files with 12 additions and 114 deletions
126
cauldron/db.py
126
cauldron/db.py
|
|
@ -346,6 +346,15 @@ MIGRATIONS = [
|
||||||
FOREIGN KEY (job_id) REFERENCES cauldron_consolidate_jobs(id) ON DELETE CASCADE
|
FOREIGN KEY (job_id) REFERENCES cauldron_consolidate_jobs(id) ON DELETE CASCADE
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||||
""",
|
""",
|
||||||
|
# 020 — Drop the game-system pick-points table. Stripped from /plan
|
||||||
|
# 2026-04-30; tables and methods kept around as dead code for a few
|
||||||
|
# days to let it bake. Now that the simpler base flow is proven,
|
||||||
|
# the ledger comes out. Recovery path: revert this migration's number
|
||||||
|
# and re-add the table from db.py history; the points data was never
|
||||||
|
# widely populated anyway.
|
||||||
|
"""
|
||||||
|
DROP TABLE IF EXISTS cauldron_pick_points
|
||||||
|
""",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -569,94 +578,7 @@ class DB:
|
||||||
)
|
)
|
||||||
return cur.rowcount
|
return cur.rowcount
|
||||||
|
|
||||||
def household_scoreboard(self, household_id: int) -> list[dict]:
|
# --- plan slots (v0.3 A4) ----------------------------------------------
|
||||||
"""Per-user lock counts + pick-points + most recent lock time.
|
|
||||||
|
|
||||||
Three numbers per row:
|
|
||||||
wins — user-locked weeks (excludes auto-locks)
|
|
||||||
weeks_locked — alias for wins, preserved for older callers
|
|
||||||
points — sum of cauldron_pick_points for this user/household
|
|
||||||
|
|
||||||
Sort: points desc, then wins desc, then last_win desc — points are
|
|
||||||
the headline metric in v0.3 (every pick lands → matters daily).
|
|
||||||
|
|
||||||
We compute lock counts and points as separate scalar subqueries so
|
|
||||||
the JOIN doesn't blow up on the cartesian (members × plans × points).
|
|
||||||
"""
|
|
||||||
with self.conn() as c, c.cursor() as cur:
|
|
||||||
cur.execute(
|
|
||||||
"""
|
|
||||||
SELECT
|
|
||||||
u.authentik_sub AS sub,
|
|
||||||
u.email AS email,
|
|
||||||
u.display_name AS display_name,
|
|
||||||
COALESCE((
|
|
||||||
SELECT COUNT(*) FROM cauldron_meal_plans p
|
|
||||||
WHERE p.locked_by_sub = m.authentik_sub
|
|
||||||
AND p.household_id = m.household_id
|
|
||||||
AND p.locked_reason = 'user'
|
|
||||||
), 0) AS wins,
|
|
||||||
(
|
|
||||||
SELECT MAX(p.locked_at) FROM cauldron_meal_plans p
|
|
||||||
WHERE p.locked_by_sub = m.authentik_sub
|
|
||||||
AND p.household_id = m.household_id
|
|
||||||
AND p.locked_reason = 'user'
|
|
||||||
) AS last_win,
|
|
||||||
COALESCE((
|
|
||||||
SELECT SUM(pp.points) FROM cauldron_pick_points pp
|
|
||||||
WHERE pp.household_id = m.household_id
|
|
||||||
AND pp.authentik_sub = m.authentik_sub
|
|
||||||
), 0) AS points
|
|
||||||
FROM cauldron_household_members m
|
|
||||||
LEFT JOIN cauldron_users u
|
|
||||||
ON u.authentik_sub = m.authentik_sub
|
|
||||||
WHERE m.household_id = %s
|
|
||||||
ORDER BY points DESC, wins DESC, last_win DESC
|
|
||||||
""",
|
|
||||||
(household_id,),
|
|
||||||
)
|
|
||||||
out = []
|
|
||||||
for r in cur.fetchall():
|
|
||||||
d = dict(r)
|
|
||||||
d["points"] = int(d.get("points") or 0)
|
|
||||||
d["wins"] = int(d.get("wins") or 0)
|
|
||||||
d["weeks_locked"] = d["wins"]
|
|
||||||
out.append(d)
|
|
||||||
return out
|
|
||||||
|
|
||||||
def household_streak(self, household_id: int) -> dict | None:
|
|
||||||
"""Compute current win streak: walk back from most recent locked week,
|
|
||||||
counting consecutive weeks won by the same user. Returns
|
|
||||||
{sub, display_name, count} or None if no locks."""
|
|
||||||
with self.conn() as c, c.cursor() as cur:
|
|
||||||
cur.execute(
|
|
||||||
"""
|
|
||||||
SELECT p.week_start, p.locked_by_sub, u.display_name, u.email
|
|
||||||
FROM cauldron_meal_plans p
|
|
||||||
LEFT JOIN cauldron_users u ON u.authentik_sub = p.locked_by_sub
|
|
||||||
WHERE p.household_id = %s
|
|
||||||
AND p.locked_at IS NOT NULL
|
|
||||||
AND p.locked_reason = 'user'
|
|
||||||
ORDER BY p.week_start DESC
|
|
||||||
""",
|
|
||||||
(household_id,),
|
|
||||||
)
|
|
||||||
rows = cur.fetchall()
|
|
||||||
if not rows:
|
|
||||||
return None
|
|
||||||
leader = rows[0]["locked_by_sub"]
|
|
||||||
count = 0
|
|
||||||
for r in rows:
|
|
||||||
if r["locked_by_sub"] != leader:
|
|
||||||
break
|
|
||||||
count += 1
|
|
||||||
return {
|
|
||||||
"sub": leader,
|
|
||||||
"display_name": rows[0]["display_name"] or rows[0]["email"],
|
|
||||||
"count": count,
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- plan slots + pick points (v0.3 A4) --------------------------------
|
|
||||||
|
|
||||||
# Day order is stable Mon..Sun. Used everywhere we need to render slots
|
# Day order is stable Mon..Sun. Used everywhere we need to render slots
|
||||||
# in calendar order.
|
# in calendar order.
|
||||||
|
|
@ -728,13 +650,10 @@ class DB:
|
||||||
return inserted
|
return inserted
|
||||||
|
|
||||||
def delete_plan_slots(self, plan_id: int) -> int:
|
def delete_plan_slots(self, plan_id: int) -> int:
|
||||||
"""Wipe slots for a plan (used by re-roll). Also nukes the matching
|
"""Wipe slots for a plan (used by re-roll)."""
|
||||||
pick_points so the ledger doesn't double-count on regenerate."""
|
|
||||||
with self.conn() as c, c.cursor() as cur:
|
with self.conn() as c, c.cursor() as cur:
|
||||||
cur.execute("DELETE FROM cauldron_meal_plan_slots WHERE plan_id=%s", (plan_id,))
|
cur.execute("DELETE FROM cauldron_meal_plan_slots WHERE plan_id=%s", (plan_id,))
|
||||||
slots_removed = cur.rowcount
|
return cur.rowcount
|
||||||
cur.execute("DELETE FROM cauldron_pick_points WHERE plan_id=%s", (plan_id,))
|
|
||||||
return slots_removed
|
|
||||||
|
|
||||||
def mark_plan_generated(self, plan_id: int, sub: str) -> dict:
|
def mark_plan_generated(self, plan_id: int, sub: str) -> dict:
|
||||||
"""Set generated_by_sub + generated_at IF not already set. Returns
|
"""Set generated_by_sub + generated_at IF not already set. Returns
|
||||||
|
|
@ -766,27 +685,6 @@ class DB:
|
||||||
(plan_id,),
|
(plan_id,),
|
||||||
)
|
)
|
||||||
|
|
||||||
def award_pick_points(
|
|
||||||
self,
|
|
||||||
household_id: int,
|
|
||||||
plan_id: int,
|
|
||||||
sub: str,
|
|
||||||
points: int,
|
|
||||||
reason: str = "pick_used",
|
|
||||||
) -> int:
|
|
||||||
"""Insert one ledger row. Returns the new row id. Reason must be one
|
|
||||||
of the ENUM values; we don't validate here — DB will reject bad ones."""
|
|
||||||
with self.conn() as c, c.cursor() as cur:
|
|
||||||
cur.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO cauldron_pick_points
|
|
||||||
(household_id, plan_id, authentik_sub, points, reason)
|
|
||||||
VALUES (%s, %s, %s, %s, %s)
|
|
||||||
""",
|
|
||||||
(household_id, plan_id, sub, int(points), reason),
|
|
||||||
)
|
|
||||||
return cur.lastrowid
|
|
||||||
|
|
||||||
def enrich_plan_with_slots(self, plan: dict) -> dict:
|
def enrich_plan_with_slots(self, plan: dict) -> dict:
|
||||||
"""In-place: add `slots` key to a plan dict. Returns the same dict
|
"""In-place: add `slots` key to a plan dict. Returns the same dict
|
||||||
for chaining. Empty list if there are no slots yet."""
|
for chaining. Empty list if there are no slots yet."""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue