---
_fragment: report
_of_phase: generate
_contributes:
- migration-report.html
---
# Generate — Stakeholder HTML Report (Heroku)
> Thin shareable HTML for SAs / founders. Not a full GCP/Vercel assessment
> clone — decision + costs + optional what-if scenarios, then point at
> `MIGRATION_GUIDE.md` for procedure. Runs **after** docs so the guide exists
> when the report links to it.
>
> **This file is the single source of truth for `decision-summary` /
> `decision-basis` / `exec-costs` / `what-if-scenarios` content rules.**
> `references/shared/report-decision-core.md` (loaded by the Decision gate,
> `estimate-assemble.md` choice A) reuses these same rules to render
> `decision-report.html` — it does not restate them. If you change a section's
> content rule here, it applies to both outputs.
**Execute ALL steps in order. Do not skip.**
---
## Inputs
| Artifact | Use |
| ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `estimation-infra.json` | Recommendation path, three cost tiers, complexity, `optimization_opportunities[]` |
| `aws-design.json` | Short service count / primary compute target |
| `preferences.json` | Region, HA, arch (active scenario = working tree) |
| `scenarios/index.json` + manifests **+ each scenario's `scenarios/scenario-NNN.preferences.json` / `.aws-design.json` copies** | What-if table when ≥2 scenarios — manifests carry the cost tiers/complexity; the per-scenario Region/HA/Compute/Arch columns come from the scenario's preferences/design copies |
| `MIGRATION_GUIDE.md` | Must already exist (docs fragment ran first) |
---
## Step 1: Gather figures
From `estimation-infra.json`:
- `recommendation.path_label` (or `financial_summary.recommendation`)
- `recommendation.outcome` / `outcome_label` / `conditions[]` / `decision_basis` /
`would_flip_if[]` when present (v2 decision fields from Estimate Part 8 — tolerate
absence on pre-extension artifacts and fall back to `path_label`)
- `recommendation.confidence` when present
- `projected_costs.aws_monthly_premium` / `_balanced` / `_optimized`
- `complexity_tier`
- `pricing_source.status` (for a one-line pricing confidence note)
From design + preferences: region, primary compute (EB / Fargate / EKS),
service count.
---
## Step 2: Write `migration-report.html`
Write a **self-contained** HTML file to `$MIGRATION_DIR/migration-report.html`
(inline CSS only). Required section IDs:
| Section ID | Content |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `decision-summary` | Typography-first verdict (see below), cost one-liner, next action |
| `exec-costs` | Heroku-vs-AWS side-by-side when a Heroku baseline exists (`current_costs.source != "unavailable"`); otherwise the AWS three-tier table with a one-line note that no Heroku baseline was available. Estimated monthly; Balanced primary |
| `cost-optimization` | Reserved Instance / Savings Plan opportunities table, or the explicit no-eligible-commitment statement — see below. Always present, never omitted |
| `next-steps` | Ordered list pointing to `MIGRATION_GUIDE.md` phases (not a procedure dump). Include one bullet noting the Terraform ships with `baseline.tf` (account security baseline — GuardDuty, CloudTrail, budget alerts) and that three contact emails must be set in tfvars before `terraform plan` |
**Cost-figure anchor (machine-checkable).** In `exec-costs`, wrap the recommended AWS
monthly (Balanced) dollar figure in a `data-cost-key="aws_monthly_balanced"` attribute so
the validator can confirm it matches `estimation-infra.json` `projected_costs.aws_monthly_balanced`
— e.g. `$112/mo`. The attribute is not
reader-visible text. The validator asserts the rendered dollars equal the JSON for every
anchor present. **The `aws_monthly_balanced` anchor is mandatory whenever that JSON value
exists and `exec-costs` is rendered — a missing anchor is a validator FAIL, not a skip.**
Only untagged _illustrative_ numbers are skipped.
### `decision-summary` content (REQUIRED)
1. **Verdict (typography-first — the thesis of this section):** When
`recommendation.outcome` exists, render `outcome_label` as the section's
**headline** in large display type (e.g.
`
Go, with conditions
`), then one body-type
metadata line: `Execution shape: [path_label] · Complexity: [complexity_tier]`.
Do **not** render the verdict as a row of colored pill badges — structure
carries the information; meaning must never depend on color alone (a muted
accent on the headline is fine; the words carry the verdict). When `outcome`
is absent (pre-extension artifacts), use `path_label` as the headline the
same way.
- `conditional_go`: render `conditions[]` as a short checklist under the
metadata line.
- `defer_for_evidence`: lead with what IS established ("AWS can host this
stack; AWS-side estimate $X–$Y/mo"), then the named missing evidence and
how to obtain it — never present defer as "no answer," and do not show a
savings headline as if the decision were made.
- `stay`: headline is the stay label; do not imply a migrate path.
2. **Confidence pointer (when `confidence` or `decision_basis` exists):** one
line under the verdict — `Confidence: [confidence] — full basis in
What This Assessment Rests On.`
3. **Cost one-liner** — Balanced AWS monthly vs Heroku baseline when available
(estimated monthly), else AWS Balanced alone.
4. **What would flip this (v2):** from `recommendation.would_flip_if[]` when
present — short unordered list. Skip silently when absent.
5. **One-sentence next action** — the single most useful next step for the
reader (Generate artifacts, confirm a condition, or gather named evidence).
### `decision-basis` (end of summary, when v2 fields exist)
When `recommendation.decision_basis` is present, render
`` **after** `decision-summary` and **before**
`exec-costs` (or immediately before `next-steps` if you keep costs first —
either order is fine as long as the confidence pointer's href resolves):
- Heading: `What This Assessment Rests On` (plain title — never "Section N").
- Three compact columns/lists from `decision_basis`: **Measured** / **Assumed** /
**Unknown**. Invent nothing — copy the arrays as written.
- One-line pricing provenance from `pricing_source` + accuracy band.
Omit the section when `decision_basis` is absent (pre-extension artifacts).
### `cost-optimization` (REQUIRED — always render, never omit)
Render `` after `exec-costs` (before
`what-if-scenarios` when present, otherwise before `next-steps`). Source:
`estimation-infra.json` → `optimization_opportunities[]`, per the eligibility
rules and three-state model in
`references/vendored/estimate/ri-sp-eligibility.md`.
- **When `optimization_opportunities` is non-empty:** render a table with
columns Optimization, Target Services, Monthly Savings, Commitment, Effort.
Below the table, one line: "Activate credits don't cover RI/Savings Plan
upfront costs — they apply only to the ongoing discounted hourly rate."
- **When `optimization_opportunities` is empty:** do not omit the section or
leave it blank. Render one sentence: "No 1-year/3-year commitment product
applies to this architecture." When Bedrock is part of the design, add:
"Bedrock inference has a separate mechanism (Provisioned Throughput, 1- or
6-month commitment), evaluated separately from Reserved Instances/Savings
Plans."
Heading: `Cost Optimization Opportunities` (plain title — never "Section N").
### Conditional — `what-if-scenarios`
When `scenarios/index.json` exists and `scenarios[]` has **≥ 2** entries,
render `` **after** `exec-costs` and **before**
`next-steps`:
Render, in order:
- Table columns (must stay in sync with `workshop-compare.md`'s table):
| Scenario | Region | HA | Compute | Arch | Premium $/mo | Balanced $/mo | Optimized $/mo | Complexity |
| -------- | ------ | -- | ------- | ---- | ------------ | ------------- | -------------- | ---------- |
- Mark the active row (`index.active_scenario_id`).
- `estimation_summary.calculator_url` is always `null`; no calculator link is rendered.
- Under the table: active vs baseline knob deltas; any `region_note`; remind
inventory is frozen and Terraform matches the **active** scenario only.
Omit the section when workshop was declined or never entered.
### TOC + footer
- `