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
69e05b1f92
commit
94c07ab156
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
|
||||
) 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
|
||||
|
||||
def household_scoreboard(self, household_id: int) -> list[dict]:
|
||||
"""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) --------------------------------
|
||||
# --- plan slots (v0.3 A4) ----------------------------------------------
|
||||
|
||||
# Day order is stable Mon..Sun. Used everywhere we need to render slots
|
||||
# in calendar order.
|
||||
|
|
@ -728,13 +650,10 @@ class DB:
|
|||
return inserted
|
||||
|
||||
def delete_plan_slots(self, plan_id: int) -> int:
|
||||
"""Wipe slots for a plan (used by re-roll). Also nukes the matching
|
||||
pick_points so the ledger doesn't double-count on regenerate."""
|
||||
"""Wipe slots for a plan (used by re-roll)."""
|
||||
with self.conn() as c, c.cursor() as cur:
|
||||
cur.execute("DELETE FROM cauldron_meal_plan_slots WHERE plan_id=%s", (plan_id,))
|
||||
slots_removed = cur.rowcount
|
||||
cur.execute("DELETE FROM cauldron_pick_points WHERE plan_id=%s", (plan_id,))
|
||||
return slots_removed
|
||||
return cur.rowcount
|
||||
|
||||
def mark_plan_generated(self, plan_id: int, sub: str) -> dict:
|
||||
"""Set generated_by_sub + generated_at IF not already set. Returns
|
||||
|
|
@ -766,27 +685,6 @@ class DB:
|
|||
(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:
|
||||
"""In-place: add `slots` key to a plan dict. Returns the same dict
|
||||
for chaining. Empty list if there are no slots yet."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue