From f52e545ddf764dc97f4d3a40b6e572546f2e0691 Mon Sep 17 00:00:00 2001 From: Matt Johnson Date: Wed, 27 May 2026 04:29:32 +0000 Subject: [PATCH] v0.9.15: live JS quota recompute on multi-bbox editor Mirror TomTomIncidentsAdapter.quota_estimate in client-side JS so the quota panel updates live as the operator edits a per-row cadence or the adapter cadence, and on Add/Delete bbox -- instead of only on Save. - tomtom_incidents.py: expose seconds_per_month + default_cadence_s in the quota dict (no JS magic numbers). - model_list.html: data-quota-* attrs + .quota-detail/.quota-msg spans; self-contained DOMContentLoaded-guarded IIFE applying the same max(adapter_cadence, bbox_cadence||default) floor and 80%/100% warn/block thresholds. No-op when no quota panel present. - tests: structural asserts for data-attrs + span hooks (JS exec needs a browser; live behaviour eyeballed manually). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/central/adapters/tomtom_incidents.py | 2 + .../gui/templates/_partials/model_list.html | 72 +++++++++++++++++-- tests/test_gui_adapter_edit.py | 21 ++++++ 3 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/central/adapters/tomtom_incidents.py b/src/central/adapters/tomtom_incidents.py index 8b9851f..8868fa1 100644 --- a/src/central/adapters/tomtom_incidents.py +++ b/src/central/adapters/tomtom_incidents.py @@ -333,6 +333,8 @@ class TomTomIncidentsAdapter(SourceAdapter): return { "calls_per_month": calls_per_month, "cap": cap, + "seconds_per_month": _SECONDS_PER_MONTH, + "default_cadence_s": cls.default_cadence_s, "percent": percent, "warn": percent >= 80.0, "blocked": percent >= 100.0, diff --git a/src/central/gui/templates/_partials/model_list.html b/src/central/gui/templates/_partials/model_list.html index ac882fb..077cd04 100644 --- a/src/central/gui/templates/_partials/model_list.html +++ b/src/central/gui/templates/_partials/model_list.html @@ -66,10 +66,13 @@ {% if quota %} -
- API quota: {{ quota.detail }} - {% if quota.blocked %}
⛔ Over free-tier cap — reduce calls before saving. - {% elif quota.warn %}
⚠️ Approaching free-tier cap.{% endif %} +
+ API quota: {{ quota.detail }} + {% if quota.blocked %}
⛔ Over free-tier cap — reduce calls before saving.{% elif quota.warn %}
⚠️ Approaching free-tier cap.{% endif %}
{% endif %} @@ -103,3 +106,64 @@ }); })(); + + diff --git a/tests/test_gui_adapter_edit.py b/tests/test_gui_adapter_edit.py index d7cb04f..f196cbf 100644 --- a/tests/test_gui_adapter_edit.py +++ b/tests/test_gui_adapter_edit.py @@ -56,6 +56,27 @@ class TestModelListRender: assert "model-list" in out and 'name="bboxes-0-name"' in out assert "free tier" in out # quota panel rendered + def test_quota_panel_exposes_client_recompute_constants(self): + # v0.9.15: data-quota-* attrs let the client JS mirror the server + # formula with no hardcoded magic numbers. + fields = describe_fields(TomTomIncidentsAdapter.settings_schema, _SETTINGS) + quota = TomTomIncidentsAdapter.quota_estimate(TomTomIncidentsSettings(**_SETTINGS), 1800) + out = _render("adapters_edit.html", _ctx(_SETTINGS, fields, quota)) + assert 'data-quota-cap="2500"' in out + assert 'data-quota-spm="2592000"' in out + assert 'data-quota-default="1800"' in out + + def test_quota_panel_wraps_detail_and_msg_for_live_update(self): + # v0.9.15: detail/msg split into spans so the IIFE can rewrite the text + # and warn/block state live. JS exec needs a browser, so this is + # structural only -- live behaviour (cadence edit + Add/Delete row) + # was eyeballed manually against /adapters/tomtom_incidents. + fields = describe_fields(TomTomIncidentsAdapter.settings_schema, _SETTINGS) + quota = TomTomIncidentsAdapter.quota_estimate(TomTomIncidentsSettings(**_SETTINGS), 1800) + out = _render("adapters_edit.html", _ctx(_SETTINGS, fields, quota)) + assert '' in out + assert '' in out + def test_nws_region_intact_no_model_list(self): from central.adapters.nws import NWSSettings s = {"contact_email": "a@b.com",