Browse Source

Fix filament usage not recorded when auto-archive is disabled

  on_print_complete returned early when no archive_id was found (auto-
  archive off), skipping both internal inventory tracking (AMS remain%
  deltas) and Spoolman usage reporting. Moved the filament usage tracking
  block to run before the archive_id early-return so consumption is
  always recorded regardless of the auto-archive setting.
maziggy 2 months ago
parent
commit
24a5217d46
2 changed files with 65 additions and 58 deletions
  1. 1 0
      CHANGELOG.md
  2. 64 58
      backend/app/main.py

+ 1 - 0
CHANGELOG.md

@@ -9,6 +9,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **Settings Queue Tab** — New dedicated Queue tab in Settings consolidates queue-related settings: staggered start defaults and auto-drying configuration (moved from the Filament tab).
 
 ### Fixed
+- **Filament Usage Not Recorded When Auto-Archive Disabled** — When a printer had "Auto-archive completed prints" turned off, filament consumption was silently lost. The `on_print_complete` callback returned early before reaching the usage tracking code, so neither the internal inventory (AMS remain% deltas) nor Spoolman received usage data. Moved filament tracking to run before the archive check so usage is always recorded regardless of the auto-archive setting.
 - **H2D External Spool Uses Wrong Nozzle** ([#836](https://github.com/maziggy/bambuddy/issues/836)) — Prints sent from Bambuddy to dual-nozzle printers (H2D, H2D Pro) with external spools always routed to the wrong nozzle. The old `ams_mapping2` format used a shared `ams_id: 255` with `slot_id: 0/1` to differentiate external slots, but the firmware interpreted slot_id as the nozzle index (0=main/right, 1=deputy/left), routing filament to the opposite nozzle. Already fixed by the #797 `ams_mapping2` format change (per-tray `ams_id` instead of shared unit), but users on older builds still experience this. Printing the same file directly from the slicer worked correctly. Reported by @NoahTingey.
 - **SpoolBuddy "Add to Inventory" Failed Silently** — The quick-add button on the SpoolBuddy kiosk did nothing when tapped. The scale weight was sent as a float but the backend requires an integer, causing a Pydantic validation error. The error was silently caught with no user feedback, leaving the confirmation modal stuck open. Fixed by rounding the weight before sending, moving the modal close to a `finally` block, and adding an error toast with the actual API message.
 - **SpoolBuddy Dashboard Crash on Null Spool Fields** — Viewing a spool with null `subtype`, `brand`, `rgba`, or `color_name` on the SpoolBuddy dashboard crashed the UI (black screen). The spool prop construction used `displayedSpool?.subtype ?? sbState.matchedSpool!.subtype` — when the field was `null`, the `??` operator fell through to `sbState.matchedSpool` which could also be null, causing a TypeError. Fixed by picking one source object instead of mixing per-field fallbacks. Added a global React error boundary so future crashes show the error instead of a black screen.

+ 64 - 58
backend/app/main.py

@@ -2631,6 +2631,70 @@ async def on_print_complete(printer_id: int, data: dict):
         task = asyncio.create_task(_background_bed_cooldown())
         _bed_cooldown_tasks[printer_id] = task
 
+    # --- Track filament consumption (must run before archive_id early-return so usage
+    # is recorded even when auto-archive is disabled) ---
+    usage_results: list[dict] = []
+    # Prefer ams_mapping captured from MQTT request topic (works for all print sources)
+    stored_ams_mapping = data.get("ams_mapping")
+    # Fallback to _print_ams_mappings for queue/reprint (set before print starts)
+    if not stored_ams_mapping and archive_id:
+        stored_ams_mapping = _print_ams_mappings.pop(archive_id, None)
+
+    # Internal inventory: track AMS remain% deltas (skip if Spoolman handles usage)
+    try:
+        async with async_session() as db:
+            from backend.app.api.routes.settings import get_setting
+
+            _spoolman_on = await get_setting(db, "spoolman_enabled")
+        if not _spoolman_on or _spoolman_on.lower() != "true":
+            from backend.app.services.usage_tracker import on_print_complete as usage_on_print_complete
+
+            async with async_session() as db:
+                usage_results = await usage_on_print_complete(
+                    printer_id,
+                    data,
+                    printer_manager,
+                    db,
+                    archive_id=archive_id,
+                    ams_mapping=stored_ams_mapping,
+                )
+                if usage_results:
+                    await ws_manager.broadcast(
+                        {
+                            "type": "spool_usage_logged",
+                            "printer_id": printer_id,
+                            "usage": usage_results,
+                        }
+                    )
+                    log_timing("Usage tracker")
+
+    except Exception as e:
+        logger.warning("Usage tracker on_print_complete failed: %s", e)
+
+    # Spoolman: report filament usage (requires archive_id for tracking data lookup)
+    if archive_id:
+        if data.get("status") == "completed":
+            try:
+                await _report_spoolman_usage(printer_id, archive_id)
+                log_timing("Spoolman usage report")
+            except Exception as e:
+                logger.warning("Spoolman usage reporting failed: %s", e)
+        else:
+            # Report partial usage if tracking data exists (only stored when weight sync is disabled)
+            try:
+                async with async_session() as db:
+                    await _cleanup_spoolman_tracking(
+                        printer_id,
+                        archive_id,
+                        db,
+                        last_layer_num=data.get("last_layer_num"),
+                        last_progress=data.get("last_progress"),
+                    )
+            except Exception as e:
+                logger.debug("[SPOOLMAN] Cleanup failed: %s", e)
+
+    log_timing("Filament usage tracking")
+
     if not archive_id:
         logger.warning("Could not find archive for print complete: filename=%s, subtask=%s", filename, subtask_name)
 
@@ -2805,64 +2869,6 @@ async def on_print_complete(printer_id: int, data: dict):
 
     log_timing("Print log entry")
 
-    # Track filament consumption from AMS remain% deltas (skip if Spoolman handles usage)
-    usage_results: list[dict] = []
-    # Prefer ams_mapping captured from MQTT request topic (works for all print sources)
-    stored_ams_mapping = data.get("ams_mapping")
-    # Fallback to _print_ams_mappings for queue/reprint (set before print starts)
-    if not stored_ams_mapping and archive_id:
-        stored_ams_mapping = _print_ams_mappings.pop(archive_id, None)
-    try:
-        async with async_session() as db:
-            from backend.app.api.routes.settings import get_setting
-
-            _spoolman_on = await get_setting(db, "spoolman_enabled")
-        if not _spoolman_on or _spoolman_on.lower() != "true":
-            from backend.app.services.usage_tracker import on_print_complete as usage_on_print_complete
-
-            async with async_session() as db:
-                usage_results = await usage_on_print_complete(
-                    printer_id,
-                    data,
-                    printer_manager,
-                    db,
-                    archive_id=archive_id,
-                    ams_mapping=stored_ams_mapping,
-                )
-                if usage_results:
-                    await ws_manager.broadcast(
-                        {
-                            "type": "spool_usage_logged",
-                            "printer_id": printer_id,
-                            "usage": usage_results,
-                        }
-                    )
-                    log_timing("Usage tracker")
-
-    except Exception as e:
-        logger.warning("Usage tracker on_print_complete failed: %s", e)
-
-    # Report filament usage to Spoolman if print completed successfully
-    if data.get("status") == "completed":
-        try:
-            await _report_spoolman_usage(printer_id, archive_id)
-            log_timing("Spoolman usage report")
-        except Exception as e:
-            logger.warning("Spoolman usage reporting failed: %s", e)
-    else:
-        # Report partial usage if tracking data exists (only stored when weight sync is disabled)
-        try:
-            async with async_session() as db:
-                await _cleanup_spoolman_tracking(
-                    printer_id,
-                    archive_id,
-                    db,
-                    last_layer_num=data.get("last_layer_num"),
-                    last_progress=data.get("last_progress"),
-                )
-        except Exception as e:
-            logger.debug("[SPOOLMAN] Cleanup failed: %s", e)
-
     # Run slow operations as background tasks to avoid blocking the event loop
     # These operations can take 5-10+ seconds and would freeze the UI if awaited
     starting_kwh = _print_energy_start.pop(archive_id, None)