**Role**: Step 8 — Generate the Mode C HTML dashboard from `analysis-result.json`. **Triggered by**: CLAUDE.md when `output_mode = "C"` after Step 7 (Analyst Agent completes analysis) **Reads**: run-local `analysis-result.json`, `references/html-template.md`, `references/color-system.md` **Writes**: `output/reports/{ticker}_C_{lang}_{YYYY-MM-DD}.html` **References**: `html-template.md`, `color-system.md`, `docs/adr/0001-mode-c-rendering-strategy.ko.md` ---
Scanned 6/5/2026
Install via CLI
openskills install kipeum86/stock-analysis-agent# Dashboard Generator — SKILL.md
**Role**: Step 8 — Generate the Mode C HTML dashboard from `analysis-result.json`.
**Triggered by**: CLAUDE.md when `output_mode = "C"` after Step 7 (Analyst Agent completes analysis)
**Reads**: run-local `analysis-result.json`, `references/html-template.md`, `references/color-system.md`
**Writes**: `output/reports/{ticker}_C_{lang}_{YYYY-MM-DD}.html`
**References**: `html-template.md`, `color-system.md`, `docs/adr/0001-mode-c-rendering-strategy.ko.md`
---
## Instructions
### Step 8.1 — Load Inputs
Load in this order:
1. Read `references/html-template.md` — complete HTML skeleton with all 11 sections
2. Read `references/color-system.md` — Tailwind CSS classes and Chart.js color configs
3. Read run-local `analysis-result.json` — analysis output from Analyst Agent
4. Verify all required sections are present in run-local `analysis-result.json`
### Step 8.2 — Compute Data Confidence Indicator
From run-local `analysis-result.json`, count the `data_quality_used` grade distribution:
```
Grade A count: {N}
Grade B count: {N}
Grade C count: {N}
Grade D count: {N}
Overall:
- All critical metrics (price, revenue, net_income) Grade A → Overall A
- ≥6 of 10 key metrics Grade A or B → Overall B
- ≥4 of 10 key metrics Grade A, B, or C → Overall C
- Otherwise → Overall D (add warning banner)
```
Translate to Data Confidence Indicator text:
- Grade A: `Data Confidence: A — All key metrics independently verified`
- Grade B: `Data Confidence: B — {N} of 10 key metrics cross-referenced`
- Grade C: `Data Confidence: C — Limited source verification. Review with caution.`
- Grade D: `Data Confidence: D — ⚠️ Insufficient verified data`
### Step 8.3 — Section-by-Section Population
Populate each section of `html-template.md` with data from `analysis-result.json`.
**JSON field → HTML placeholder mapping**:
| analysis-result.json field | HTML Section | Placeholder |
|---------------------------|-------------|-------------|
| `ticker` | Section 1 | `{TICKER}` |
| `company_name` | Section 1 | `{COMPANY_NAME}` |
| `exchange` | Section 1 | `{EXCHANGE}` |
| `price_at_analysis` | Section 1, 3 | `{CURRENT_PRICE}` |
| `price_day_change` | Section 1 | `{DAY_CHANGE}` |
| `price_day_change_pct` | Section 1 | `{DAY_CHANGE_PCT}` |
| `data_mode` | Section 1 | badge class |
| `analysis_date` | Section 1 | `{ANALYSIS_DATE}` |
| `key_metrics.market_cap` | Section 3 | `{MARKET_CAP}` |
| `key_metrics.pe_ratio` | Section 3 | `{PE_RATIO}` |
| `key_metrics.ev_ebitda` | Section 3 | `{EV_EBITDA}` |
| `key_metrics.fcf_yield` | Section 3 | `{FCF_YIELD}` |
| `key_metrics.revenue_growth_yoy` | Section 3 | `{REV_GROWTH}` |
| `key_metrics.operating_margin` | Section 3 | `{OP_MARGIN}` |
| `scenarios.bull.*` | Section 2 | bull card values |
| `scenarios.base.*` | Section 2 | base card values |
| `scenarios.bear.*` | Section 2 | bear card values |
| `rr_score` | Section 2 | R/R badge |
| `verdict` | Section 2 | verdict badge |
| `sections.variant_view_q1` | Section 4 | Q1 text |
| `sections.variant_view_q2` | Section 4 | Q2 text |
| `sections.variant_view_q3` | Section 4 | Q3 text |
| `sections.precision_risks` | Section 4 | risk table rows |
| `sections.valuation_metrics` | Section 5 | valuation table |
| `sections.sotp` | Section 5 | SOTP section |
| `sections.dcf_analysis` | Section 5 | DCF subsection (after SOTP) |
| `valuation_bridge` (top-level) | Section 5b | `{VALUATION_BRIDGE_SECTION}` (4 anchors + weighted fair value + reconciliation paragraph) |
| `sections.macro_context` | Section 4/6 | Macro Environment section |
| `sections.peer_comparison` | Section 6 | peer table rows |
| `sections.analyst_coverage` | Section 7 | analyst data |
| `historical_prices` | Section 8 | Chart 1 data array |
| `income_statements` | Section 8, 9 | Charts 2/3, quarterly table |
| `sections.qoe_summary` | Section 9 | QoE section |
| `sections.portfolio_strategy` | Section 10 | strategy text |
| `sections.what_would_make_me_wrong` | Section 10 | WWMMW list |
| `upcoming_catalysts` | Section 10 | catalyst table |
### Step 8.4 — Chart.js Data Arrays
Convert structured data to Chart.js format:
**Chart 1 — 12-Month Price History**:
```javascript
// From historical_prices array: [{date: "2025-03-12", close: 165.20}, ...]
const priceLabels = historical_prices.map(d => d.date);
const priceData = historical_prices.map(d => d.close);
// Apply color-system.md priceChart config
```
**Chart 2 — Quarterly Revenue + Operating Income**:
```javascript
// From income_statements (last 8 quarters, oldest first)
const quarters = income_statements.slice(-8).map(q => q.period_label || q.period);
const revenueData = income_statements.slice(-8).map(q => q.revenue / 1000000); // in billions
const opIncomeData = income_statements.slice(-8).map(q => q.operating_income / 1000000);
// Apply color-system.md revenueBar and operatingIncomeBar configs
```
**Chart 3 — Margin Trends**:
```javascript
// From income_statements (last 8 quarters)
const grossMarginData = income_statements.slice(-8).map(q =>
q.gross_profit && q.revenue ? (q.gross_profit / q.revenue * 100).toFixed(1) : null
);
const opMarginData = income_statements.slice(-8).map(q =>
q.operating_income && q.revenue ? (q.operating_income / q.revenue * 100).toFixed(1) : null
);
const netMarginData = income_statements.slice(-8).map(q =>
q.net_income && q.revenue ? (q.net_income / q.revenue * 100).toFixed(1) : null
);
// Apply color-system.md grossMarginLine, operatingMarginLine, netMarginLine configs
```
**If historical_prices unavailable** (Standard Mode without price API):
Replace Chart 1 canvas with a text table:
```html
<div class="text-gray-400 text-sm italic p-4">
Price chart data not available in Standard Mode. Use Enhanced Mode for historical price chart.
</div>
```
### Step 8.5 — Apply Color System
From `color-system.md`:
**R/R Score badge**:
```
rr_score > 3.0 → class: "bg-emerald-900 text-emerald-300 border border-emerald-700"
rr_score 1.0–3.0 → class: "bg-yellow-900 text-yellow-300 border border-yellow-700"
rr_score < 1.0 → class: "bg-red-900 text-red-300 border border-red-700"
```
**Price change colors**:
```
day_change_pct > 0 → text-emerald-400, ▲
day_change_pct < 0 → text-red-400, ▼
day_change_pct = 0 → text-gray-400, —
```
**Verdict badges**:
```
Overweight / 비중확대 → bg-emerald-900 text-emerald-300
Underweight / 비중축소 → bg-red-900 text-red-300
Neutral / 중립 → bg-gray-700 text-gray-300
Watch / 관찰 → bg-blue-900 text-blue-300
```
**Data confidence badge colors**:
```
Grade A → bg-emerald-900 text-emerald-300
Grade B → bg-blue-900 text-blue-300
Grade C → bg-amber-900 text-amber-300
Grade D → bg-red-900 text-red-300
```
### DCF Valuation Rendering (if sections.dcf_analysis exists)
Place AFTER the Valuation + SOTP section.
HTML structure:
- Subsection heading: "DCF Valuation"
- Base case highlight: large text showing "${fair_value} ({upside_pct}%)" with color (green/red based on sign)
- Sensitivity table: HTML <table> with WACC rows × terminal growth columns
- Header row: terminal growth rates
- Each cell: "${fair_value}" with color badge
- Green: upside > 10%, Gray: -10% to +10%, Red: downside > 10%
- Bull/Bear line: "Bull: ${X} (+Y%) | Bear: ${X} (-Y%)"
- Methodology note: small text below showing WACC, terminal growth, forecast years
- If dcf_analysis is absent or null: omit entire subsection
### Valuation Bridge Rendering (if top-level `valuation_bridge` exists)
Place AFTER the DCF / Reverse DCF subsection and BEFORE the Peer Comparison
section (Section 5b). The bridge reconciles 4 valuation anchors (DCF Base,
Comp Multiples, Analyst Median Target, our Base Scenario) into a weighted
fair value plus a ≥50-word `reconciliation_logic` paragraph.
Substitute the `{VALUATION_BRIDGE_SECTION}` placeholder in `html-template.md`
with the full section markup shown in the Section 5b comment block when
`valuation_bridge` is present in `analysis-result.json`. When the field is
absent (older snapshots, or runs without all three input anchors), replace
the placeholder with an empty string — do NOT render an empty stub.
Rendering rules:
- 4 anchor cards in a `md:grid-cols-4` grid; show value, weight × 100 as
integer percentage, method, and source tag.
- Implied view colour: `text-red-600` if negative, `text-green-600` if
positive, `text-gray-600` if ~0.
- Pass `reconciliation_logic` through verbatim — do NOT truncate.
- Display `decision_anchor` as a small footnote so the reader knows which
anchor drives the verdict (typically `scenarios.base`).
### Macro Context Rendering (if sections.macro_context exists)
Place BETWEEN Precision Risk and Peer Comparison sections.
HTML structure:
- Section heading: "Macro Environment" with globe icon
- If `macro_context.structured.status == "available"`: render only the provided `series[]` values, with `[Macro]`/grade labels.
- If `macro_context.structured.status == "unavailable"`: show "Macro data unavailable" plus the `reason`, and do not render quantitative macro cards.
- Narrative paragraph(s)
- Factor cards (horizontally laid out, 2-3 cards):
- Factor name (bold)
- Impact line (normal text)
- Confidence badge: High (green), Medium (amber), Low (gray)
- If risk_slot_allocated is true: note with link to Precision Risk section
- If macro_context is absent or null: omit entire section
### Auto Delta Banner Rendering (Phase B — `{DELTA_BANNER}` placeholder)
The dashboard skeleton in `references/html-template.md` reserves a
`{DELTA_BANNER}` placeholder at the top of `<main>`, above the Scenario
Valuation section. This is the Mode C surface for Phase B Auto Delta Mode.
**Substitution rules**:
1. If the orchestrator pipeline state holds `auto_delta_payload.html`
(stdout of `delta-comparator.py compare ... --format html`), substitute
that string verbatim.
2. If no payload is available (no prior snapshot, `--no-delta` toggle,
sanitization failure, or empty stdout), substitute **the empty string**.
Do NOT render an empty stub, a fabricated banner, or the literal
placeholder. Empty-string substitution leaves the surrounding `space-y-8`
layout intact because the banner is a peer of other `<section>` blocks.
3. Never modify the Scenario Valuation, Hero, or Variant View sections to
make room for the banner — the placeholder is positioned to keep the rest
of the document untouched.
4. The banner is a self-contained `<section class="delta-banner ...">`. Do
not wrap it in another `<section>` or strip its outer element.
When `pipeline_state.auto_delta == false` (because `--no-delta` was passed),
treat the placeholder substitution as the empty string and skip the
delta-comparator call entirely.
### Step 8.6 — Missing Data Handling
For any section where data is null or Grade D:
```html
<!-- Instead of leaving empty or omitting: -->
<div class="text-gray-500 text-sm italic">[Data unavailable]</div>
```
Do NOT remove the section from the HTML. Do NOT substitute fabricated data. Always show the placeholder.
Sections with all-null data: collapse the section with a note:
```html
<div class="bg-gray-800/50 border border-gray-700 rounded-lg p-4 text-gray-500 text-sm">
Section data not available for this analysis. Data confidence insufficient.
</div>
```
### Step 8.7 — Write HTML File
1. Replace all placeholders with actual values
2. Ensure Chart.js initialization code is complete with actual data arrays
3. Verify HTML is well-formed (all tags closed)
4. Write to: `output/reports/{ticker}_C_{lang}_{YYYY-MM-DD}.html`
5. Report path to user
Do not use `scripts/render-dashboard.py` for final delivery or critic patch-loop rerenders. That script is eval-only. If a Mode C patch changes `analysis-result.json`, repopulate the full `html-template.md` manually/template-guided from the patched artifact and run the rendered output validator again before delivery.
Language suffixes: `EN` or `KR`
Example: `output/reports/AAPL_C_EN_2026-03-12.html`
---
## Multi-Ticker Mode B HTML Generation
When called for Mode B (comparison), use `mode-b-template.md` instead of `html-template.md`.
Path: `output/reports/{T1}_{T2}_{T3}_B_{lang}_{YYYY-MM-DD}.html`
Load each ticker's validated-data from the run-local artifact root, or from `output/data/{ticker}/latest.json` refs when explicitly reusing a fresh snapshot.
---
## Completion Check
- [ ] `html-template.md` loaded
- [ ] `color-system.md` loaded
- [ ] `analysis-result.json` loaded and all required fields present
- [ ] Data Confidence Indicator computed
- [ ] All 11 sections populated (or placeholder for missing)
- [ ] Chart.js data arrays correctly formatted (labels array, datasets array)
- [ ] R/R Score badge uses correct color class
- [ ] Verdict badge uses correct color class
- [ ] Missing data uses `[Data unavailable]` placeholder (NOT removed)
- [ ] HTML written to correct path
- [ ] File path reported to user
No comments yet. Be the first to comment!