mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
Statistics now aggregate over PrintLogEntry (one row per print event,
the same table backing the global Print Log) rather than PrintArchive
(one row per file). A reprint creates a new PrintLogEntry instead of
overwriting the source archive's runtime fields, so:
- a 100 g successful print + a 10 g failed reprint correctly sums to
110 g / 2 prints / 1 successful / 1 failed in Quick Stats and the
Prometheus /metrics endpoint (previously the failed reprint silently
replaced the source archive's data; totals dropped from 100 g to 10 g)
- the archive's card cost/energy_kwh are preserved on reprints (only
the first run writes them); per-run actuals live on PrintLogEntry
- failed/cancelled/stopped reprints record partial-aware filament: sum
of tracked spool deltas when inventory is set up, else estimate
scaled to progress%, else None — prevents the full slicer estimate
from inflating totals on a print that stopped at 10 % progress
PrintLogEntry gains six columns: archive_id (nullable FK, ON DELETE
SET NULL so log entries survive archive deletion preserving #1343
soft-delete-vs-stats decoupling), cost, energy_kwh, energy_cost,
failure_reason, created_by_id. Idempotent SQLite + Postgres migrations.
New per-archive surface:
- archive list response carries run_count / last_run_at /
total_filament_actual_grams / successful_run_count / failed_run_count
via a single batch JOIN, no N+1
- new GET /archives/{id}/runs endpoint returns every PrintLogEntry for
the archive (ARCHIVES_READ permission, newest-first ordering)
- archive cards render an orange "N prints" badge for archives with
more than one run; clicking the badge opens a dedicated PrintLogModal
with date/status/duration/filament/cost columns plus failure_reason
under failed runs. Also reachable via the context menu's new "Print
Log" entry (works for single-run archives too), and embedded at the
top of the Edit Archive modal for context.
The purge_stats=true delete path now hard-deletes linked PrintLogEntry
rows up front so the archive's contribution truly leaves the totals;
without it, ON DELETE SET NULL would orphan the runs and leave them
counting toward stats.
65 lines
1.9 KiB
Python
65 lines
1.9 KiB
Python
"""Service for writing independent print log entries.
|
|
|
|
Log entries are written to a separate table and never touch archives or queue items.
|
|
"""
|
|
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from backend.app.models.print_log import PrintLogEntry
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def write_log_entry(
|
|
db: AsyncSession,
|
|
*,
|
|
status: str,
|
|
archive_id: int | None = None,
|
|
print_name: str | None = None,
|
|
printer_name: str | None = None,
|
|
printer_id: int | None = None,
|
|
started_at: datetime | None = None,
|
|
completed_at: datetime | None = None,
|
|
filament_type: str | None = None,
|
|
filament_color: str | None = None,
|
|
filament_used_grams: float | None = None,
|
|
cost: float | None = None,
|
|
energy_kwh: float | None = None,
|
|
energy_cost: float | None = None,
|
|
failure_reason: str | None = None,
|
|
thumbnail_path: str | None = None,
|
|
created_by_id: int | None = None,
|
|
created_by_username: str | None = None,
|
|
) -> PrintLogEntry:
|
|
"""Write a print log entry."""
|
|
duration = None
|
|
if started_at and completed_at:
|
|
duration = int((completed_at - started_at).total_seconds())
|
|
|
|
entry = PrintLogEntry(
|
|
archive_id=archive_id,
|
|
print_name=print_name,
|
|
printer_name=printer_name,
|
|
printer_id=printer_id,
|
|
status=status,
|
|
started_at=started_at,
|
|
completed_at=completed_at,
|
|
duration_seconds=duration,
|
|
filament_type=filament_type,
|
|
filament_color=filament_color,
|
|
filament_used_grams=filament_used_grams,
|
|
cost=cost,
|
|
energy_kwh=energy_kwh,
|
|
energy_cost=energy_cost,
|
|
failure_reason=failure_reason,
|
|
thumbnail_path=thumbnail_path,
|
|
created_by_id=created_by_id,
|
|
created_by_username=created_by_username,
|
|
)
|
|
db.add(entry)
|
|
await db.flush()
|
|
return entry
|