Files
bambuddy/backend
maziggy 8e4f815b37 fix(stats): backfill PrintLogEntry.cost/energy/archive_id for pre-#1378 rows (#1390)
Reporter IndividualGhost1905 upgraded to 0.2.4.1 (which shipped the
  per-event aggregation rewrite from #1378) and saw Quick Stats split
  between consistent values (Total Prints, Print Time, Filament Used,
  Energy, Success Rate matched the archive list) and zero-or-empty ones
  (Filament Cost, Time Accuracy).

  Root cause: #1378's migration added six columns to print_log_entries
  - archive_id, cost, energy_kwh, energy_cost, failure_reason,
  created_by_id - but never backfilled them. Pre-upgrade rows kept NULL
  on all six. The new Quick Stats query sums PrintLogEntry.cost (gets 0
  on legacy data); the time-accuracy query JOINs PrintArchive ON
  archive_id (drops every legacy run from the average). Counts and the
  pre-existing per-row fields (status, duration_seconds,
  filament_used_grams) kept working - which is why some panels looked
  right and others didn't.

  Two-step backfill added inside run_migrations next to the existing
  column-add block, as DML inside begin_nested() (not _safe_execute,
  which is documented DDL-only):

    Step 1: link each orphan log entry to its archive via
            print_name + printer_id (highest archive id wins on
            tiebreak - newest matches the overwrite-then-stop shape
            pre-#1378 reprints left behind).

    Step 2: copy archive.cost / energy_kwh / energy_cost onto the
            latest matching log entry per archive, BUT only for
            archives where no log entry yet carries a cost. That
            second clause is the idempotency anchor and the
            double-count guard for users running this after #1378
            has already written cost-bearing rows for new runs -
            those archives are left untouched.

  Earlier reprints stay NULL, matching the "first/latest writes, rest
  stay NULL" convention #1378 introduced for new prints. Sum across the
  legacy reprint chain reproduces sum-of-archive-cost exactly, so Quick
  Stats Filament Cost matches the pre-upgrade total instead of dropping
  to zero.

  SQL is plain ANSI - correlated UPDATE with LIMIT 1 in the SET
  subquery, WHERE id IN (SELECT MAX(id) ... GROUP BY archive_id HAVING
  SUM(CASE WHEN cost IS NOT NULL THEN 1 ELSE 0 END) = 0). Verified
  end-to-end on SQLite (4 new unit tests in
  test_print_log_backfill_migration.py: link-via-name, latest-run-gets-
  cost, idempotent, skip-archives-with-any-costed-run) and against a
  live postgres:16-alpine + asyncpg container (first-pass and second-
  pass produce identical state).

  The other widgets the reporter listed (Printer Stats, Filament
  Trends, By Material, Success by Material, Color Distribution) iterate
  the archives list on the frontend rather than calling /stats - they
  read consistent pre-upgrade data and aren't part of this fix; the
  inconsistency between them and Quick Stats resolves once the backfill
  brings Quick Stats in line.
2026-05-17 09:30:02 +02:00
..