charts: add Energy and Carbon (co-op/support#26)
Two more metrics on the same chart, from the portal's new energy_wh and co2e_g daily columns. - Energy in watt-hours, carbon in grams CO2e, both for the day shown. - Continuous metrics use the 1/2/5 axis rule (not the integer rule reserved for counts), so energy ticks read 500 Wh rather than being rounded to whole people-style numbers. - Labels carry units, so a reader never has to guess what the axis measures. - Same measured-vs-estimated provenance as the data layer: GreenPT models use facts fitted from their own impact API, everyone else a labelled proxy. The docs page on estimation is still to be written; the factor table and its sources are in metrics_snapshot.py. Harness extended: one bar per measured day for each, no NaN in markup, and axis labels matching ^[\d,]+(\.\d+)? (Wh|g)$ so a unit prefix or a forced integer axis fails the build.
This commit is contained in:
1 parent
10d62fdde4
commit
a5229103f6
1 file changed
+12
-2
+12
-2
@@ -370,6 +370,8 @@ PAGE_HTML = """<!DOCTYPE html>
|
|||||||
<span class="range-label">Show</span>
|
<span class="range-label">Show</span>
|
||||||
<button class="range-btn active" id="tm-members" onclick="setTrendMetric('members')">Members</button>
|
<button class="range-btn active" id="tm-members" onclick="setTrendMetric('members')">Members</button>
|
||||||
<button class="range-btn" id="tm-spend" onclick="setTrendMetric('spend')">Spend</button>
|
<button class="range-btn" id="tm-spend" onclick="setTrendMetric('spend')">Spend</button>
|
||||||
|
<button class="range-btn" id="tm-energy" onclick="setTrendMetric('energy')">Energy</button>
|
||||||
|
<button class="range-btn" id="tm-co2e" onclick="setTrendMetric('co2e')">Carbon</button>
|
||||||
<button class="range-btn" id="tm-requests" onclick="setTrendMetric('requests')">Requests</button>
|
<button class="range-btn" id="tm-requests" onclick="setTrendMetric('requests')">Requests</button>
|
||||||
</span>
|
</span>
|
||||||
<span class="range-group">
|
<span class="range-group">
|
||||||
@@ -473,8 +475,10 @@ let trendDays = 90; // 0 = all time
|
|||||||
|
|
||||||
const TREND_CONF = {
|
const TREND_CONF = {
|
||||||
members: { kind: "line", title: "Members" },
|
members: { kind: "line", title: "Members" },
|
||||||
|
requests: { kind: "bar", title: "Requests per day" },
|
||||||
spend: { kind: "bar", title: "Spend per day" },
|
spend: { kind: "bar", title: "Spend per day" },
|
||||||
requests: { kind: "bar", title: "Requests per day" }
|
energy: { kind: "bar", title: "Energy per day" },
|
||||||
|
co2e: { kind: "bar", title: "Carbon per day" }
|
||||||
};
|
};
|
||||||
|
|
||||||
async function loadMetrics() {
|
async function loadMetrics() {
|
||||||
@@ -496,6 +500,8 @@ function setTrendMetric(m) {
|
|||||||
trendMetric = m;
|
trendMetric = m;
|
||||||
$("tm-members").className = "range-btn" + (m === "members" ? " active" : "");
|
$("tm-members").className = "range-btn" + (m === "members" ? " active" : "");
|
||||||
$("tm-spend").className = "range-btn" + (m === "spend" ? " active" : "");
|
$("tm-spend").className = "range-btn" + (m === "spend" ? " active" : "");
|
||||||
|
$("tm-energy").className = "range-btn" + (m === "energy" ? " active" : "");
|
||||||
|
$("tm-co2e").className = "range-btn" + (m === "co2e" ? " active" : "");
|
||||||
$("tm-requests").className = "range-btn" + (m === "requests" ? " active" : "");
|
$("tm-requests").className = "range-btn" + (m === "requests" ? " active" : "");
|
||||||
renderTrends();
|
renderTrends();
|
||||||
}
|
}
|
||||||
@@ -511,6 +517,8 @@ function setTrendDays(d) {
|
|||||||
function trendVal(row) {
|
function trendVal(row) {
|
||||||
if (trendMetric === "members") return row.members_total;
|
if (trendMetric === "members") return row.members_total;
|
||||||
if (trendMetric === "spend") return row.spend_usd;
|
if (trendMetric === "spend") return row.spend_usd;
|
||||||
|
if (trendMetric === "energy") return row.energy_wh;
|
||||||
|
if (trendMetric === "co2e") return row.co2e_g;
|
||||||
return row.requests;
|
return row.requests;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,7 +526,7 @@ function trendVal(row) {
|
|||||||
// numbers (a 1.08 headroom factor alone yields labels like 19.17 people).
|
// numbers (a 1.08 headroom factor alone yields labels like 19.17 people).
|
||||||
// Money gets the classic 1/2/5 x 10^k rounding so ticks read $5.00, not $4.92.
|
// Money gets the classic 1/2/5 x 10^k rounding so ticks read $5.00, not $4.92.
|
||||||
function niceStep(raw) {
|
function niceStep(raw) {
|
||||||
if (trendMetric === "spend") {
|
if (trendMetric === "spend" || trendMetric === "energy" || trendMetric === "co2e") {
|
||||||
const mag = Math.pow(10, Math.floor(Math.log10(Math.max(raw, 1e-9))));
|
const mag = Math.pow(10, Math.floor(Math.log10(Math.max(raw, 1e-9))));
|
||||||
const norm = raw / mag;
|
const norm = raw / mag;
|
||||||
const nice = norm <= 1 ? 1 : (norm <= 2 ? 2 : (norm <= 5 ? 5 : 10));
|
const nice = norm <= 1 ? 1 : (norm <= 2 ? 2 : (norm <= 5 ? 5 : 10));
|
||||||
@@ -534,6 +542,8 @@ function niceStep(raw) {
|
|||||||
function trendLabel(v) {
|
function trendLabel(v) {
|
||||||
if (v === null || v === undefined) return "not measured";
|
if (v === null || v === undefined) return "not measured";
|
||||||
if (trendMetric === "spend") return fmt(v);
|
if (trendMetric === "spend") return fmt(v);
|
||||||
|
if (trendMetric === "energy") return Number(v).toLocaleString() + " Wh";
|
||||||
|
if (trendMetric === "co2e") return Number(v).toLocaleString() + " g";
|
||||||
return Number(v).toLocaleString();
|
return Number(v).toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user