Log filament usage to FilamentDB when starting a job

- filamentdb.get_ams_slots(): parses /ams/ HTML to extract slot, material,
  color, weight_remaining_g, roll_id for each loaded AMS slot
- filamentdb.find_matching_ams_roll(): filters by material+color (case-insensitive),
  picks the roll with least filament remaining
- filamentdb.log_print_on_roll(): POSTs to /rolls/{id}/prints
- start_job: after setting job to printing, looks up AMS, computes weight
  (math.ceil of estimate), logs against matching roll, writes result to job log
- Verified parser against live AMS: PLA Black → slot 3 (30g) over slot 1 (1000g)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martin Hohenberg
2026-06-18 22:59:33 +02:00
parent bef57027f2
commit 17097061c3
2 changed files with 93 additions and 2 deletions

View File

@@ -1,5 +1,7 @@
import logging
import math
import os
import re
import time
import httpx
@@ -64,6 +66,70 @@ def spool_counts() -> dict[tuple[str, str], int]:
return result
def _parse_ams_html(html: str) -> list[dict]:
"""
Parse the /ams/ HTML page to extract slot info.
Each card yields: slot, material, color, weight_remaining_g, roll_id (numeric).
"""
slots = []
for card in re.split(r'class="card ams-card"', html)[1:]:
slot_m = re.search(r'<h4>Slot (\d+)</h4>', card)
matcol_m = re.search(
r'<strong>.*?</strong><br>\s*(.*?)<br>\s*(.*?)\s*</p>', card, re.DOTALL
)
weight_m = re.search(r'~(\d+)g', card)
roll_id_m = re.search(r'action="/rolls/(\d+)/prints"', card)
if slot_m and matcol_m and weight_m and roll_id_m:
slots.append({
"slot": int(slot_m.group(1)),
"material": matcol_m.group(1).strip(),
"color": matcol_m.group(2).strip(),
"weight_remaining_g": int(weight_m.group(1)),
"roll_id": int(roll_id_m.group(1)),
})
return slots
def get_ams_slots() -> list[dict]:
"""Return currently loaded AMS slots (live, not cached — state changes often)."""
try:
r = httpx.get(f"{_base()}/ams/", timeout=5, verify=False)
r.raise_for_status()
return _parse_ams_html(r.text)
except Exception:
logger.warning("FilamentDB AMS endpoint unreachable")
return []
def find_matching_ams_roll(material: str, color: str) -> dict | None:
"""
Find the best roll in AMS1/slots 1-4 matching material+color.
If multiple match, pick the one with least filament remaining (use it up first).
"""
matches = [
s for s in get_ams_slots()
if s["material"].lower() == material.lower()
and s["color"].lower() == color.lower()
]
return min(matches, key=lambda s: s["weight_remaining_g"]) if matches else None
def log_print_on_roll(roll_id: int, weight_g: int, note: str) -> bool:
"""POST a print log entry to FilamentDB for the given roll."""
try:
r = httpx.post(
f"{_base()}/rolls/{roll_id}/prints",
data={"weight_used_g": weight_g, "note": note, "redirect_to": ""},
timeout=5,
verify=False,
)
r.raise_for_status()
return True
except Exception:
logger.warning("FilamentDB print log failed for roll %s", roll_id)
return False
def available_colors(material: str | None = None) -> list[str]:
"""Unique color names in stock, optionally filtered by material."""
rows = get_stock()