The second review of PR #12 (after the suspect-run fix) found that the two BAR charts were still plotting runs they should not have been, while the curve chart and the tiles had been corrected. The page was contradicting itself. The ABV chart plotted the suspect run -------------------------------------- Run 12's impossible reading gives it an ABV of 18.64%. The chart's y axis went to 18.64, so the fifteen real bars were squashed into the bottom ~45% of the plot - while the "Strongest 8.53%" tile on the same page showed the correct figure. The chart now leaves the suspect runs out. The attenuation chart plotted the active runs ---------------------------------------------- An active run's attenuation is measured against its CURRENT gravity, so a brew that has just started shows a 0% bar. That is the absence of a result, not a result, and it sat directly above the table of finished brews. The chart now leaves the active runs out. Verified against the live data: ABV chart bars: bars=15 max=8.53 (was 16 bars, max 18.64) attenuation chart bars: bars=15 min=76.2 (was 17 bars, min 0) A suspect active run no longer vanishes --------------------------------------- The API flags suspect runs over EVERY run, so the charts leave them out. But the analysis page only looked at the finished ones, so an active run with a bad reading would be dropped from the charts and never listed in "Readings that need a second look" - it would disappear with no explanation anywhere. The analysis page now lists suspect active runs too. The figures say what they are based on -------------------------------------- - The analysis prose counted the non-suspect runs, not the ones that actually contributed a figure to the mean. It now counts the latter. - The history page's "Average ABV — across finished brews" was averaging 15 of 16 without saying so, while the analysis page said "16 brews (15 counted)". It now says "across 15 of 16 finished brews". - `/api/runs/<run_id>` carries the `suspect` and `active` flags that `/api/runs` does. 148 tests (up from 142).
214 lines
8.3 KiB
HTML
214 lines
8.3 KiB
HTML
{% extends 'base.html' %}
|
||
|
||
{% block title %}History — Brew Log{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="page__head">
|
||
<div>
|
||
<h1 class="page__title">History</h1>
|
||
<p class="page__sub">How every brew has gone, and what it turned out like.</p>
|
||
</div>
|
||
<div class="btn-row">
|
||
<a class="btn btn--ghost btn--sm" href="{{ url_for('analysis.active') }}">Active beers</a>
|
||
<a class="btn btn--ghost btn--sm" href="{{ url_for('brews.index') }}">Brew process</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid--4 mb-lg">
|
||
<div class="stat">
|
||
<div class="stat__label">Total brews</div>
|
||
<div class="stat__value">{{ stats.total_runs }}</div>
|
||
<div class="stat__meta">{{ stats.completed }} finished · {{ stats.active }} active</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat__label">Average ABV</div>
|
||
<div class="stat__value">{{ '%.2f' % stats.avg_abv }}%</div>
|
||
<div class="stat__meta">
|
||
{% if stats.abv_counted < stats.abv_total %}
|
||
across {{ stats.abv_counted }} of {{ stats.abv_total }} finished brews
|
||
{% else %}
|
||
across finished brews
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat__label">Strongest</div>
|
||
<div class="stat__value">
|
||
{% if stats.best %}{{ '%.2f' % stats.best.abv }}%{% else %}—{% endif %}
|
||
</div>
|
||
<div class="stat__meta">
|
||
{% if stats.best %}{{ stats.best.name }}{% else %}—{% endif %}
|
||
</div>
|
||
</div>
|
||
<div class="stat">
|
||
<div class="stat__label">Lightest</div>
|
||
<div class="stat__value">
|
||
{% if stats.worst %}{{ '%.2f' % stats.worst.abv }}%{% else %}—{% endif %}
|
||
</div>
|
||
<div class="stat__meta">
|
||
{% if stats.worst %}{{ stats.worst.name }}{% else %}—{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid--2 mb-lg">
|
||
<section class="card">
|
||
<div class="card__head">
|
||
<h2 class="card__title">Alcohol by brew</h2>
|
||
<span class="card__hint">Standard formula (OG − FG) × 131.25</span>
|
||
</div>
|
||
<div class="card__body">
|
||
<canvas id="abv-chart" height="120"></canvas>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="card">
|
||
<div class="card__head">
|
||
<h2 class="card__title">Brews over time</h2>
|
||
<span class="card__hint">Runs started per month (active runs counted at their first reading)</span>
|
||
</div>
|
||
<div class="card__body">
|
||
<canvas id="months-chart" height="120"></canvas>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
{% if outliers %}
|
||
<section class="card mb-lg">
|
||
<div class="card__head">
|
||
<h2 class="card__title">Readings worth a second look</h2>
|
||
<span class="card__hint">{{ outliers | length }} outside the usual hydrometer range</span>
|
||
</div>
|
||
<div class="card__body card__body--flush">
|
||
<div class="table-wrap">
|
||
<table class="data">
|
||
<thead>
|
||
<tr>
|
||
<th>Run</th>
|
||
<th>Name</th>
|
||
<th>Date</th>
|
||
<th class="num">Reading</th>
|
||
<th>Comment</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for row in outliers %}
|
||
<tr>
|
||
<td class="mono">{{ row.beer_run_id }}</td>
|
||
<td>{{ row.name }}</td>
|
||
<td class="mono nowrap">{{ row.date or '—' }}</td>
|
||
<td class="num"><span class="chip chip--warn">{{ row.sg }}</span></td>
|
||
<td class="comment">{{ row.comment or '—' }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
{% endif %}
|
||
|
||
<section class="card mb-lg">
|
||
<div class="card__head">
|
||
<h2 class="card__title">Every brew</h2>
|
||
<span class="card__hint">{{ summaries | length }} runs</span>
|
||
</div>
|
||
<div class="card__body card__body--flush">
|
||
<div class="table-wrap">
|
||
<table class="data">
|
||
<thead>
|
||
<tr>
|
||
<th>Run</th>
|
||
<th>Name</th>
|
||
<th>Status</th>
|
||
<th class="num">OG</th>
|
||
<th class="num">FG</th>
|
||
<th class="num">Drop</th>
|
||
<th class="num">ABV</th>
|
||
<th class="num">Legacy</th>
|
||
<th>Finished</th>
|
||
<th>Comment</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for run in summaries %}
|
||
<tr>
|
||
<td class="mono">{{ run.beer_run_id }}</td>
|
||
<td>{{ run.canonical_name }}</td>
|
||
<td>
|
||
{% if run.active %}
|
||
<span class="chip chip--active">Active</span>
|
||
{% else %}
|
||
<span class="chip chip--done">Finished</span>
|
||
{% endif %}
|
||
</td>
|
||
<td class="num">{{ run.og_points or '—' }}</td>
|
||
<td class="num">{{ run.fg_points or '—' }}</td>
|
||
<td class="num">{{ run.drop_points if run.drop_points is not none else '—' }}</td>
|
||
<td class="num">
|
||
{% if run.abv is not none %}<strong>{{ '%.2f' % run.abv }}%</strong>
|
||
{% elif run.projected_abv is not none %}{{ '%.2f' % run.projected_abv }}%*
|
||
{% else %}—{% endif %}
|
||
</td>
|
||
<td class="num muted">{{ run.legacy_abv if run.legacy_abv is not none else '—' }}</td>
|
||
<td class="mono nowrap">{{ run.final_date or '—' }}</td>
|
||
<td class="comment">
|
||
{% if run.comments %}{{ run.comments[-1] }}{% else %}<span class="faint">—</span>{% endif %}
|
||
</td>
|
||
<td class="right">
|
||
<a class="btn btn--ghost btn--sm"
|
||
href="{{ url_for('brews.run_detail', run_id=run.beer_run_id) }}">Open</a>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="card">
|
||
<div class="card__head">
|
||
<h2 class="card__title">Standard vs legacy alcohol figures</h2>
|
||
<span class="card__hint">The old app used (max − min) ÷ 7.36 + 0.5, which is not a recognised formula</span>
|
||
</div>
|
||
<div class="card__body card__body--flush">
|
||
<div class="table-wrap">
|
||
<table class="data">
|
||
<thead>
|
||
<tr>
|
||
<th>Run</th>
|
||
<th>Name</th>
|
||
<th class="num">Standard ABV</th>
|
||
<th class="num">Legacy ABV</th>
|
||
<th class="num">Overstated by</th>
|
||
<th>Finished</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for row in abv_rows %}
|
||
<tr>
|
||
<td class="mono">{{ row.beer_run_id }}</td>
|
||
<td>{{ row.name }}</td>
|
||
<td class="num">{{ '%.2f' % row.abv }}%</td>
|
||
<td class="num muted">{{ '%.2f' % row.legacy_abv }}%</td>
|
||
<td class="num">
|
||
{% if row.delta > 0 %}
|
||
<span class="chip chip--warn">+{{ '%.2f' % row.delta }}</span>
|
||
{% else %}
|
||
<span class="faint">{{ '%.2f' % row.delta }}</span>
|
||
{% endif %}
|
||
</td>
|
||
<td class="mono nowrap">{{ row.final_date or '—' }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
{% endblock %}
|
||
|
||
|