| name | template-hardcoded-literal-vs-existing-payload |
| description | Diagnose "the dashboard shows the wrong number even though the backend
payload has been correct for months." Use when: (1) auditing rendered
numbers against a baked / cached / pre-aggregated payload table, (2)
finding that the payload row in BQ / Redis / KV has the right value but
the template renders a stale hardcoded literal (e.g. "~930 students"
next to a payload that says cohort_size=94), (3) earlier PRs in the
area updated copy / historical-rate language but never wired the
headline integer to the payload key, (4) other surfaces on the same
page DO read the payload correctly (proves the wiring pattern exists)
but specific tiles were missed during a redesign carve-up.
Distinct from `baked-payload-stale-after-merge` (producer didn't
re-run) and from `cloud-run-results-bq-postsync` (write race) — this
is the inverse: the producer has been correct for a long time, the
CONSUMER never connected to it. Provides the "for each rendered
number on every audit page, classify as payload-derived / hardcoded /
mock-data" diagnostic + the fix-shape (template variable wiring,
zero data-pipeline change).
|
| author | wan-huiyan + Claude Code |
| version | 1.0.0 |
| date | "2026-05-07T00:00:00.000Z" |
Template hardcoded literal vs existing payload
Problem
The dashboard / page / report renders a stale headline number. Common reaction: "the bake must be broken" or "the deploy must not have shipped." Both wrong. The bake has been producing the correct value for weeks; the template just never read it. The literal sits in HTML next to a payload accessor it never references.
This is silent. No error. No broken build. The page renders. Numbers off by 2.5×–10×. Counselors export the cohort thinking "930 to call" and Salesforce shows 94.
Context / Trigger Conditions
All of:
- The page reads a baked / cached / pre-aggregated payload table for SOME of its content (KPI tiles, charts, etc.). Other surfaces work correctly.
- The audit shows a specific tile / cell / heading rendering a value that does NOT match the payload row's value.
- The template source for that tile shows a literal (e.g.
~930, 162 enrollment signals, 9,747 deduped), not a {{ variable }} accessor.
git blame on the literal points at an old PR that mass-edited copy; subsequent PRs in the area touched OTHER content (historical rates, headline wording, layout) but never came back to wire the headline integer.
- A SISTER tile on the SAME page (e.g. A8, A9 in a series A1–A9) DOES read the payload correctly — proves the wiring pattern is supported, just unused for the broken tiles.
The strongest signal: the audit can produce a per-tile table where columns are Element / Source (template-literal vs payload-derived) / Claimed / Actual (BQ) — and ALL the offenders cluster at "template-literal" while the OK ones are "payload-derived."
Solution
Step 1: Classify every rendered number
Don't fix the first one you find. Do a per-tile classification first:
| Element | Source | Claimed | Actual (payload) | Verdict |
| A1 cohort tile | hardcoded | ~930 | 94 | ❌ |
| A2 cohort tile | hardcoded | ~290 | 295 | ✅ ish |
| A8 lede | payload-derived | (live) | 795 | ✅ |
| A9 metric | payload-derived | (live) | 2,376 | ✅ |
The pattern is clear: A1–A7 are unwired; A8/A9 already work. The fix is "extend the wiring pattern A8/A9 use to A1–A7."
Step 2: Confirm the payload exists and is fresh
SELECT payload_key, scoring_date, built_at,
JSON_VALUE(payload, '$.cohort_size') AS cohort_size
FROM `proj.dataset.payload_table`
WHERE payload_key LIKE 'actions_cohort_top25__%'
AND scoring_date = (SELECT MAX(scoring_date) FROM `proj.dataset.payload_table`);
If the payload exists with the right value at a recent built_at: bake is fine, the gap is in the consumer.
If the payload is missing or stale: this is the wrong skill — see baked-payload-stale-after-merge.
Step 3: Fix in the consumer, not the producer
Two paths:
Route-handler threading. The simplest. Load the payload in the route, pass into the render context as a variable, replace the literal with {{ variable }}:
def page_route():
cohort_sizes = {
aid: _cohort_size(aid) for aid in ("a1", "a2", "a3", "a4", "a5")
}
return render_template("page.html", cohort_sizes=cohort_sizes, ...)
<div class="metric-cohort">
<strong>{% if cohort_sizes and cohort_sizes.a1 %}{{ "{:,}".format(cohort_sizes.a1) }}{% else %}—{% endif %}</strong>
students
</div>
Always include a fallback (or '—', or '...') so a missing/stale payload renders a dash instead of crashing the page. The audit / monitoring will flag dashes; users will not be misled by a wrong number.
Globals threading. If the variable is page-agnostic (e.g. a model-version count that should be the same on every page), use the framework's globals injector (@app.context_processor in Flask, app.locals in Express, useContext in React) and read it in templates without route plumbing.
Step 4: Update tests for the SHAPE, not the literal
If a test asserted on the literal ~930, it was already wrong (it pinned the bug). Update tests to assert that:
- The page renders SOME number (not a dash), proving the wiring works.
- Optionally: the rendered number matches a fixture / mock payload with a known value.
Don't pin tests to live BQ values — the bake refreshes daily and the test would flake.
Step 5: Watch for the "redesign carve-up" pattern
This bug-shape is most common after a multi-PR redesign: PR-1 ships layout, PR-2 ships copy, PR-3 ships visual fidelity, PR-N ships filter row. Each PR is small and focused; "wire the integers to live data" gets deferred and then forgotten. After the redesign closes out, the cohort tiles render whatever literals the original layout PR shipped with.
Good signal: when reviewing a redesign-cluster PR chain in git log, ask "which PR wired the data?" If the answer is "no specific PR did," look at the rendered output against the live data before the redesign declares done.
Verification
- BQ probe of payload row matches what the page now renders.
- Targeted test suite green (page renders without crashing, renders the wired variable).
- Visual smoke against the rendered page (localhost or deployed) confirms the headline integer matches BQ.
Example
Setup: Multi-PR redesign of an /actions page on a propensity-modelling dashboard shipped 8 PRs over 2 weeks. Audit found 5 of 9 metric tiles (call them A1/A3/A4/A5) render ~930 / ~325 / ~412 / ~7,500 while live cohort sizes from the existing payload key actions_cohort_top25__a{N} are 94 / 52 / 3,233 / 2,984. The remaining tiles (A8/A9) render the live n_total correctly — proves the wiring pattern works for some tiles, just not all.
Fix: ~30 LoC change. Route handler loaded cohort_sizes dict from the existing payload accessor; template replaced 5 literals with {{ cohort_sizes.aN }}. No bake change. No data pipeline change. Single PR.
Result: A1 was 10× overstated; daily-user exports of the cohort now match the source CRM. Hero stat "9,747 deduped" — also unverifiable — replaced with live sum across cohorts (with caveat "summed across cohorts; some may overlap" rather than the false "deduped" claim).
Notes
-
Always classify before fixing. The temptation is to fix the first wrong number you see. But the audit table reveals whether this is a wiring problem (5+ tiles unwired, fix pattern is the same) or a single rendering bug (one tile, different fix shape). The classification step is what makes this scalable.
-
Naming the caveat matters. If the original literal said "deduped across cohorts" and your sum is NOT deduped, don't keep the word "deduped." Rename it ("summed across cohorts (some may overlap)"). The audit will flag the language drift; don't ship the new number under the old caption's claim.
-
A4-shaped pattern: literal describes the wrong cohort. Worst case found in this audit — A4's literal ~412 was the fresh baseline from a historical analysis, but the SQL filter behind A4 targets stale (>60d) cohorts (live n=3,233). The literal was off AND it described the inverse cohort of what the action targets. Fix: wire to live cohort_size, then in PR description note that the framing may need a follow-up review.
-
Sister patterns:
baked-payload-stale-after-merge — producer didn't re-run after a code change.
bq-loadjob-json-column-double-encode — bake-time shape bug.
- This skill — consumer never connected to a correct producer.
-
The diagnostic table format (Element / Source / Claimed / Actual / Verdict) is the same one used by whole-dashboard-factcheck-via-parallel-cluster-agents (sister skill in this toolkit) for the audit phase. The classification result of that audit IS the work-list for this skill's fix step.