Add capacity test history and chart to battery detail
- New CapacityTest model (battery_id FK CASCADE, mah, date, notes) - DB migration: create capacity_test table, migrate existing single-test data - Two new routes: add and delete capacity test records - Battery.tested_capacity_mah/tested_date kept in sync with latest test so dashboard display requires no changes - Battery detail: Capacity History card with sortable table, health % per reading, and a canvas line chart (shown when >= 2 records) - Chart uses CSS variables for colors — works in light and dark mode - Remove tested_capacity_mah/tested_date from Edit Details form - 3 new acceptance tests (48 total)
This commit is contained in:
@@ -2,7 +2,7 @@ from flask import Flask, render_template, redirect, url_for, request, flash, abo
|
||||
from sqlalchemy import create_engine, func
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
|
||||
from models import Base, Battery, Device
|
||||
from models import Base, Battery, Device, CapacityTest
|
||||
|
||||
|
||||
def create_app(config_object="config"):
|
||||
@@ -105,8 +105,13 @@ def create_app(config_object="config"):
|
||||
.filter(Battery.storage_location.isnot(None))
|
||||
.distinct().order_by(Battery.storage_location).all()
|
||||
]
|
||||
capacity_tests = (db.query(CapacityTest)
|
||||
.filter_by(battery_id=battery_id)
|
||||
.order_by(CapacityTest.tested_date, CapacityTest.id)
|
||||
.all())
|
||||
return render_template("battery_detail.html", battery=battery,
|
||||
storage_locations=storage_locations)
|
||||
storage_locations=storage_locations,
|
||||
capacity_tests=capacity_tests)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Battery — edit notes
|
||||
@@ -130,8 +135,6 @@ def create_app(config_object="config"):
|
||||
battery.size = f.get("size", "").strip() or None
|
||||
battery.chemistry = f.get("chemistry", "").strip() or None
|
||||
battery.capacity_mah = _int("capacity_mah")
|
||||
battery.tested_capacity_mah = _int("tested_capacity_mah")
|
||||
battery.tested_date = f.get("tested_date", "").strip() or None
|
||||
battery.charge_cycles = _int("charge_cycles")
|
||||
battery.purchase_date = f.get("purchase_date", "").strip() or None
|
||||
battery.storage_location = f.get("storage_location", "").strip() or None
|
||||
@@ -140,6 +143,57 @@ def create_app(config_object="config"):
|
||||
flash("Details updated.", "success")
|
||||
return redirect(url_for("battery_detail", battery_id=battery_id))
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Battery — capacity test history
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def _sync_latest_test(battery):
|
||||
latest = (db.query(CapacityTest)
|
||||
.filter_by(battery_id=battery.id)
|
||||
.order_by(CapacityTest.tested_date.desc(), CapacityTest.id.desc())
|
||||
.first())
|
||||
battery.tested_capacity_mah = latest.tested_capacity_mah if latest else None
|
||||
battery.tested_date = latest.tested_date if latest else None
|
||||
|
||||
@app.route("/battery/<int:battery_id>/capacity-test/add", methods=["POST"])
|
||||
def battery_capacity_test_add(battery_id):
|
||||
battery = db.get(Battery, battery_id)
|
||||
if battery is None:
|
||||
abort(404)
|
||||
mah_raw = request.form.get("tested_capacity_mah", "").strip()
|
||||
date_val = request.form.get("tested_date", "").strip()
|
||||
notes = request.form.get("notes", "").strip() or None
|
||||
if not mah_raw or not date_val:
|
||||
flash("Capacity (mAh) and date are required.", "error")
|
||||
return redirect(url_for("battery_detail", battery_id=battery_id))
|
||||
try:
|
||||
mah = int(mah_raw)
|
||||
if mah <= 0:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
flash("Capacity must be a positive integer.", "error")
|
||||
return redirect(url_for("battery_detail", battery_id=battery_id))
|
||||
db.add(CapacityTest(battery_id=battery_id, tested_capacity_mah=mah,
|
||||
tested_date=date_val, notes=notes))
|
||||
db.flush()
|
||||
_sync_latest_test(battery)
|
||||
db.commit()
|
||||
flash("Test record added.", "success")
|
||||
return redirect(url_for("battery_detail", battery_id=battery_id))
|
||||
|
||||
@app.route("/battery/<int:battery_id>/capacity-test/<int:test_id>/delete", methods=["POST"])
|
||||
def battery_capacity_test_delete(battery_id, test_id):
|
||||
battery = db.get(Battery, battery_id)
|
||||
test = db.get(CapacityTest, test_id)
|
||||
if battery is None or test is None or test.battery_id != battery_id:
|
||||
abort(404)
|
||||
db.delete(test)
|
||||
db.flush()
|
||||
_sync_latest_test(battery)
|
||||
db.commit()
|
||||
flash("Test record deleted.", "success")
|
||||
return redirect(url_for("battery_detail", battery_id=battery_id))
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Battery — assign
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
Reference in New Issue
Block a user