# Sheet Show

> Turn an Excel report (.xlsx) into one self-contained interactive HTML briefing leadership will actually read: KPI strip with caveats, faceted lenses (themes, origin, sector × type, cheque size), searchable entity cards, a detail drawer per entity with its records, and a co-investor network — offline, one file, no libraries. Built from an IB buyer-universe / transaction-screening report; fits any entity ↔ record workbook (investors ↔ deals, customers ↔ orders, peers ↔ data points). ALWAYS use when the user wants an Excel report made "readable", "presentable", "visual", "easy to look up", "leadership-friendly", wants "quick insight" or to "see the connections", or asks to convert a spreadsheet / deal list into HTML, a web page, a briefing, a one-pager or a dashboard for a boss, MD, partner, IC or client — even without saying "HTML" — and whenever a Buyer Universe, transaction screening or precedent-transactions workbook must reach someone who won't open Excel.

- Skill: `felixx1502/sheet-show` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add felixx1502/sheet-show`
- Raw SKILL.md: https://api.skillmd.com/api/skills/felixx1502/sheet-show/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: Felixx1502 (https://skillmd.com/u/felixx1502)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/felixx1502/sheet-show

---


# sheet-show

Excel is where the analysis happens; it is not where it gets read. Leadership
wants the three numbers, the slice they care about, and one click to the
detail — not thirteen tabs. This skill produces that page from the workbook,
and keeps every number on it traceable back to the sheet the boss already has.

## What you produce

One `.html` file (≈0.5 MB for ~300 records), no external assets, opens from
disk. Layout: sticky header + search → KPI strip with a caveat → lens rail
(themes / origins / sector×type / cheque bands) → active-filter chips → card
grid of entities → footer with source and join rate and the author's name.
Clicking a card opens a drawer: stats, every record with expandable detail,
vehicles used, co-investor ring graph. Filters AND across lenses and the
counts in each lens are computed with that lens held out, so the page answers
"what if I also pick…" before the reader clicks.

See `references/design-system.md` for why each of those exists, and keep the
reasons when you adapt it.

## Workflow

### 1. Map the workbook before touching code

```bash
pip install openpyxl
python scripts/inspect_xlsx.py "Report.xlsx"            # whole workbook
python scripts/inspect_xlsx.py "Report.xlsx" -s "Sheet"  # one sheet, more rows
```

Read the output with these questions:

- **Which sheet is the final, reconciled summary?** It usually has the
  report's own name (here `Buyer Universe`) and a totals row. Build from final
  sheets, never from raw pulls — a number leadership can't find in the
  workbook is a number they can't trust.
- **What is the lookup unit?** The thing the reader will want to click. It
  becomes the *entity* (cards). The thing that happens to it becomes the
  *record* (drawer rows). Transaction reports: buyer ↔ deal. Pipelines:
  target ↔ touchpoint. Comps: peer ↔ data point.
- **Which columns are facets?** The inspector marks low-cardinality text as
  `FACET`. Pick the 3–4 that carry the analyst's argument (class, geography,
  sector, size band). More than four lenses is a dashboard.
- **Where is the narrative?** A commentary table (themes, theses, screen
  outcomes, "what the investor is buying") is the most valuable lens on the
  page because it is the analyst's reading, not a column. Find it.
- **How do entities and records join?** Look for a bridge/alias sheet, a
  "rolled-up name" column, or a semicolon-separated buyers column. Note
  anything non-unique (ids like "Google search", duplicate target-years).

Write the mapping down in one paragraph before coding. If the lookup unit is
genuinely ambiguous (two equally plausible entities), ask — everything else
is a judgment call you make.

### 2. Build `data.json`

The template consumes a fixed shape — `references/data-model.md` — entities
(`buyers`), records (`deals`), `themes`, `origins`, `meta`, `rec`. Keep the
keys; change the labels.

Start from `scripts/build_buyer_universe.py`, the builder for the
Growth Equity Transaction Screening report, and adapt it:

```bash
python scripts/build_buyer_universe.py "Report v3.xlsx" -o data.json --author "Vỹ Mạc"
```

For a sibling version of the same report (v4, next quarter) that usually
means editing `SHEETS` / the `col(...)` candidates / `THEME_RULES` at the top.
For a different report, keep the helpers (`find_table` locates a table by its
header text so positions may move; `col` tolerates header rewording;
`s`/`num`/`split_names` normalise the `-`/`n/a`/`;` conventions) and rewrite
`build()`. `references/worked-example.md` explains each join and why it is
ordered the way it is.

Rules that make the page trustworthy:

- **Entities come from the entity sheet only.** Never invent one from a
  record's buyer string — that is how counts drift from the workbook.
- **Join by the most specific key that is actually unique**, fall back to
  `(name, year)`, and count every miss. Misses go in `rec` and in the footer.
- **Narrative tagging is code.** If themes are attached by rule, the rules
  live in `THEME_RULES`, one per theme, and a residual theme catches the rest
  so no record is invisible from the themes lens.
- **`value` is a number or `null`.** Undisclosed is `null`, never 0 or "n/a".
- **Say what an aggregate means.** If you sum round sizes across co-investors
  (the template does), the `kpiNote` caveat must say it is round size, not
  capital deployed. Write the caveat for whatever your aggregate is.

### 3. Render and reconcile

```bash
python scripts/render.py data.json -o "Buyer Universe.html"
```

`render.py` validates the invariants (ids = positions, mirrored entity↔record
links, numeric values) and prints the reconciliation block. Every line should
tie to the sheet. If one doesn't, fix the builder — do not ship a page whose
footer says 325 when the sheet says 326 and hope nobody compares.

### 4. Open it and click three things

Open the HTML in a browser (headless Chrome works for a screenshot:
`chrome --headless=new --screenshot=out.png --window-size=1400,1100 file:///…`).
Check: KPI numbers match the reconciliation; each lens renders and its counts
change when another lens is filtered; one card opens the drawer; one record
expands; search finds a target name; the footer sentence reads correctly; the
page works at 560px width. Then look at it as the boss would — is the first
screen the answer to "so what"?

### 5. Adapting the UI (only when the data model isn't enough)

Edit `assets/template.html`. It is ~860 lines: CSS tokens → components →
responsive → `<script id="DATA">` placeholder → ~420 lines of vanilla JS
(`derive()` computes the filtered set and held-out counts; `render*()`
paint; one delegated click handler). The smallest lens to copy when adding a
facet is `bands`. All user-facing strings are Vietnamese literals in the
script block; translating means editing those strings, not the logic.

Keep: the hold-out counting, the chip bar as the single source of filter
truth, `aria-*` attributes as the state hooks, the first-paint-only stagger,
the reduced-motion block, `min-width:0` on grid children, the footer source
sentence and the author signature.

### 6. Deliver

Hand back the HTML plus the reconciliation block, and tell the user:

- the file embeds every record's comments and descriptions from the
  workbook — same confidentiality as the workbook; it is not for a public
  repo or an unrestricted share link;
- what the KPI sum means (the caveat text), so it is quoted correctly;
- which records were excluded and why (undisclosed buyers, unmatched rows).

## Repo layout

`SKILL.md` · `scripts/inspect_xlsx.py` (workbook map) ·
`scripts/build_buyer_universe.py` (worked-example builder: xlsx → data.json) ·
`scripts/render.py` (data.json + template → HTML, validates, reconciles) ·
`assets/template.html` (the UI) · `references/data-model.md` ·
`references/design-system.md` · `references/worked-example.md` ·
`tests/make_demo.py` (demo workbook in the same layout) ·
`demo/` (generated from demo data only).

