Skill instructions
benchmark20.8 KB
View saved version →
---
name: benchmark
description: >
Operational peer / comp discovery for US public companies via deepKPI semantic search — not
valuation multiples. Build KPI fingerprint from SEC data; company_summary_search (same-vertical
first); align KPIs; diff gaps → segment sub-benchmarks. Outputs: tight chat 1-pager, optional
Revelata HTML, optional Excel (.xlsx). Use when comparing to peers, finding comps or most-similar
companies, benchmarking, comp sets, valuation peers, thematic "what's like X?", or segment
pure-plays. Exclude unrelated verticals (e.g., gym vs car wash) except clear segment-to-segment,
same customer + same need.
---
# benchmark
Find comparable public companies ("benchmarks") for a target company or segment using deepKPI.
The output is not a mechanical list of industry peers — it's a curated set where every benchmark
adds a specific analytical lens, and where the gaps in coverage are as informative as the matches.
**Do not pair companies that do wildly different work** — different end customers, different
underlying need, different operating model — just because semantic search surfaced them together
(e.g. a gym chain and a car-wash operator). That is not a useful whole-company benchmark. The
only exception is when a **specific segment** of each business plausibly targets the **same
customers** with products that serve the **same job-to-be-done**; in that case, treat the match as
**segment-to-segment** (Step 3 sub-benchmarks, clearly labeled), not as "these two conglomerates
are comps."
**Vertical first, then wider.** Build the core benchmark set from companies in the **same
vertical** as the target's primary business (same industry, same customer job, same operating
shape) before you add names from **outside** that vertical. Broader or cross-vertical searches are
for filling gaps after same-vertical peers are in place, for a user-requested "outside the sector"
lens, or for Step 3 segment sub-benchmarks — not as the default first pass.
The workflow is explicitly iterative. Segment sub-benchmarks are not pre-assumed based on what
segments exist; they are discovered by diffing the target's KPI fingerprint against what the
whole-company benchmarks can actually explain. If something in the target's operations has no
counterpart in the whole-company set, that's the signal to go deeper.
## Data source
See `../_common/data-source-rules.md` for the runtime routing table and the canonical
inventory of deepKPI operations.
## Hard stop if deepKPI access fails
See `../_common/connection-failure.md`. STOP and ask before falling back.
## Dependencies
Before starting, consult:
- The `kpi` skill — retrieval workflow, provenance rules, scaling artifact
handling, KPI search strategy. Required for all data pulls.
- `../_common/revelata-excel-styling-rules.md` — only if user requests Excel output.
- The `implied-metric` skill — when filling Q4 or segment gaps in candidate
alignment.
Every number displayed must have a `[value](provenance-url)` link. No exceptions.
## Output mode — ask first
**Before pulling any data**, ask the user which output format they want:
> "Would you like an **interactive HTML report** (opens in browser, clickable provenance links, Chart.js **time-series** charts) or **in-chat markdown** output? Or both?"
- If **HTML**: generate the full HTML report (dark Revelata branding, Chart.js line charts for KPI **trends over time**, fingerprint grid). Also produce the chat 1-pager as a compact summary.
- If **in-chat**: produce only the chat 1-pager with full markdown provenance links (see rules below). Offer the HTML at the end.
- If **both**: produce chat 1-pager first, then generate the HTML.
Offer Excel at the end regardless of which mode is chosen, unless the user already requested it.
## Outputs
| Format | When | Description |
|--------|------|-------------|
| Chat 1-pager | Always | Fingerprint pill rows + master table + KPI alignment + ≤5 notes — **all values hyperlinked** |
| HTML report | If user chose HTML or both | Dark Revelata-branded report with fingerprint grid, benchmark cards, and Chart.js **KPI trends over time** (line charts: target vs benchmarks on a shared period axis) |
| Excel workbook | User requests .xlsx | KPI alignment table, periods as columns per `../_common/revelata-excel-styling-rules.md` |
### In-chat provenance link rules (non-negotiable)
Every numeric value shown in chat **must** be a markdown hyperlink to its source filing passage. No bare numbers.
Format: `[+4.9%](https://www.revelata.com/docviewer?pvid=11240)` — the pvid comes from the deepKPI response.
This applies everywhere in the chat output: fingerprint bullets, the KPI alignment table, notes, and the diff insight. If a value has no pvid (e.g. an estimate or derived metric), label it explicitly as `~$X est.` or `implied` with no link. Never omit the link for a directly sourced value.
Example of correct in-chat KPI alignment table row:
```
| TXRH | [+4.9%](https://www.revelata.com/docviewer?pvid=11240) | [$8.7M](https://www.revelata.com/docviewer?pvid=10061) | [714](https://www.revelata.com/docviewer?pvid=11537) |
```
---
## Workflow
The four steps below run in sequence. Steps 1–3 are analytical; Step 4 is output generation.
Run all deepKPI calls in parallel wherever possible to save time.
---
### Step 1: Build the target's KPI-enriched fingerprint
This is the foundation everything else builds on. Pull it from deepKPI — do not use general
knowledge for specific metrics.
**Calls:** `query_company_id` → `get_company_summary` → `get_company_segments` → `list_kpis`
→ `search_kpis` (for each segment's signature metrics)
**What to extract:**
**A. Narrative** — 2-3 sentences: business model, revenue drivers, customer type, go-to-market,
geography. Ground it in numbers where possible (unit count, revenue scale, market share if stated).
**B. Segment map** — For each reportable segment:
- Name and revenue share %
- One-sentence description of the economic model (how it makes money, who pays)
- 3-5 signature KPIs with actual values from deepKPI, each with provenance link
The segment KPIs are what make this fingerprint useful for diffing. For an airline: ASMs, load
factor, CASM, TRASM. For a refinery: capacity bpd, utilization %, gross margin per barrel.
For a restaurant segment: unit count, comp sales %, AUV. Pull the real numbers.
**C. KPI signature** — The 3-5 metrics that matter most for the consolidated company. These
become the columns in the KPI alignment table in the output.
**Output of Step 1:** An internal working document (not shown to user) with:
```
Target: [TICKER] — [Company] (CIK: NNNNNN, FY: DATE)
Primary vertical (for comp search ordering): [e.g. full-service restaurant, regional bank, SaaS HCM]
Narrative: ...
Segments:
[Segment A] (XX% rev): [description] | KPIs: metric1=[val](url), metric2=[val](url), ...
[Segment B] (XX% rev): [description] | KPIs: metric1=[val](url), ...
KPI signature: metric1, metric2, metric3 [, metric4, metric5]
```
---
### Step 2: Find candidates and align their fingerprints
**A. Search**
Run `company_summary_search` with 3-5 queries. Each query should describe a *business model*,
not just a sector name. The goal is semantic richness — describe what the company does, for whom,
how it earns money, and what drives its unit economics.
**Order matters — same vertical first.** At least **the first two queries** must stay tightly
inside the target's **primary vertical** (from Step 1: summary, segments, and the "Primary
vertical" line). Pull and KPI-align those results before you run looser or cross-vertical queries.
Use additional queries to go **outside** the vertical only **after** you have a credible
same-vertical candidate set, or when the user explicitly wants analogies from other sectors, or
when Step 3 needs a segment-specific search.
Examples of the contrast:
- Weak: `"airline"` → Strong: `"hub-and-spoke network carrier international domestic routes premium cabin loyalty co-brand revenue per ASM"`
- Weak: `"restaurant chain"` → Strong: `"multi-brand casual full-service dining operator franchised same-store sales average unit volume"`
- Weak: `"software company"` → Strong: `"B2B SaaS subscription recurring revenue net revenue retention enterprise mid-market"`
Use `top_k_companies: 10-12` per query. Deduplicate across queries. Target 6-12 candidates.
**B. KPI alignment**
For each candidate: call `list_kpis` (free), then `search_kpis` for the target's KPI signature
metrics (from Step 1C). This produces aligned rows — the target's metrics sitting next to each
candidate's equivalent metrics.
**Recency rule (do this from the start):** when the user's question is about "current" / "latest"
comparability, prefer the **most recent quarterly periods** available (10-Q-derived) for each KPI
over stopping at the last 10-K annual figure. Use annual series for long-cycle context, but do not
ship a benchmark set that effectively says "data is old" if newer 10-Q periods exist for the KPI.
The alignment reveals two things:
1. How close each candidate is on the dimensions that matter (similar range = structurally comparable)
2. Where candidates have no data or wildly different values (poor fit on that dimension)
Coverage coding:
- **Green**: metric present and plausible
- **Yellow**: metric present but value seems off (scaling artifact, possible cross-contamination)
- **Red**: no data
**C. Tier the candidates**
Based on the aligned KPI fingerprints (not just the description similarity), and only after
passing the **structural fit gate** below:
- **Tier 1**: Closest structural match — same business model and same customer/need, similar values on 3+ signature KPIs
- **Tier 2**: Useful comparators — same overall job-to-be-done; good match on some KPIs, different on others; adds a specific lens
- **Tier 3**: Same **kind** of business as the target, different scale, mix, or geography — still
a coherent comp (e.g. regional vs national airline). **Not** unrelated verticals "for color."
**D. Structural fit gate (drop before the output table)**
Before anything appears in the chat 1-pager or HTML as a whole-company benchmark, ask: *Would
an investor reasonably describe this company as doing the **same job** for the **same type of
buyer** as the target?* If **no**, exclude it from Tier 1–3 for whole-company analysis — even if
a few metrics line up by coincidence. If there is a **real but narrow** overlap (same
wallet, same purchase occasion, same enterprise buyer for a specific product line), keep the
candidate **only** as a **segment** sub-benchmark in Step 3 with an explicit "Maps to: Segment …"
story, not as a peer for the consolidated company.
---
### Step 3: Diff — find what the whole-company benchmarks don't explain
This is the step that surfaces the most analytically valuable insights.
**The question:** After aligning KPI fingerprints in Step 2, which parts of the target's
operations have *no good coverage* in the whole-company benchmark set?
A "coverage gap" exists when:
1. The target has a reportable segment whose signature KPIs appear nowhere in the Tier 1/2 benchmark set, OR
2. The target reports KPIs that are structurally foreign to all benchmark candidates
(e.g., `refinery throughput bpd` when all candidates are airlines), OR
3. A major segment has a fundamentally different economic model than what any whole-company
benchmark represents
**For each gap identified:**
a. Build a **segment-specific fingerprint** using the segment's own KPIs from Step 1B
("this segment, if it were a standalone public company, would look like:")
b. Run a fresh `company_summary_search` describing that segment as a standalone business.
Sub-benchmarks must still satisfy **same customer + same need** for that segment; do not
pull unrelated verticals "for coverage."
Use the segment's KPIs as the anchor — e.g., for a refinery segment:
`"independent petroleum refiner crude oil throughput barrels per day capacity utilization refining margin per barrel crack spread"`.
Not: `"refinery"`.
c. Pull KPI alignment for sub-benchmark candidates using the *segment's* KPI signature
(not the parent company's consolidated metrics)
d. Tag all matches as `Maps to: Segment: [name]` in the master table
**Important framing:** The sub-benchmark tells you what that segment *would* look like as a
standalone profit-maximizing business. When the segment operates under a different economic logic
inside its parent (cost-center, captive supplier, hedge), say so explicitly — the gap between
the sub-benchmark's economics and the segment's implied economics *is the analytical insight*.
**Example of the diff in action:**
- Target: Delta (airline + refinery)
- Step 2 finds: UAL, AAL, LUV, ALK — all cover the airline KPIs (load factor, CASM, ASMs)
- Diff: `refinery throughput bpd`, `utilization %`, `gross margin/bbl` appear in Delta's segment
map but in *none* of the airline benchmarks
- Step 3 triggers: build refinery segment fingerprint → search → find PBF, CVI, DINO as
sub-benchmarks → align on throughput/utilization/margin
- Insight that emerges: Monroe's 200K bpd capacity is closest to CVI (207K bpd combined);
PBF shares East Coast PADD 1 geography; but Monroe runs as a hedge, not a profit center —
the merchant refiner margins ($8–16/bbl) represent the *opportunity cost* Delta pays to own it
The segment sub-benchmark logic is not limited to named segments. If the target has any
operational dimension that reads as structurally foreign to the whole-company comps —
a financial arm, a logistics network, a retail footprint embedded in a manufacturer —
ask whether a standalone pure-play exists in the S&P 1500 that illuminates it.
---
### Step 4: Build outputs
#### Chat 1-pager
Tight. No prose sidebars. Everything fits on one screen. In the **benchmark table** and **KPI
alignment** table, put **same-vertical** names first (clearest structural peers in the primary
vertical), then cross-vertical or segment-mapped comps, with `Maps to` spelling out any segment
story.
```
# Benchmarks for [Company] ([TICKER])
[TARGET FINGERPRINT — 2-3 sentences. Every sourced metric hyperlinked:
e.g. [+2.0% blended comps](https://www.revelata.com/docviewer?pvid=578),
[$12.1B revenue](https://www.revelata.com/docviewer?pvid=11442)]
Segments: [Segment A] XX% rev | [Segment B] XX% rev
## Benchmark table
| Ticker | Maps to | Similar on | Different on | Scale vs [TARGET] |
|--------|---------|------------|--------------|-------------------|
## KPI alignment — latest available period(s)
| Company | [metric1] | [metric2] | [metric3] | [segment metric] | Period |
|---------|-----------|-----------|-----------|-----------------|--------|
| [TARGET] | [val](pvid-url) | [val](pvid-url) | [val](pvid-url) | [val](pvid-url) | FY... or FY..-Q.. |
| BENCH1 | [val](pvid-url) | [val](pvid-url) | — | — | FY... or FY..-Q.. |
Column headers are plain text. Only the value cells carry hyperlinks.
Estimates/derived values: `~$X est.` with no link.
## Notes
- [≤5 bullets — only material caveats: scaling flags, cross-contamination, structural differences,
coverage gaps, economic logic mismatches (cost-center vs. profit-maximizer)]
## Sources
[Ticker (CIK) — Green/Yellow/Red — FY end — any corrections]
```
If a section would be empty, omit it. If the KPI alignment table would exceed 10 rows, drop Tier 3
from it (keep Tier 3 in the benchmark table only).
#### HTML report
Read `references/html-template.md`. Key structure:
- **Hero fingerprint grid** (`table.fp-grid`): KPIs × benchmark columns only — **no prose inside the grid**
(no "Why it matters", thesis blurbs, or `fp-context`-style rows). Put narrative in benchmark cards
and diff insight. **`.legend-strip`** (color key) sits **below** the grid, not in the page header.
- **Benchmark cards** (one per company): order **same-vertical** peers before cross-vertical or
segment-mapped names. Tier-colored top border, similar/different columns,
key KPI values hyperlinked. Segment sub-benchmarks use purple border + "Segment:" badge.
- **KPI trends over time (charts + alignment table)**: This section is where deepKPI's edge shows up—**filing-resolved time series** most vendors do not reconstruct at this depth. For the **2–3 most important** signature KPIs, pull **multi-period** values for the target **and every benchmark** (prefer quarterly where coverage is solid; otherwise annual—follow the `kpi` skill for period selection, scaling, and provenance). Render **Chart.js line charts** (not single-period grouped bars): **x-axis = time** (ordered FY/FQ labels), **one line per company** so the reader compares **trajectory, inflection, and pace** across competitors on the **same** metric. Put the full **KPI alignment** table (with hyperlinked values) **below** the charts as today. If a KPI truly has fewer than three comparable periods across the set, you may show a sparse line chart **or** a one-line caption pointing readers to the table—do not imply a cross-sectional bar is equivalent to the time-series view.
- **Chronology QA for every time-series chart (mandatory)**: Ensure the plotted points are in **chronological order** for every company series and the x-axis labels are in **ascending time**. **Do not rely on input order.** Sort by a real time key (prefer **period end date**; otherwise parse FY/FQ labels into a comparable key) before passing points to Chart.js. A zig-zagging line is almost always an ordering bug.
- **Y-axis QA for every time-series chart (mandatory)**: **Double-check** each chart's **Y scale** before shipping HTML. The visible **min/max domain must include every non-null plotted point** across **all** series (target + benchmarks). **Do not** set fixed `min`/`max`, `suggestedMin`/`suggestedMax`, or `beginAtZero` unless those bounds are **recomputed from the union** of all series values—wrong bounds **clip lines off-canvas** so they look missing. Prefer automatic scaling plus **padding** (e.g. Chart.js `scales.y.grace` as a %) so tight clusters stay visible. For **log** scales, omit non-positive points or avoid log if that would drop the series; confirm lines still render. **Mentally verify each tab**: any ticker with data must show a **visible** polyline or points—not a legend entry with no trace.
- **Diff insight box** (cyan glow, same as synthesis in pressure test): 2 paragraphs on what
the benchmark set reveals that whole-company analysis would miss.
- **Notes** and **Sources** footer.
#### Excel workbook
Follow `../_common/revelata-excel-styling-rules.md`.
One sheet for whole-company benchmarks, one sheet per segment sub-benchmark group.
Annual periods as columns; green cells for target values; clickable provenance links.
---
## Failure modes
- **Assuming segment sub-benchmarks without doing the diff**: Don't pre-decide "this company
has a real estate arm so I'll find REITs." Let the KPI gap analysis surface it. The diff step
earns its keep precisely because the gaps are often surprising.
- **Narrative-only fingerprint**: If Step 1 doesn't pull actual KPI values, the diff in Step 3
has nothing to work with. The fingerprint must have numbers.
- **Prose inside the HTML hero grid**: Do not add "Why it matters", thesis blurbs, or other
commentary rows inside `table.fp-grid` — the grid is KPI labels and numbers only. Use benchmark
cards and diff insight for narrative (`references/html-template.md`).
- **Skipping KPI alignment for candidates**: Checking description similarity is not enough.
Pull the actual metrics — two companies can describe themselves similarly but operate very
differently when you look at unit economics.
- **Treating all segments equally**: Not every segment warrants a sub-benchmark search.
Focus on segments where: (a) the parent discloses separate KPIs, and (b) the KPIs are foreign
to the whole-company benchmark set.
- **Time series plotted out of order**: If any chart line zig-zags "backward," the points are not time-sorted. Sort each series by **period end date** (or FY/FQ key) before charting, and make sure the shared x-axis label list is in ascending order too.
- **Y-axis clips time series in HTML**: Hard-coded or guessed Y bounds, or `beginAtZero` used without checking the data band, often **hides entire lines** outside the drawable range. Always derive bounds from **all** series (or use defaults + `grace`) and re-check before delivery.
- **Too much prose in chat**: The 1-pager is a reference artifact. Cut until it hurts.
- **Skipping list_kpis**: Free call that prevents wasted credits on metrics that don't exist.
- **Unrelated verticals as comps**: A gym and a car wash are not whole-company benchmarks for
each other unless you have a defensible segment-level story (same customers, same need). If you
cannot write that one sentence without hand-waving, drop the name.
- **Cross-vertical before in-vertical**: Do not foreground or prioritize outside-the-vertical
names until same-vertical peers are identified and KPI-aligned. If the first search pass drifts
cross-vertical, reset with tighter in-vertical queries before presenting the table.
Referenced files: 1 — file references are metadata, not package contents.
company-summary5.2 KB
View saved version →
---
name: company-summary
description: >
Answer "what does this company do?" and segment / geography questions using deepKPI:
get_company_summary (narrative summary) and get_company_segments (segment & operating
structure). For discovery — "which companies do X", "who operates in Y" — use
company_summary_search across summaries, then pull full summaries for names
that need detail. NOT for numeric KPI time series — use the kpi skill for that.
Triggers: what does X do, describe the business, segments, geographies,
reportable segments, companies that make, firms in industry, thematic lists.
---
# company-summary
**Purpose:** Ground answers in deepKPI's **10-K-derived company summary** and **segment
breakdown** endpoints. Use **summary search** when the user is looking for **names**
that match a **theme** or **activity**.
For **historical metrics**, provenance tables, and Excel-style pulls, use the `kpi`
skill instead.
## Data source
See `../_common/data-source-rules.md` for the runtime routing table and the canonical
inventory of deepKPI operations. This workflow uses `query_company_id`,
`get_company_summary`, `get_company_segments`, and `company_summary_search`.
`query_company_id` resolves **name / brand → `company_id`** (SEC CIK for US issuers).
Use it whenever the user names a company and you do not already have `company_id`.
## Hard stop if deepKPI access fails
See `../_common/connection-failure.md`. STOP and ask before falling back.
## Credits (budget consciously)
| Call | Cost |
|------|------|
| **`get_company_summary`** | **3 credits** on success (one company, latest 10-K narrative). |
| **`get_company_segments`** | **3 credits** on success (one company, structured segments / geos from latest 10-K). |
| **`company_summary_search`** | **1 credit per company returned** (`top_k_companies`, max **15**). Empty results: **0**. |
## Workflow A — Named company (default)
Use this when the user is asking about **one or more specific issuers** by name or
ticker.
### What does the company do? (business, model, strategy)
1. **`query_company_id`** if you need **`company_id`**.
2. **`get_company_summary`** — return the **narrative** from the response; that is the
canonical answer to "what do they do?", "describe the business", "how do they
make money at a high level?", etc.
Do **not** substitute web summaries or training-data descriptions when this endpoint
is available.
### Segments, geographies, how they break out the business
1. **`query_company_id`** if needed.
2. **`get_company_segments`** — use for **segment structure**, **operating geographies**,
**reportable segments**, and similar **organizational** questions (what lines /
regions they disclose), as returned by the API.
For **segment revenue / margins over time**, follow the `kpi` skill (`list_kpis` /
`search_kpis`) after you have the segment names you care about.
### Both narrative and structure
Call **`get_company_summary`** and **`get_company_segments`** when the question needs
**prose plus** how they **slice** the business (same `company_id`).
## Workflow B — Thematic discovery (no single target company)
Use **`company_summary_search`** when the user wants a **set of companies** defined by
**what they do** or **where / how they operate**, e.g.:
- "Companies that **make** …", "**operate in** …", "**exposed to** …"
- "Who **reports** …", "**US-listed** firms in …" (as natural language — tune the `query`)
Steps:
1. **`company_summary_search`** with a clear natural-language **`query`** and
**`top_k_companies`** (often **10**; max **15**). **1 credit per row returned.**
2. Present the ranked list (names / `company_id` / ticker as returned).
3. If the user (or the task) needs **full narrative** for one or more hits — not just
the fact they matched — call **`get_company_summary`** on those `company_id`s
(**3 credits** each). Use **`get_company_segments`** on selected names if they ask
how those companies **break out** operations.
If results are weak, **rephrase `query`** before giving up.
## Out of scope — benchmarking vs this skill
**Not covered here:** "**Companies most similar to [Target]**", formal **peer sets**
for valuation comps, or **benchmark** workflows keyed off a **specific** company as
the anchor. Use the `benchmark` skill for that. This skill's
**`company_summary_search`** is for **theme / activity** discovery ("who does X?"),
not a drop-in replacement for target-based similarity benchmarking.
## Opening line
Pick one tone that matches the ask, e.g.:
- Named company: **"I'll pull their summary and segment breakdown if needed from deepKPI."**
- Thematic list: **"I'll search deepKPI's company summaries for matches, then pull full summaries where we need detail."**
## Common failure modes
- **Answering "what do they do?" from memory** instead of **`get_company_summary`**.
- **Guessing segment names** instead of **`get_company_segments`** (or KPI search) when
the user asked how the business is **broken out**.
- **Using `company_summary_search`** when the user named **one company** and only need
summary/segments — use **Workflow A**.
- **Expecting full segment financial history** from **`get_company_segments` alone** —
use the `kpi` skill for time series.
filing6.77 KB
View saved version →
---
name: filing
description: >
Retrieves SEC filing markdown from Revelata deepKPI for US public companies: full
filing text in chat (10-K/10-Q/8-K as markdown) or verbatim excerpts — MD&A,
risk factors, footnotes, management commentary, quotes. Use when the user wants
the filing document to work with, "give me the 10-K", "what did they say", exact
wording, or sections as markdown — NOT structured KPI time series (use the kpi
skill for that). MUST call list_sec_filing_markdowns (free) then
get_sec_filing_markdown (10 credits) before any web/SEC.gov search. For excerpt
answers, lead with blockquoted contiguous text; do not paraphrase as quotes.
Triggers: "full filing", "filing markdown", "quote the filing", "MD&A", "risk
factors", "show me the 10-K", "verbatim", "management said".
---
# filing
**Purpose:** Pull **primary SEC filing text** as markdown so the user can **work with
the full document in chat** (search, copy, section pulls) or get **exact language**
via blockquoted excerpts. This is **not** the KPI / metrics skill — for revenue
series, segment tables, store counts, and other structured metrics, use the `kpi`
skill instead.
## Data source
See `../_common/data-source-rules.md` for the runtime routing table and the canonical
inventory of deepKPI operations. This workflow uses `query_company_id`,
`list_sec_filing_markdowns`, and `get_sec_filing_markdown`.
If neither MCP nor the REST fallback applies, say so and ask the user how to proceed.
## Hard stop if deepKPI access fails
See `../_common/connection-failure.md`. STOP and ask before falling back. Until
the user opts in, **do not** use the web as a workaround for text that
`get_sec_filing_markdown` would cover.
## SEC filing text — source hierarchy (non-negotiable)
When the user wants **US public-company SEC primary text** (10-K, 10-Q, 8-K,
exhibits, MD&A, Risk Factors, footnotes, "what they said", quotes, or "give me
the filing"), you MUST treat deepKPI's filing markdown as the **first and default**
source.
**Order of operations (do not skip or reorder):**
1. **`list_sec_filing_markdowns`** — **free** (0 credits). Use it to pick the right
`acc_no` / `seq_no` / form when the user didn't specify them exactly.
2. **`get_sec_filing_markdown`** — **10 credits per call**. This is the intended way
to get machine-readable filing text for quoting. **Do not avoid this tool to
save credits** by browsing the web instead. If credits may be insufficient, say
so and ask whether to proceed (or which filing to prioritize).
3. **Only if step 1–2 fail** after a genuine attempt, OR the user explicitly authorizes
non-deepKPI sources: follow **Hard stop** above, then you may use web/SEC.gov —
clearly labeled.
**Explicit prohibitions (while deepKPI is available and the user has not opted out):**
- Do **not** open SEC.gov, full-text search EDGAR, or use generic web search to
substitute for `get_sec_filing_markdown`.
- Do **not** answer from memory, training data, or news articles for "what the
filing says" — pull markdown and quote it.
**Credit transparency (say this when relevant):** Listing filings is free; retrieving
filing markdown costs **10 credits** per document pull. The correct behavior is to
**call the tool and disclose the cost**, not to route around it.
## Workflow
### 1. Resolve CIK
Call `query_company_id` with the **official registrant name** (not a brand). Same
brand→registrant hints as in the `kpi` skill (e.g. "Chipotle" → "Chipotle
Mexican Grill"). The returned `company_id` is the SEC CIK for
`list_sec_filing_markdowns` / `get_sec_filing_markdown`.
### 2. List filings (free)
Call `list_sec_filing_markdowns` with `cik`, optional `form_type`, optional date
range. Pick the `acc_no` and `seq_no` that match the user's request (e.g. latest
10-K, specific dated 8-K).
### 3. Fetch markdown (10 credits)
Call `get_sec_filing_markdown` with `cik`, `acc_no`, and `seq_no` (default `1`).
## Opening line
Before tool calls, say:
**"Let me pull the SEC filing markdown using deepKPI so you have the full filing text in context (and I can quote exact language where needed)."**
(If you are also pulling KPIs in the same turn, use the `kpi` skill's opening
line for the metrics portion.)
## When users ask "what did they say?" (verbatim quotes required)
When the user asks for **exact language** from filings ("what did management say",
"what comments were made", "how did they describe…", "quote the 10-K/10-Q"), you MUST:
- Use `list_sec_filing_markdowns` (if needed) to identify the right filing and `seq_no`.
- Call `get_sec_filing_markdown` to retrieve the markdown.
- Lead with **verbatim snippets** only: copy/paste **contiguous** text from the
markdown into **Markdown blockquotes** (`>`). Minimum useful length: **at least
one full sentence** per quote (prefer a short paragraph). Do not "tighten" wording.
- **Do not paraphrase** in the same breath as quoting. If you add interpretation,
label it explicitly as **"Interpretation (not a quote):"** and only **after** the
blockquoted passages.
- If you cannot find supporting language in the pulled markdown, say so and either
pull a different `seq_no`/filing or ask a narrowing question — do not invent
plausible filing language.
Avoid summarizing by default. Only summarize if the user explicitly asks for a
summary (and even then, prefer 1–2 blockquotes as anchors unless they asked for
summary-only).
## When users ask for "the filing" (return full markdown)
If the user asks for the **filing itself** (e.g. "give me the 10-K", "show the
filing", "send the filing", "put the markdown in chat"), you MUST:
- Use `list_sec_filing_markdowns` (if needed) for `acc_no` / `seq_no`.
- Call `get_sec_filing_markdown`.
- Return the **full markdown** (not a summary) so they can scroll, search, and ask
follow-ups against the same text. Use excerpts only if they asked for
excerpts/snippets or a specific section.
## KPIs + filing text in one request
If the user wants **both** structured metrics and **verbatim filing language** (or
full filing markdown):
1. Use the `kpi` skill for KPI discovery, search, provenance tables, and the
mandatory Excel offer when you deliver data-pull tables.
2. Use **this skill** for `list_sec_filing_markdowns` / `get_sec_filing_markdown` and
quote / full-markdown rules.
Do not satisfy a "what did they say?" request with KPI tables alone unless they
only asked for numbers.
## Common failure modes
- **Web first**: Browsing SEC.gov before calling `get_sec_filing_markdown`.
- **Paraphrase as quote**: Summarizing filing language without blockquoted source text.
- **Wrong `seq_no`**: Listing returns multiple documents per accession; confirm the
intended exhibit/main document.
- **Skipping the free list**: Calling `get_sec_filing_markdown` without knowing
`acc_no` when the user didn't provide it.
ideas21.8 KB
View saved version →
---
name: ideas
description: >
Idea-generation onramp for users who don't know what to look at yet. Turns vague
prompts like "what should I invest in?", "help me come up with new ideas",
"what interesting companies are out there?", "what stock should I buy", "any good ideas?",
"what's interesting right now?", "help me find something to research", or "I'm bored, what should I
look at" into a short rapid-fire interview, then pulls real operating data on
1-2 names. Use whenever a user shows up without a specific company or thesis.
Also trigger when users describe a vibe or trend they've noticed ("everyone's
ordering delivery now", "my kids only watch YouTube", "dollar stores are
everywhere") and want to know which public companies that maps to. Do NOT
trigger when the user already has a specific company or KPI in mind — use the
kpi skill for that instead.
---
# ideas
**General Disclosure:** The information provided is for informational purposes only and is not intended to constitute financial advice or a recommendation to buy, sell, or hold any securities.
We are not a licensed financial advisor, broker, or dealer, and this tool is not intended to provide investment, legal, accounting, or tax advice.
**Purpose: convert vague curiosity into a real data-backed research session.
Rapid-fire back-and-forth. Lots of short turns, not a few long ones.**
Target user: new to public company data, but notices things in the world.
Your job is to prove their intuitions are testable, then show the test, then
keep exploring with them.
## Data source
See `../_common/data-source-rules.md` for the runtime routing table and the canonical
inventory of deepKPI operations.
## Hard stop if deepKPI access fails
See `../_common/connection-failure.md`. STOP and ask before falling back.
## Dependencies
Before pulling KPI time series, consult the `kpi` skill (provenance,
`list_kpis` / `search_kpis`, parallel pulls). For thematic company discovery via natural-language
queries, use the `company_summary_search` tool per the `company-summary` skill.
For diagnostic KPI *concepts* by sector when choosing what to pull, use
`list_kpis` / `search_kpis` (and the `kpi` skill) rather than guessing names.
## Style rules — enforce strictly
**Keep each turn tight. Let the conversation run long.** The goal is a
high-tempo dialogue — many short exchanges — not a few monologues. Don't
front-load; let information come out over multiple turns as the user
reacts.
- **Mandatory opener (always).** The **first line of your first message** must be:
**"General Disclosure: The information provided is for informational purposes only and is not intended to constitute financial advice or a recommendation to buy, sell, or hold any securities."**
The **second line** must be:
**"We are not a licensed financial advisor, broker, or dealer, and this tool is not intended to provide investment, legal, accounting, or tax advice."**
Then continue into the interview. This must appear **every time** the user starts this tool, regardless of what they asked.
- **One thing per turn.** One question, or one compact data block, or one
observation. Not three.
- **Short messages.** Interview turns: 1-3 sentences. Data turns: a compact
table + 1-2 line read. Close-the-loop turns: 2-4 bullets. Never a wall
of text.
- **No narration.** Don't explain what you're about to do. Do it.
- **Data first, prose second.** Table / numbers up top, one-line
interpretation below. Not the reverse.
- **Cut hedges and filler.** No "great question", "let me know if...",
"happy to dig deeper", "that's a fascinating observation". Just work.
- **Plain English, not jargon.** Define terms in 3 words max the first
time: "comps (same-store sales)".
- **Every number gets a provenance link.** deepKPI returns
`value_with_provenance` — always use that form in tables and inline
mentions. The user should be able to click any number and land on the
SEC source. No exceptions.
- **Use charts liberally.** Unlike most deepKPI workflows, this skill
should produce charts whenever a trend is clearer visually than in a
table — line charts for multi-quarter trends, bar charts for
cross-company comparisons. Don't ask permission; just chart it.
Charts make the data land faster for the target user.
- **No investing advice.** Describe operating reality, not buy/sell framing. IMPORTANT.
- **Always hand the turn back.** End with a question or an offer, not a
summary. Keep them in the driver's seat.
- **Use `AskUserQuestion` by default** (or your host's equivalent multiple-choice UI). Every interview question should
be multiple-choice. After the screen, use it for "which name to dig
into?" too. Free-text is fine once the user is engaged and typing
naturally, but never force them to come up with something from scratch.
**If `AskUserQuestion` is unavailable**, render the same options as A/B/C/D (+ Other) in chat and continue from the user's reply.
---
## The flow
The shape is a **funnel**, not a pick-two-and-go:
```
observation → broad screen (10-15 names) → user narrows →
deep pull (1-2 names, 8-12 quarters) → close loop → back to the screen
(or a new observation)
```
The broad screen is what makes this skill useful. Picking two obvious
names off the top of your head is worthless — the user could do that.
What they can't do is scan 10-15 related public companies in 30 seconds
with a signature operating KPI on each. That's the value.
### 1. Interview — 5 questions via AskUserQuestion
Drive the whole interview through the `AskUserQuestion` multiple-choice
tool. The user should be clicking, not typing, for most of the funnel.
Every question should have an "Other" escape hatch (which AskUserQuestion
provides automatically), but the options you offer should be good enough
that most users don't need it.
**Design your options to be concrete and evocative, not abstract.** Bad
option: "Consumer staples". Good option: "Grocery & household brands —
stuff in your pantry". The options should read like things a normal person
would say, not sector labels from a Bloomberg terminal.
Run all 5 questions before screening. The interview IS the product
for this phase — it builds the user's own understanding of what they're
curious about. Don't rush to data.
---
#### Q1 — Starting point
> "What's your starting point?"
- "Something I've noticed changing in everyday life"
- "A company I already like or use a lot"
- "An industry I work in or know well"
- "Something I saw in the news recently"
This question sets the entire branching path. Each answer leads to a
different Q2.
---
#### Q2 — Depends on Q1
**If "something I've noticed changing":**
> "What area of life?"
- "Shopping & retail — where and how people buy stuff"
- "Food & restaurants — eating out, delivery, groceries"
- "Health, fitness & pharma — gyms, drugs, wellness"
- "Entertainment & media — streaming, gaming, social"
(Rotate in as needed: "Work & productivity — AI tools, remote, hiring",
"Money & payments — how people pay, save, borrow",
"Cars & transportation — EVs, ride-share, commuting",
"Kids & family — what the next generation does differently",
"Housing & home — renting, buying, renovating, furnishing",
"Travel & experiences — airlines, hotels, concerts, sports",
"Energy & environment — solar, oil, utilities, climate",
"Education & skills — college, bootcamps, online learning")
**If "a company I already like or use":**
> "Which company? (Type a name — doesn't have to be exact)"
Use AskUserQuestion with 3-4 popular/timely examples as options, plus
Other for free text:
- "Costco"
- "Netflix"
- "Apple"
When they name a company, don't just pull that company. The goal is to
use their admired company as a *seed* to explore its neighborhood —
competitors, suppliers, adjacent plays. Use `company_summary_search` with a
description of what the admired company does to find 10-15 related names.
Then in Q3-Q5 below, ask what about this company they admire, and what
they'd want to compare.
**If "an industry I work in":**
> "What industry?"
- "Tech / software"
- "Healthcare / pharma"
- "Finance / banking / insurance"
- "Manufacturing / industrial"
(Rotate in: "Retail / e-commerce", "Media / advertising",
"Real estate / construction", "Energy / utilities",
"Education", "Government / defense")
**If "something in the news":**
> "What kind of story?"
- "A company doing really well or really badly"
- "A big trend everyone's talking about (AI, tariffs, etc.)"
- "A sector in crisis or turnaround mode"
- "A deal, merger, or IPO"
---
#### Q3 — What's the specific shift or angle
Tailor to the path. This is where you turn a broad area into a testable
observation. Each option should map to a hypothesis.
**Examples for "Food & restaurants":**
> "What's the shift you're noticing?"
- "Fast casual (Chipotle, Sweetgreen) is taking over"
- "Delivery apps are everywhere — DoorDash, Uber Eats"
- "Traditional sit-down restaurants feel emptier"
- "Grocery prices keep going up and brands are changing"
**Examples for "a company I admire" (say they said Costco):**
> "What do you admire about Costco?"
- "The membership model — people are loyal and keep coming back"
- "They keep prices insanely low and still grow"
- "The store experience — treasure hunt, Kirkland brand"
- "They just seem to execute better than everyone else"
**Examples for "AI trend in the news":**
> "Which part of the AI story?"
- "Companies building AI tools that are actually being used"
- "Chip and hardware companies powering it"
- "Companies whose workers are getting replaced"
- "The data center and energy buildout"
Craft similar option sets for each path. Keep options vivid.
---
#### Q4 — Business story preference
> "What kind of business story appeals to you?"
- "Fast growers — companies taking market share"
- "Turnaround stories — beaten-down companies coming back"
- "Steady compounders — boring businesses that just execute"
- "Disruptors — companies changing how an industry works"
This shapes which names from the screen you'll highlight, and how
you'll frame the data. A user who likes turnarounds wants to see
the inflection; a user who likes compounders wants to see consistency.
---
#### Q5 — Company size
> "Any preference on company size?"
- "Big, well-known names (> $20B)"
- "Mid-size, less followed ($2B - $20B)"
- "Small and emerging (< $2B)"
- "No preference — show me everything"
This is a practical filter for the screen. `company_summary_search` returns a
mix; use this answer to weight which names you feature.
---
#### After the survey
Bridge to the screen in one short line:
> Found [N] public companies tied to [theme]. Here's how they're trending:
Don't recap the survey answers. Don't explain what you're about to do.
The screen is the payoff.
#### When the user types instead of clicking
If the user skips the survey and types a concrete observation ("my gym
is packed", "nobody buys new cars") or names a company, that's fine —
harvest whatever Q1-Q5 info you can infer, skip the questions you
already know, and ask only the remaining ones. The survey is a scaffold
for users who don't know what to say, not a gate.
### 2. Broad screen — the core move
**Use `company_summary_search` to discover companies from the observation,
not from your own memory.** Craft a search query that captures the
theme in natural language, ask for 10-15 results, and let deepKPI
surface the universe.
Examples of good `company_summary_search` queries:
- Observation: "gym has been packed" → *"US public company operating
fitness clubs, health clubs, or gyms"*
- Observation: "everyone uses Instacart" → *"US public company providing
grocery delivery, online grocery, or last-mile delivery services"*
- Observation: "nobody buys new cars" → *"US public company selling new
or used vehicles, automotive retail, or auto parts to consumers"*
#### Picking thesis-matched KPIs for the screen
The screen KPI should **test the user's thesis**, not just show generic
revenue. Go back to what they said in the interview (their observation,
their angle, their preferred story type) and pick KPIs that directly
speak to it.
For example, if the user's thesis is "AI is automating finance work":
- Don't just pull revenue growth (that tells you nothing about AI)
- Pull **efficiency ratio** (is the bank getting more done with less?),
**headcount** (are they hiring fewer?), **tech spend as % of revenue**
(are they investing?), **operating leverage** (is margin expanding
faster than revenue?)
If the thesis is "gyms are packed":
- Don't just pull revenue
- Pull **member count** (is it actually growing?), **same-club revenue**
(are existing locations getting busier?), **dues per member** (pricing
power?), **new unit openings** (are they expanding?)
If the thesis is "I admire Costco's membership model":
- Pull **membership renewal rate**, **fee income growth**, **member
count**, **comp traffic vs. ticket decomposition**
The point: every screen KPI should make the user go "oh, that's
exactly the question I was asking." Use `list_kpis` (free) per company
to find what's available, then pick **2-3 KPIs per company for the
screen** — not just 1. This is a richer screen than a typical stock
screener, and that richness is the whole value proposition.
#### Screen format
Present as a compact table. With 2-3 KPIs per company, use rows
grouped by company:
```
**Theme:** [one-line restatement of the observation]
| Ticker | Company | KPI | ~1yr ago | Latest | Trend |
|--------|--------------|--------------------|----------|--------|--------|
| AON | Aon | Organic rev growth | 5% | 7% | accel |
| | | Efficiency ratio | 71% | 68% | better |
| MSCI | MSCI | Sub run rate growth| 15% | 8% | decel |
| | | Retention rate | 95% | 94% | stable |
| COF | Capital One | Efficiency ratio | 42% | 45% | worse |
| | | Tech spend ($B) | $4.2 | $4.8 | +14% |
| ... | ... | ... | ... | ... | ... |
```
Every value in the table must use its provenance link (the
`value_with_provenance` form from deepKPI). If the trend is better
shown visually, follow the table with a bar or line chart.
Then a short (2-3 line) **pattern read** that connects the screen data
back to their thesis — not just "X jumps out" but *why* it matters
given what they're looking for:
> The surprise: Aon (an insurance broker) is the one showing both
> accelerating growth AND improving efficiency — that's the "real AI
> payoff" pattern you described. Capital One is investing heavily in
> tech ($4.8B) but efficiency went backward. MSCI's subscription growth
> halved — possibly losing to cheaper AI-native data products?
Then hand the turn back via `AskUserQuestion`.
**Do not pre-select names for the user.** Let the screen speak and
let them point. If they say "whichever looks most interesting", *then*
pick based on the biggest trend break — but surface the screen first.
### 3. Narrow — user picks from the screen via AskUserQuestion
After showing the screen table, use `AskUserQuestion` to let them pick.
Build options from the most interesting rows in the screen — not all of
them, just the 3-4 with the clearest stories. Add a short descriptor
so the user knows what they're clicking into.
Example:
> "Which of these do you want to dig into?"
- "ONON (On Holding) — athletic brand growing +32%, accelerating"
- "NKE (Nike) — the opposite story, N.A. revenue down -9%"
- "LTH (Life Time) — premium gym chain, revenue +20%"
- "PLNT (Planet Fitness) — mass-market gym, members +7% but decelerating"
If the user picks "Other" and types something, roll with it.
### 4. Deep pull — 8-12 quarters, multi-KPI
Only now do you go deep. Pull on the 1-2 narrowed names. Consult the
`kpi` skill for mechanics.
- 8-12 quarters minimum
- 3-5 diagnostic operating KPIs (use `list_kpis` to discover what's available,
then `search_kpis` for the best thesis-matched names) —
not just revenue
- Same KPIs across both names if comparing, so the tables line up
### 5. Present the deep pull — data forward
Template per company:
```
**[Ticker] — [Company]**
| Metric | 8Q ago | 4Q ago | Latest | Trend |
|-------------|--------|--------|--------|----------------|
| Revenue | $X.Xb | $X.Xb | $X.Xb | accel / decel |
| [Op KPI 1] | X% | X% | X% | ... |
| [Op KPI 2] | X | X | X | ... |
*[One-line read on what the numbers say.]*
```
Table first, one-line read below. No walls of prose. No repeating the
numbers in sentence form. All values must use provenance links. Follow
the table with a chart (line chart for the trend, grouped bar for
cross-company comparison) — the deep pull is where charts add the most
value.
### 6. Close the loop — bullets, not paragraphs
Three short bullets max:
- **Gut was right:** [specific number]
- **Gut was off:** [specific number]
- **Wrinkle:** [something the vibe wouldn't have told them]
Then use `AskUserQuestion` to offer the next step:
> "What next?"
- "Another name from the screen — [next most interesting ticker]"
- "Go deeper on [Ticker just analyzed] — more KPIs, longer history"
- "Start over with a different observation"
Stop there. Don't re-summarize. Don't editorialize. The funnel runs
again from whichever point they pick.
---
## Example — the funnel in action
**User:** what stock should I buy today?
**You:** What have you been noticing lately? Shopping, streaming, work,
kids — anything feeling different?
**User:** my gym has been packed every night
**You:** Same gym or gyms in general?
**User:** gyms in general I think, my friends say the same
**You:** *[runs `company_summary_search` for "US public company operating fitness
clubs, gyms, athletic apparel, or wellness services", 12 results, then
pulls one signature KPI per name]*
> **Theme:** broad fitness / wellness consumer spending
>
> | Ticker | Company | Signature | Latest | 4Q ago | Δ |
> |--------|--------------------|---------------------|--------|--------|------|
> | LTH | Life Time | Center revenue | $710m | $590m | +20% |
> | XPOF | Xponential Fitness | Studios open | 3,300 | 3,020 | +9% |
> | PLNT | Planet Fitness | Members (M) | 20.1 | 18.7 | +7% |
> | LULU | Lululemon | N.A. comp | +3% | +7% | decel|
> | ONON | On Holding | Net sales growth | +32% | +21% | accel|
> | DECK | Deckers (Hoka) | Hoka revenue growth | +28% | +34% | decel|
> | NKE | Nike | N.A. revenue growth | -9% | -2% | decel|
> | LIFW | MSP Recovery | n/a | — | — | — |
>
> Pattern: brick-and-mortar gyms accelerating, athletic apparel mixed
> (ONON ripping, NKE falling). Which angle — gyms or the apparel
> divergence?
**User:** oh interesting, the apparel thing. why is nike falling if on is up?
**You:** *[goes deep on ONON vs NKE — 8-12 quarters, multiple KPIs]*
*[…presents deep tables + close-the-loop bullets…]*
> Want a third name from the screen (Deckers/Hoka would round it out),
> more on ONON, or a different observation?
The point of this example: the user never named a company. They said
"gyms". deepKPI surfaced the universe, the screen revealed the real
story wasn't gyms at all — it was apparel brand divergence — and the
user redirected there naturally. That's the skill's job.
---
## When to break the rules
- If the user explicitly asks for more color / deeper explanation, give
it. Otherwise keep each turn tight.
- If the data genuinely surprises you, a single extra sentence to flag it
is fine. Don't turn it into a paragraph.
- If deepKPI doesn't cover a KPI you expected, say so in one line and
move on.
- Session length is unlimited — keep looping as long as the user is
curious. Brevity is per-turn, not per-session.
---
## Default against Excel
Don't offer `.xlsx` export unless the user asks or explicitly wants to
track / share. This skill is conversational. Hand off to the Revelata
Excel styling rules (`../_common/revelata-excel-styling-rules.md`) only
if prompted.
---
## Tool usage notes
**Speed matters.** The user is watching you work. Minimize round-trips:
- **Batch calls in parallel** wherever possible. When building the
screen, fire `list_kpis` for all 10 companies at once (they're free),
then fire `search_kpis` for all in a single turn. Don't do them
one at a time.
- **Use `company_summary_search` once, broadly.** Budget ~10-15 results per
screen (1 credit per result). Craft the query broadly the first time;
if results look too narrow, re-run with a broader description.
- **`list_kpis` is free** — always check coverage before paying for
`search_kpis`. If a company has no useful operating KPIs listed,
skip it.
- **One `search_kpis` call per company for the screen.** Request
`num_of_res=3` to get 2-3 thesis-matched KPIs per name. Save
longer history and additional KPIs for the narrow phase.
- When picking the signature KPI for the screen, prefer a **unit metric**
(members, subs, stores, orders, volume) over a financial one —
unit metrics are more intuitive for new users. Fall back to revenue
growth if no clean unit KPI is available.
- If `company_summary_search` returns something irrelevant, drop it from the
table silently. Don't explain.
## Reference
Use `list_kpis` / `search_kpis` (via the `kpi` skill) when picking signature KPIs
for the broad screen and the fuller set for the deep pull — prefer unit /
operating metrics over financials when both exist.
## When not to use
- User already named a company → use the `kpi` skill
- User uploaded a sell-side PDF → use the `pressure-test` skill
- User wants a forecast or model → use the `seasonality` or `implied-metric` skills
This skill is only for the "I don't know where to start" moment.
implied-metric9.58 KB
View saved version →
---
name: implied-metric
description: >
Computes a financial or operational metric that isn't directly reported, using
the algebraic relationship between available quantities. Use whenever a needed
data point is absent from deepKPI but can be computed — Q4 values, segment
remainders, per-unit economics (ASP, ARPU, AUV, revenue per store), take rates,
implied useful life, penetration rates, geographic mix percentages. Trigger for
"back into", "derive", "implied", "impute", "calculate from", "what's the Q4",
or any situation where the needed number isn't in the data but its ingredients
are. Always pull inputs via the kpi skill; derived values inherit provenance
from their deepKPI-sourced components. For `.xlsx`/CSV exports, follow
../_common/revelata-excel-styling-rules.md (plus the xlsx skill).
---
# implied-metric
Computes metrics that aren't directly reported by identifying the algebraic
relationship between available quantities.
**All inputs must come from deepKPI** (via the `kpi` skill). Use `list_kpis`
first to discover exact KPI names, then `search_kpis` to pull the values. Never
use hardcoded or assumed figures as inputs to a derivation.
## Data source
See `../_common/data-source-rules.md`. Inputs to derivations are pulled with
`query_company_id` / `list_kpis` / `search_kpis` — same workflow as the `kpi` skill.
## Hard stop if deepKPI access fails
See `../_common/connection-failure.md`. STOP and ask before falling back.
## Critical: flow vs. stock
Before any derivation, determine whether the metric is a **flow** (accumulated
over a period) or a **stock** (level at a point in time).
**Flows** — income statement, cash flow, unit sales, deliveries, openings:
`Q4 = FY − (Q1 + Q2 + Q3)`
**Stocks** — balance sheet items, period-end subscriber/store/user counts:
`Q4 = fiscal year-end value directly` — do not subtract.
When in doubt: "Is this a running total for the period, or a level at the end?"
## Derivation patterns
### Q4 from annual + Q1–Q3 (flows only)
```
Q4 = FY − (Q1 + Q2 + Q3)
```
**Always attempt this derivation when quarterly granularity is needed.** If Q4
is not directly in deepKPI, check whether FY is available and compute it — do
not present a partial quarterly series without first trying to fill Q4.
**Where the imputed value goes:** Put Q4 **in the same row** as the quarterly
series, in the **Q4 period column** (in-chat wide tables) or the matching period
column in Excel—**not** a separate line labeled "Q4 (derived)" or "Imputed Q4".
The data stream should read as one continuous series; use **Notes** (or a footnote)
to flag imputation. Same rule for other **gap-fills** in a single stream.
Apply to: revenue, COGS, gross profit, opex, net income, unit sales, deliveries,
store openings, SBC, D&A, capex, cash from operations.
Do NOT apply to: balance sheet items, period-end subscriber/store/user counts.
### Segment remainder
```
missing_segment = total − sum(known_segments)
```
Examples: domestic = total − international; Maggiano's = consolidated − Chili's − Corporate.
### Per-unit metrics
| Derived metric | Formula |
|---|---|
| Average Selling Price (ASP) | Revenue ÷ Units sold |
| ARPU | Revenue ÷ Paying users |
| Average unit volume (AUV) | Revenue ÷ Restaurant count |
| Revenue per store | Revenue ÷ Store count |
| Cost per store | COGS ÷ Store count |
| CAF income per vehicle financed | CAF income ÷ Vehicles financed |
| CapEx per new/remodeled store | Capex ÷ Affected stores |
| Revenue per square foot | Revenue ÷ Total sq ft |
| Average net sales per case | Revenue ÷ Cases sold |
### Implied useful life (depreciation)
```
implied_useful_life = net_PP&E / depreciation_expense
next_year_depreciation = current_net_PP&E / implied_useful_life
```
Use the average of the last three reported years as the forward assumption, held
constant (fewer than three available: the average of what exists).
### Take rate
```
take_rate = net_revenue / gross_bookings_or_GMV
```
### Geographic / segment mix
```
segment_pct = segment_value / total_value
projected_segment = projected_total × segment_pct
```
### Penetration rate
```
penetration_rate = units_captured / total_addressable_units
```
## Verify the derivation
- Does the derived Q4 make the four quarters sum exactly to the annual?
- Does the segment remainder produce a non-negative number? (Negative → period mismatch, unit mismatch, or wrong metric definition)
- Does the per-unit figure fall in a plausible range for this business?
- Does implied useful life match the depreciation policy in the 10-K notes?
## Output format — show all work with clickable inputs
Table structure rules (in-chat):
- **Time flows left to right** — column headers are period labels
(`FY22-Q1` … `FY22-Q4`), earliest at left.
- **One row per time series** — each **reported** deepKPI series is one row across
periods. Do not fragment into "Revenue - Q1" rows. **Imputed** points (e.g. Q4)
sit **in that same row** in the missing column—**not** an extra "Q4 (derived)" row.
- **Operands for imputation** — FY and Q1–Q3 may appear on **additional** rows when
they are **separate** deepKPI pulls (e.g. annual revenue row + quarterly row); the
**imputed value still fills the gap inside the quarterly row**, not a third row
for "Q4".
- **Truly distinct derived KPIs** (a metric that is *not* the same stream with a
hole—e.g. a computed ratio or remainder **as its own line item**) may use their
own row; show operands and formula as today.
- **Links embed in cell values** — every directly reported input is a clickable
hyperlink: `[$41.8M](exact-deepkpi-url)`. No separate Source column.
- **Notes is a bottom row** — populated in columns where something non-standard
occurred (derivation applied, stock item, unit conversion, MD&A gap-fill, etc.).
Show all operands (with links) needed for imputation, and the filled-in cell in the
**same stream** as the missing point. For a distinct derived KPI row, show operands
and the result on that KPI's row. Never hide how a number was obtained.
**Single derivation block:**
```
**[Metric] ([Ticker], [Period])** — *derived*
Formula: FY − (Q1 + Q2 + Q3)
= [FY: $X](<exact-deepkpi-url>) − ([Q1: $A](<url>) + [Q2: $B](<url>) + [Q3: $C](<url>))
= **$D**
Caveat: Q4 not directly reported in SEC filings; derived from 10-K annual figure.
```
**Multi-period table (preferred when deriving multiple periods):**
```
## [Metric] ([Ticker]) — Q4 imputed in-stream
Units: $M
| FY22-Q1 | FY22-Q2 | FY22-Q3 | FY22-Q4 | FY23-Q1 | FY23-Q2 | FY23-Q3 | FY23-Q4 |
-------------------|---------------|---------------|---------------|----------------|---------------|---------------|---------------|----------------|
Quarterly Revenue | [$41.8](url) | [$55.3](url) | [$65.1](url) | **$47.9** (FY−Q1−Q2−Q3; link FY + Q1–Q3 operands) | [$43.9](url) | [$59.8](url) | [$69.7](url) | **$53.2** (same) |
Annual Revenue | | | |[$210.1](url) | | | |[$226.6](url) |
Notes | | | | imputed Q4 | | | | imputed Q4 |
```
Use the Notes row to flag anything non-standard: stock item used directly as Q4,
unit conversion applied, anomaly year excluded, one input estimated from MD&A
rather than deepKPI, etc.
## Excel workbook (`.xlsx`)
Follow `../_common/revelata-excel-styling-rules.md` for layout, styling, provenance
**hyperlinks on the value cells** (not separate Source rows), date headers, freeze
panes, column grouping, and checklist. Use the **xlsx** skill to build the file.
**implied-metric addenda:**
- **Q4 / gap imputation:** formula **`=FY_cell - Q1_cell - Q2_cell - Q3_cell`** in
the **quarterly series row**, **Q4 column**—not a separate "Q4 Revenue" row.
- Structure ALL CAPS sections as needed. **Distinct** derived KPIs (not gap-fill)
get their own rows with formulas.
- **Every derived cell** is a live `=` formula — no hardcoded computed numbers:
- **Q4 in-stream:** `=FY_cell - Q1_cell - Q2_cell - Q3_cell` in the **same row**
as Q1–Q3
- **Segment remainder:** `=Total_cell - Seg1_cell - Seg2_cell`
- **Per-unit:** `=Revenue_cell / Units_cell`
- **Implied useful life:** `=NetPPE_cell / Depreciation_cell`
- **Take rate:** `=NetRevenue_cell / GrossBookings_cell`
- **Segment mix %:** `=Segment_cell / Total_cell`
- DeepKPI-sourced cells are **values** (with links per the Revelata Excel styling
rules), not formulas; derived cells are **black** formulas, not hardcoded.
**CSV:** Per `../_common/revelata-excel-styling-rules.md` — `provenance_url` /
`formula` columns as needed; recommend `.xlsx`.
## Common pitfalls
- **Separate row for imputed Q4**: imputation belongs in the **quarterly series row**,
not a second line item for the same stream (especially in Excel).
- **Flow/stock confusion**: subscriber counts, store counts, balance sheet items
are stocks — year-end value is Q4, not derived by subtraction.
- **Unit mismatch**: deepKPI may return some values in thousands, others in
millions. Check units before dividing.
- **Non-December fiscal year**: ensure FY and Q1–Q3 are all from the same fiscal
year, not the calendar year (e.g., AAPL ends September, NVDA ends January).
- **Negative remainder**: if FY − Q1 − Q2 − Q3 < 0, check that annual and
quarterly values use the same metric definition and filing series.
- **Hardcoded inputs**: never use a revenue or count figure that wasn't just
pulled from deepKPI in this session — always show the search that sourced it.
kpi18 KB
View saved version →
---
name: kpi
description: >
Retrieves financial and operational KPIs for a US public company from Revelata's
deepKPI database. Use for historical metrics from SEC filings — revenue by segment,
unit-level KPIs (paying users, store counts, same-store sales, ASP, ARPU,
deliveries), income statement, balance sheet, or cash flow data. Triggers include
"pull data for", "get historicals for", "find the KPI", "what does deepKPI have
on", naked KPI names (user has search intent), or modeling tasks that need
structured time series. For verbatim filing text, MD&A quotes, full filing markdown,
or "what did they say", use the filing skill. For what a company does,
segment/geography breakdowns, or thematic "who does X?" lists, use the
company-summary skill instead.
Always use before analyzing or projecting on KPIs. After in-chat KPI tables, end
with a bold Excel (.xlsx) offer unless the user asked for a file or declined. For
.xlsx output, follow ../_common/revelata-excel-styling-rules.md.
---
# kpi
**The sole data source for all historical financial and operational metrics is
deepKPI.** Do not use LLM's training knowledge, analyst reports, or any other
source for specific financial figures. Every number in the output must trace back
to a deepKPI call.
**SEC filing language (quotes, MD&A, full filing markdown):** use the `filing`
skill — not this one.
**Business description, segment/geo structure, thematic company discovery:** use
the `company-summary` skill (`get_company_summary`, `get_company_segments`,
`company_summary_search`) — not this one.
## Data source
See `../_common/data-source-rules.md` for the runtime routing table (MCP-capable
vs non-MCP) and the canonical inventory of deepKPI operations. The KPI workflow
uses `query_company_id`, `list_kpis`, and `search_kpis`.
For known structured-KPI gaps (quarter vs. half-year period
mismatches on 4-4-5 retailers, monthly sales 8-Ks, late-filed
material 8-Ks, one-time MD&A disclosures), read
`../_common/deepkpi-coverage-gaps.md` BEFORE concluding that a needed
metric is unavailable.
If neither MCP nor the REST fallback applies, say so and ask the user how to
proceed.
## Hard stop if deepKPI access fails
See `../_common/connection-failure.md`. STOP and ask before falling back.
## Opening line
Before doing anything else, say:
**"Let me pull the KPI data using deepKPI."**
## Granularity principle — always go deep first
deepKPI's value is in the detail. Users asking broad questions ("how is the
company doing?", "analyze their revenue") are used to top-line summaries from
Bloomberg or news articles. Surprise them by going granular first.
**Default search order:**
1. **Unit-level KPIs first** — store counts, same-store/comparable sales, ASP,
ARPU, deliveries, paying users, occupancy, RevPAR, etc.
2. **Segment or geography breakdowns** — revenue by business unit, brand,
region, or channel before consolidated totals
3. **Margin and cost structure detail** — food costs as % of revenue, labor,
four-wall EBITDA, restaurant-level margins before blended operating margin
4. **Consolidated / top-line metrics last** — only after the above; use as
context or cross-check, not as the primary answer
If granular data exists for a metric, never lead with the consolidated version.
Only fall back to top-line when the detailed series is genuinely absent from
deepKPI. Note when you've done this.
This applies to both data pulls and analysis. When writing analysis, build
the narrative from the unit economics up — don't start with "revenue grew X%"
when you have store count, traffic, and ticket data to explain why.
## Recency principle — never settle for avoidably stale data
If deepKPI has quarterly coverage (10-Q-derived periods) that is more recent than the
last annual 10-K figure for the same KPI, **use the quarterly data** when the user's
question is about "current", "latest", "recent", or near-term dynamics.
- Do **not** write (or imply) "our data is a little old" if a more recent 10-Q period
exists for the KPI in question.
- Use annual (10-K) series for **long-cycle** context, not as an excuse to stop early
on recency.
- If quarterly data is genuinely unavailable for a KPI, say so plainly and use the
freshest available period; do not shrug or hand-wave.
## Retrieval workflow
### 1. Resolve company ID
Call `query_company_id` with the official registrant name (not a brand or
subsidiary). Map common brands first:
- "Chipotle" → "Chipotle Mexican Grill" · "Snapchat" → "Snap Inc."
- "Olive Garden" → "Darden Restaurants" · "CarMax Auto Finance" → "CarMax Inc."
The returned `company_id` is the SEC CIK. Required for all subsequent calls.
### 2. Discover available KPIs — always do this first, it's free
Call `list_kpis` before any `search_kpis` call. It returns every KPI name
organized by category: Company KPIs, Segment Financials, Income Statement,
Balance Sheet, Cash Flow, Capital Structure.
When you would like to retrieve a specific KPI, use the exact name strings from
this list as `search_kpis` queries with number of results equal to 1 — this
avoids wasted credits and ambiguous matches.
Never skip this step.
### 3. Search for specific KPIs
Call `search_kpis`. Each result costs 1 credit — start with `num_of_res: 3`,
increase only if needed.
Effective query patterns:
- Be specific: `"average revenue per paying user"` not `"ARPU"`
- Be specific about segment: `"Chipotle domestic restaurant comparable sales increase"`
- Include cost language: `"food beverage and packaging costs as percentage"`
- Geographic splits: `"Net Sales by Geography Americas Europe Greater China"`
A typical request needs 2–4 separate searches.
### Search exhaustion — required before declaring a KPI absent
Before concluding "this KPI is not in deepKPI" and falling back to a
non-deepKPI source, run the four-variant search exhaustion check in
`../_common/deepkpi-coverage-gaps.md` (the metric as phrased, a synonym, the
parent metric, the segment- or geography-level variant), testing any close
`list_kpis` candidate with a `search_kpis` call before concluding. A single
pass that finds nothing is not a confirmed gap: deepKPI often structures a
metric at a different period or level than the one asked for (a half-year
figure where a quarter was requested, a segment line where a total was), and
one more query usually finds it.
When the gap is real, document what was searched in the audit table:
`tried [variant1, variant2, variant3, variant4], not in deepKPI,
fallback to SEC URL`. A bare "not in deepKPI" is a defect — without
the search log, a reviewer cannot tell whether the fallback is justified.
### 4. Handle gaps
**Whenever quarterly granularity is requested, always attempt to derive Q4 for
flow metrics before presenting incomplete data.** Do not surface a partial series
(Q1–Q3 only) without first checking whether FY is available and computing
`Q4 = FY − (Q1+Q2+Q3)`. Only leave Q4 blank if the annual figure is also absent.
| Gap | Handling |
|---|---|
| Q4 missing, have Q1–Q3 + FY | Derive: Q4 = FY − (Q1+Q2+Q3). Consult the `implied-metric` skill. |
| Annual missing, have quarters | Sum four quarters (flow items only). |
| Segment missing, have total + others | Derive: missing = total − sum(known). Consult the `implied-metric` skill. |
| Balance sheet Q4 missing | Use FY year-end value — stocks are snapshots, not flows. |
| Metric absent entirely | Note the gap; for narrative context from filings, use the `filing` skill (`list_sec_filing_markdowns` + `get_sec_filing_markdown`). |
Flag clearly when a value is derived or manually sourced from filings.
## Provenance — non-negotiable rules
deepKPI returns a provenance URL for every data point linking to the exact SEC
filing passage it was extracted from. These URLs are the audit trail.
- **Every number in the output must have a clickable hyperlink** to its deepKPI
provenance URL. No exceptions.
- Copy URLs **exactly as returned** from the API. Never reconstruct, shorten, or
guess a URL. If uncertain, omit and cite filing type + period instead.
- Format: embed the link directly in the cell value — `[$248.0M](exact-url)` or
`[2,410 stores](exact-url)`. The value and its source are one clickable unit.
- Group multiple values from the same filing by linking each to the same URL.
- Never present a number without at least its provenance URL.
- If a value is derived, provide the provenance of its operands.
## After the pulls are complete
Once all deepKPI calls have returned, follow the branch that matches what the user
asked for. If they asked for more than one (e.g. analysis **and** a CSV), satisfy
every constraint that applies.
**Hard requirement — in-chat data pull:** If your reply includes **data-pull
tables** (KPI numbers for the user) and the user **did not** ask for Excel/CSV in
that same message, you **must not** end your response until you have asked the
**bold** Excel question in **Mandatory: Excel offer after in-chat data pull**
below. Treat this as part of the deliverable, not optional follow-up.
### Analysis
When the user wants **analysis**, **interpretation**, **trends**, **commentary**,
or **what it means** — structure the response from the ground up: unit economics
and segment detail first, consolidated metrics as context. A response that leads
with "revenue grew 12%" when you have same-store sales, ticket size, and traffic
data is a missed opportunity — pull the thread from the most granular level
available and build toward the headline number, not the reverse.
**Still enforce Provenance:** every **numeric figure** must include its deepKPI
provenance using `[value](exact-url)` (and for derived numbers, link operands or
state sources as already required). No bare numbers.
### Data pull (in-chat: read top to bottom)
When the user wants **data**, **historicals**, **pull KPIs**, or a **modeling
feed** but has **not** (yet) asked for CSV or an Excel file:
- **Tall layout in chat** — optimize for **reading in the thread**. Use tables where
**each row is one period** (time reads **down**). **Do not** lay out **dates as
many columns** in chat — that wide date-axis layout is for **.xlsx** only (see
`../_common/revelata-excel-styling-rules.md`).
- **Same periods → one table, metrics as columns:** When multiple metrics **share the
same period set** (same granularity — e.g. all quarterly with identical quarter
labels, or all annual with identical FY labels), **combine them into a single
table**: first column **Period**, then **one column per metric** (clear header per
column, each cell `[value](url)`). Do **not** split into separate one-column tables
when the periods line up. Use **separate tables** only when period axes **differ**
(annual vs quarterly, misaligned dates, or incompatible units need their own block).
- **Order:** Show **all annual figures first**, in **chronological order** (oldest
at the **top**), then a **clear break** (e.g. horizontal rule, blank line, or a
**Quarterly** subheading), then **all quarterlies** (oldest first). Do not
interleave annual and quarterly rows without a visual separation.
- **Consolidate or split by KPI identity:** If deepKPI returns multiple series that
are the **same KPI** (same metric, **same unit**, **same aggregation period**),
**merge into one vertical series** (one row per date, value in the value column).
If they **differ** (e.g. quarterly vs annual, or different units), use **separate
blocks** or **separate labeled tables** — **one block per distinct deepKPI time
series** when they are not the same KPI.
- **Derived / imputed metrics:** When you **fill a missing point** in an existing
series (e.g. Q4 from FY − Q1−Q2−Q3), put the value **in that series' row** in the
correct period column—**not** a separate "Q4 (derived)" line item. **Bold** or
annotate in-cell / **Notes** where helpful. **Distinct** derived KPIs (different
line item, e.g. YoY %) may still use their **own** row or column.
- **Provenance:** `[value](exact-url)` per **Provenance — non-negotiable rules**.
- **Prose:** optional short intro; do not bury the data in long narrative unless the
user asked for analysis.
For layout examples, see **In-chat table template (data pull)** below.
### Mandatory: Excel offer after in-chat data pull
**You must do this** whenever you showed KPI **data-pull** tables and the user did
**not** already request a file (CSV / spreadsheet / Excel / `.xlsx`) in the same turn.
1. Deliver the in-chat tables first.
2. Then, in the **same assistant response**, add a **final** paragraph whose **main
question is entirely wrapped in Markdown bold** (`**...**`), asking whether the
user wants an **Excel workbook (.xlsx)** built for modeling (**periods as
columns:** annual block, blank separator, quarterly block — per the Revelata
Excel styling rules in `../_common/revelata-excel-styling-rules.md`). Example
(paraphrase allowed; **bold** is not optional):
**Would you like me to build an Excel workbook (.xlsx) laid out for modeling — periods across columns (annuals, then a blank column, then quarterlies) per our export spec?**
For a **full operating model** (revenue build, costs, consolidated financials,
depreciation, NWC in one workbook), use the `operating-model` skill instead of
a raw KPI export.
3. Do **not** skip this step because the answer feels complete without it. Do **not**
bury it mid-message; put it **after** the tables so it is visible.
4. If they **decline**, stop; if they **accept**, build the file per
`../_common/revelata-excel-styling-rules.md`.
5. **Skip** this block only if: they already asked for a file export in that turn,
or you did **analysis-only** with **no** data tables, or they explicitly said they
do not want Excel.
### CSV or spreadsheet output
When the user asks for **CSV**, **spreadsheet**, **Excel**, **.xlsx**, agrees to
the **post-pull .xlsx offer**, or downloadable tabular output for a model — read
`../_common/revelata-excel-styling-rules.md` and follow it in full (plus the
**xlsx** skill for implementation).
---
## In-chat table template (data pull)
Structure (read **down** the page; **time is rows**, not columns):
- **Annual block first** — one table: **Period |** then one column per metric if
several annual series share FY labels; otherwise `Period | Value`. Oldest period
at the **top**.
- **Break** — then **Quarterly** table(s): if quarterlies **align** on the same
periods, use **one table** with **Period | Metric A | Metric B | …**. Otherwise
split by mismatched axes.
- **Merge vs split (KPI identity)** — unchanged: merge values that are the same KPI;
separate rows when metric/unit/aggregation differs.
- **Derivations / imputation** — gap-fills (imputed Q4, etc.) live **inside** the
same row as the parent series for that period; **do not** add an extra line item
for the imputed point. Other derived KPIs get their own row/column when they are
genuinely separate metrics. **Note** imputation in Notes or inline where needed.
**.xlsx is different:** file export uses **periods as columns** (annual block |
blank | quarterlies) — see `../_common/revelata-excel-styling-rules.md`.
```
## [Metric group] ([Ticker])
Units: $M unless noted (store count = stores)
### Annual
| Period | Revenue (annual) | … |
|--------|------------------|---|
| FY2022 | [$1,196.8](url) | … |
| FY2023 | [$1,383.6](url) | … |
### Quarterly — aligned metrics (one table)
| Period | Revenue | Store count |
|---------|--------------------|-------------|
| FY22-Q1 | [$248.0](url) | [2,410](url) |
| FY22-Q2 | [$300.9](url) | [2,445](url) |
| FY22-Q3 | [$292.2](url) | [2,489](url) |
| FY22-Q4 | **[$355.7](url)** (imputed in this row: FY−Q1−Q2−Q3; link operands) | [2,521](url) |
| FY23-Q1 | [$267.1](url) | [2,563](url) |
```
## Excel workbook (`.xlsx`)
For any **CSV / spreadsheet / Excel** deliverable from deepKPI, read
`../_common/revelata-excel-styling-rules.md` and apply it end-to-end (layout,
styling, hyperlinks, dates, freeze panes, column grouping, checklist, CSV
notes). Use the **xlsx** skill to implement it. Do not duplicate those rules
here.
## Common failure modes
- **Dates as many columns in chat** on a plain data pull: keep **periods as rows**;
you may use **one column per metric** when periods align. Reserve **dates as
columns** (wide date axis) for **.xlsx** only.
- **Splitting aligned metrics** into duplicate single-metric tables: if periods
match, **combine into one table** with a column per metric.
- **Skipping list_kpis**: it's free and consistently guides better, cheaper searches.
- **Querying too broadly**: "revenue" returns weak matches; use segment-specific terms.
- **Leading with top-line when granular data exists**: always check for unit-level and segment KPIs first; only use consolidated totals when detail is unavailable. Showing same-store sales + traffic + ticket is more valuable than consolidated revenue alone.
- **Apologizing for stale 10-K data when a newer 10-Q exists**: if the KPI has a more
recent quarterly period available, use it. Reserve annual-only caveats for cases
where quarterlies truly aren't present in deepKPI.
- **Mixing period types silently**: always label each value as annual or quarterly.
- **Balance sheet items as flows**: receivables at Dec 31 IS Q4 — don't subtract.
- **Hallucinating a URL**: if you don't have the exact URL, say so; never construct one.
- **Plain-text URLs in tables**: always format as `[label](url)` — clickability is required.
- **Extra Source rows in .xlsx**: filing identity belongs in the **cell hyperlink**
only; do not add redundant source listing rows.
- **Omitting the post-pull Excel question** after showing KPI tables: the **bold**
offer is **mandatory** unless the user already asked for a file or refused Excel.
- **Verbatim filing text / "what did they say"**: wrong skill — use the `filing`
skill instead of improvising from KPI pulls.
- **"What do they do?" / segment breakdowns / "who operates in X?"**: wrong skill —
use the `company-summary` skill instead of `search_kpis` alone. (Target-based
"most similar" benchmarking: use the `benchmark` skill, not `company_summary_search`
as a substitute.)
operating-model53.4 KB
View saved version →
---
name: operating-model
description: >
Builds a full Excel operating model for a US public company from Revelata
deepKPI data. Resolves a ticker or company name, classifies it as restaurant,
retail, or software, and stops without creating a workbook for unsupported
industries. Creates a single-sheet model with Revenue, Costs, Consolidated
Financials, Depreciation Schedule, and Net Working Capital sections; discovers
the KPI inventory once; and orchestrates the section sub-skills in dependency
order. Triggers include "build an operating model", "build a model for",
"model out this company", "set up the model", and "scaffold a model". For a
single section, KPI pull, or filing text, use the narrower skills.
---
# operating-model
This skill is the **orchestrator** for building an operating model. Its own job
is narrow: **resolve the company**, **have its industry classified once** (and
stop if it is not one the skill models), **lay down a styled, empty workbook
skeleton** with the five canonical section headers, and **discover the company's
full KPI inventory once** so it can be shared. It does **not** populate any
section itself — each section is built by a dedicated sub-skill that this skill
invokes in turn, drawing on the shared KPI inventory.
**The sole data source for all figures is deepKPI.** Do not use training
knowledge, analyst reports, or any other source for financial figures. Every
number that eventually lands in the workbook must trace back to a deepKPI call
(enforced by the sub-skills).
## GOLDEN RULES (non-negotiable — read before every build)
These rules override convenience, speed, and your own judgment. Violating any of
them is a failed build, even if the output looks correct.
1. **Follow the workflow exactly, in order. Never look ahead.** Execute this
skill's steps (and each sub-skill's steps) strictly in sequence. Do **not**
open, read, or reason about a downstream sub-skill before the workflow has
actually routed you there. You may only read a sub-skill's file at the moment
the workflow hands control to it. Peeking ahead to "see what's coming" or to
pre-plan biases your decisions and is prohibited.
2. **Never skip `list_kpis`, and always wait for it to fully resolve.** The full
KPI inventory discovery (Step 3) is mandatory and must complete before **any**
routing or sub-skill work begins. It may be large and slow — that is expected.
Wait for it. If the result is too large to read inline, page through it / parse
it from the saved file, but **do not** substitute a handful of `search_kpis`
guesses for the full inventory. Every routing decision depends on the complete
inventory being in hand, and the sub-skills rely on it to know exactly what to
call.
3. **Route only on the available KPIs — never on your own knowledge.** Routing
decisions (e.g. which revenue sub-build to use) must be made **solely** from
what the KPI inventory contains, exactly as each router's criteria specify. Do
**not** factor in anything else — not the company's real-world characteristics,
and not which sub-skill is more complete. Facts about the **nature of the
business** are not the routers' to decide: the company's **industry** and, for
software, its **revenue model** (platform / subscription / advertising / mixed)
are determined exactly once, by the `industry-classification` sub-skill in
Step 1.2, from the 10-K business description, and handed to the routers as
inputs. A router may use those determinations and the inventory, and nothing
else. A company outside the three supported industries (restaurant, retail,
software) stops at Step 1.2. Nothing is ever routed to a placeholder.
4. **Do not go off on tangents. Build only what the active skill specifies —
nothing more.** If a sub-skill says it is authored section by section and that
only the sections present are implemented, build **exactly** those sections and
**stop**. Do **not** invent, add, "improve," or extrapolate extra sections,
line items, or analysis beyond what the current skill explicitly defines, even
if you think it would be helpful. When a skill is incomplete, stop and say so —
do not fill the gap with your own construction.
If a rule here conflicts with something you want to do, the rule wins. When in
doubt, stop and ask the user rather than improvising.
## Model timeline — historical and projection years
This section is the **single source of truth** for the model's fiscal-year
columns. **Every sub-skill inherits this timeline; none defines its own.**
Let **`N`** = the **last historical fiscal year** — the most recent fiscal year
for which deepKPI has complete reported actuals.
- **Historical years (actuals).** The **four most recent fiscal years** with
deepKPI actuals: **`N−3`, `N−2`, `N−1`, `N`**. This is the established window the
revenue build lays down and every downstream section mirrors; older years are
dropped.
- **Projection years (forecast).** The **five fiscal years immediately after the
last historical year**: **`N+1`, `N+2`, `N+3`, `N+4`, `N+5`**. The projection
window is therefore **always five years long**, beginning the year after the last
historical year.
- *Example:* if the last historical year is **FY2025**, the projection years are
**FY2026, FY2027, FY2028, FY2029, FY2030**.
- **Full model horizon** = the four historical years followed by the five
projection years, oldest → newest, left → right: **`N−3 … N`** (historical) then
**`N+1 … N+5`** (projection) — **nine fiscal-year columns** in total.
Notes on this definition:
- **"Fiscal year" means the company's fiscal year-end**, not the calendar year —
many issuers are non-December. `N` is the latest fiscal year fully reported in
deepKPI.
- The historical/projection boundary is also where cell behavior changes:
historical columns carry reported deepKPI inputs and live historical formulas;
projection columns carry the forward drivers/assumptions.
- **Scope of this section:** it fixes *which* fiscal years are historical and
which are projection, and nothing else. How each section lays its projection
columns — its drivers, their seeds and the formulas that invert them — is
defined in that section's own file (every build carries a Projections section);
build the projection columns exactly as the active sub-skill instructs, never
from this definition alone.
## Units, scale, and number format
`../_common/revelata-excel-units-and-scale.md` is the **single source of truth** for how
every number in this model is stored, scaled, and labeled. **Read it before
writing any cell, and follow it in every section.** Like the **Model timeline**
above, it is a sheet-wide contract that the sub-skills inherit.
A sub-skill may override a **display scale** or a **tag** for a named row or family
where its own file says so explicitly; nothing may override base-unit storage or
reintroduce a scale constant. Money **precision** is not a stage's choice either: the
renderer derives one format per money family from the stored historical inputs (one
decimal when the issuer reports below the display unit, whole units otherwise, the
contract's Rule 3) and the audit verifies it. The contract's **Rule 6** defines the audit that
**Step 5** runs before delivery.
## Contract rows — the labels sections may reference across section boundaries
The CONSOLIDATED FINANCIALS and DEPRECIATION SCHEDULE builders are identical for every
industry and carry no industry knowledge; they reach into the REVENUE and COSTS sections
**only through the rows below, by these exact labels**. The NET WORKING CAPITAL
builder's mechanics are industry-free too, and its industry knowledge is confined to
one **driver map** per industry (`net-working-capital/<industry>/`) that names which
upstream rows its drivers divide by — the contract rows below plus, under the
issuer's own captions, the cost-of-sales and labor lines the map describes. Every revenue
and cost build must lay the rows that apply to it under these labels, whatever the
issuer's own caption is (the issuer's wording goes in the Col C comment). A downstream
builder never resolves a label by industry, by example, or by fallback between two
spellings. Each contract row also has a **fixed key**, the `{key}` a formula writes to
reference it (Build mechanics), so a cross-section reference is exact whatever anyone's
spelling of the label; and an **existence condition**, evaluable from the shared
inventory or a hand-back report, which is how a downstream stage tells a row that is
missing from one that never exists for this company (Step 4.2).
| Contract row | Key | Laid by | Exists when | Meaning | Status |
|---|---|---|---|---|---|
| `Total Revenue` | `rev_total` | every revenue build (the grand-total row of its consolidated revenue table) | always | reported total revenue in history; the assembled total in projection | in force 2026-10-12 |
| `Revenue from <Stream>` | `rev_<stream>` (slug of the issuer's caption), one per stream | the software revenue builds and the retail units and square-footage builds, one row per named revenue stream each lays; a build whose single metric covers all revenue lays none — its one row is `Total Revenue` (the ARPU build's covers-all case) — (the stream's own block value row and the row that restates it in the consolidated revenue table carry this same label; `<Stream>` is the issuer's caption with the issuer's own revenue noun removed and every other word kept, including a qualifier such as ", net": `Subscription` → `Revenue from Subscription`; `Credit card revenues, net` → `Revenue from Credit Card, net`; `Macy's Media Network revenue, net` → `Revenue from Macy's Media Network, net`; `Net revenue from licensing` → `Revenue from Licensing, net`. The noun removed is one of revenue, revenues, sales, net sales; nothing else is shortened, reordered or renamed, and the issuer's full caption goes in the Col C comment. A caption that is only a revenue noun (`Revenues`, `Net sales`) is a total, never a stream; the residual plug is always `Other Revenue` and never carries a stream name) | for each named stream the revenue build lays; never for the residual, which is `Other Revenue` (`rev_other`) | that stream's revenue — the denominator a cost-of-revenue line divides by; the software cost build matches a cost line to its stream by this label and falls back to `Total Revenue` when no row matches | in force 2026-10-12 |
| `Net Sales` | `net_sales` | every retail revenue build, only when the issuer's income statement shows a merchandise net sales line **and** at least one separate revenue line (membership fees, other revenue); omitted for a single-revenue-line issuer | the shared inventory's `Consolidated Income Statement` shows a merchandise net sales line **and** at least one separate revenue line | the reported merchandise net sales line — the base the retail cost build strikes cost of sales against; it falls back to `Total Revenue` when the row is absent (a row-presence test). Retail-internal, like `Revenue from <Stream>` for software | in force 2026-10-13 |
| `Total Costs and Expenses` | `tce` | every cost build | always | the all-in total of every additive cost line between revenue and `Operating Income (modeled)`, including cost of sales / cost of revenue; `Operating Income (modeled) = Total Revenue − Total Costs and Expenses` | in force 2026-10-12 |
| `Total Costs and Expenses — <Segment>` | `tce_<segment>` | cost builds on a segment path (C1 / C2) | the cost build ran a segment path (its hand-back report says which) | that segment's all-in subtotal; forward, the segment subtotals and the consolidated total tie by the spine rule in `costs/segmentSpine.md` | in force 2026-10-12 |
| `Operating Income (modeled)` / `Operating Income (reported)` | `oi_modeled` / `oi_reported` | every cost build | always / when the inventory lists a reported operating-income line | modeled operating income and the reported cross-check | in force 2026-10-12 |
| `Depreciation & Amortization` | `dna` | the depreciation schedule (its `Depreciation` build) | always | the model's single D&A source: cash-flow D&A in history, `prior-year Net PP&E ÷ Implied Useful Life` forward. CONSOLIDATED FINANCIALS' `Total Depreciation & Amortization` references it in every column; a cost build's total-D&A line references it in the projection columns. A cost build that carries no D&A line (D&A embedded in its cost lines) references nothing — the consolidated section still has its source | in force 2026-10-12 |
| `Company-Operated EOP Balance` | `coeop_total` (consolidated); `coeop_<brand>` per brand / banner block | every location build (its consolidated Total block) | a location build ran (the revenue hand-back report says so) | the consolidated company-operated unit count — the per-location denominator for the cost builds' per-location rows and NWC's lease driver; a downstream builder keys its fallback on the row's absence, never on the industry | in force 2026-10-12 |
The cost-of-sales line is **not** a contract row: its only downstream reader, the NET
WORKING CAPITAL build, resolves it through its industry driver map, under the issuer's
own caption.
**The segment map.** A segment's revenue is not a contract row either, because a revenue
build may lay it as one row or as several (a brand's row; an advertising stream plus that
segment's other-revenue stream). The revenue stage's hand-back report therefore carries a
**segment map**: for each issuer segment the build laid revenue rows for, the segment's
name as the inventory's segment series spell it and the keys of those rows; an empty map
when the build laid no segment, brand or banner rows. The orchestrator passes it with
the revenue hand-back to the costs router and to the consolidated section (Step 4). A
cost build on a segment path takes each segment's revenue from the map and from nothing
else; the consolidated section lays its segment revenue lines from the same map. No
downstream stage resolves a segment by comparing labels.
### Lays when — the row-condition vocabulary
Every stage file specifies its rows in tables that carry a **`Lays when`** column. That
column says under what condition the row must be on the sheet, and `scripts/audit.py`
reads it at Step 5 to check that every specified row was laid. The values below are
the only ones the column may hold; they are defined here once, mirrored as constants
in `scripts/audit.py`, and `make lint` rejects any other value.
| Value | Meaning |
|---|---|
| *(blank)* | the row is always laid, and its historical cells are never all dashes. This is the default; `ALWAYS_COMPLETE` is its explicit spelling. |
| `ALWAYS_COMPLETE` | as blank, written out |
| `ALWAYS_DASHES_OK` | the row is always laid; its historical cells may all be dashes where the issuer reported nothing (an intermittent impairment line, a stream disclosed in one year) |
| `DATA_DEPENDENT` | the row is laid only when the issuer reports the line, which the sheet cannot prove either way; the audit does not check it. Prefer `ALWAYS_DASHES_OK` wherever the text can lay the row empty instead — except for a reported anchor (`<row> (reported)`) and its `Memo: … − reported` check: when the issuer does not report the figure, both are omitted, never laid as dashes, because a check row with nothing to check reads as a failed check; completeness is still tested by the required `Operating Income (reported)` foot |
| a flag expression | the row is laid, complete, exactly when the expression is true; `NOT` and `AND` over the flags below, e.g. `NOT HAS_DNA_EMBEDDED`, `HAS_LOCATION_BLOCK AND HAS_SEGMENT_ROWS` |
Each flag is the presence of one fixed-label row on the finished sheet, so the audit
reads it from the workbook and never from a hand-back:
| Flag | True when the sheet has |
|---|---|
| `HAS_GROSS_PROFIT_ROW` | a `Gross profit` row in COSTS (the issuer presents a gross-profit subtotal) |
| `HAS_DNA_EMBEDDED` | a `Memo: depreciation and amortization (embedded in the lines above)` row in COSTS — the retail cost build's memo row for an issuer whose D&A sits inside cost of sales and SG&A with no face line; the software build records the same fact in Notes and lays no row, so the flag is false on a software sheet and no software row keys on it |
| `HAS_SEGMENT_ROWS` | any `Total Costs and Expenses — <Segment>` row in COSTS (a segment cost path ran) |
| `HAS_LOCATION_BLOCK` | a `Company-Operated EOP Balance` row in REVENUE (a location build ran) |
| `HAS_NET_SALES_ROW` | a `Net Sales` row in REVENUE (the issuer reports merchandise net sales separately) |
A row whose label is a template (`Revenue from <Stream>`, `<Banner> — Costs`) is not
checked by label; the memo rows that foot those families to reported totals are what
guard them, and the audit checks every memo's content.
## Build mechanics — compose the sections, then render the workbook
The workbook is never edited in place. Every section is composed as a list of keyed
rows, and the whole workbook is regenerated from those lists on every render, in the
scaffold order of Step 2. `scripts/framework.py` is the single implementation of this
mechanic, `scripts/style.py` the single implementation of the styling and units
contracts, and `scripts/README.md` shows the build-directory layout. Four rules
follow:
- **One module per section, returning rows.** A section module returns its rows
(`Row` objects) and its notes, nothing else. It never opens the workbook, never
writes a cell, never computes a sheet address.
- **Formulas reference rows by key, never by address.** A formula writes `{key}`
where it needs another row's row number (`=F{rev_total}-F{cost_total}`), and the
renderer resolves every key at render time. A section may therefore reference a row
that another section lays down, whichever is written first and wherever it sits on
the sheet.
- **Build order and sheet order are independent.** Step 4 builds the sections in
dependency order (revenue → depreciation schedule → costs → consolidated financials
→ net working capital); `assemble()` always places them in the Step 2 scaffold
order. Nothing about the sheet layout changes when the build order changes.
- **Rendering is whole-workbook, from scratch.** `render()` assigns row numbers,
resolves keys, applies the styling in `style.py`, sets every row's indent from its
kind (the styling rules' indent hierarchy: a stage indents only its headers), writes
the Notes block, and saves.
Re-running the build after any change re-resolves every reference: there is no
insertion into a saved workbook and no address that can go stale. A key that no
assembled section lays down is a render error naming the key and the referencing
row, not a `#REF!` discovered later.
- **A stage owns its rows; the renderer is never the fix.** Every row has one owner,
the section or sub-build whose file defines it, and no stage adds, removes or edits a
row in another stage's module. A stage that needs a row it does not lay references it
by key and, if the row is missing, reports it (Step 4.2) rather than laying it. What
gets edited in a correction is always the owner's **section module**, the file the
agent wrote for that stage; `framework.py`, `style.py` and `audit.py` are fixed plugin
code. Build-process history — findings, re-runs, their outcomes — goes to
`BUILD_LOG.md` in the build directory, never into the workbook's Notes block, which
carries only modeling facts. A **modeling fact** is what a reader needs to interpret
a number on the sheet: what a line contains, why a plug or a ratio has the size it
has, which convention or basis was applied. **Build history** is what was tried,
changed or re-run. The explanation of a plug is a fact and stays on the sheet; the
story of how the builder found it is history and goes to the log.
- **Three places for what a build records, and the test for each.** Everything a
stage would write down goes to exactly one of these:
- **Notes block** — a modeling fact an analyst needs to read the model: what a line
contains, which basis or convention a ratio uses, a judgment the build made where
the filing allowed more than one reading, a default the analyst may want to
override. One line per fact, written as `<Section>: <fact>` in the analyst's
words, for a reader who has not read this skill: no section numbers, no route,
tier, path, mode, case or layout names, no driver colors, no words such as
contract row, spine, hand-back or stage.
- **`BUILD_LOG.md`** — build history: which route, tier, path or mode ran and why,
which series was chosen over a sibling, which checks were run and what they
returned, every re-run and its outcome, every data trap met.
- **Cell comment** — provenance and basis for one row, on its Col C label cell: the
series or filing a value came from, the denominator a ratio divides by, which rung
set a seed. An input cell itself carries no comment — its hyperlink and Col A series
name are the provenance — unless the value is an override or on a basis the link
does not show.
A stage file that says "record" or "note" something names the place; where it
does not, apply the test above.
After every render, recalculate with the xlsx skill's `recalc.py` (zero formula
errors); at Step 5, run `scripts/audit.py`.
The styling rules and the units contract in `../_common/` remain the specification;
`style.py` carries their values (colors, number formats, borders) once. Skill text
names a convention — "pale-green input fill", "red driver", "SUM-BAR" — and never
restates a color code or a format string. A change to a rule is made in the rule
text and in `style.py` in the same commit.
- **A rate names its base, and a count identity holds only counts.** A rate
driver's Col D tag names the row its formula multiplies or divides by, and that
row is on the sheet; a formula never applies a rate to a different row than its
tag names. A row inside an identity of counts (a unit roll, a store roll) is a
count, so a percentage never enters a sum of counts. A quantity the issuer states
as a share of a base is converted on the sheet from a visible share input against
the row that carries that base, never typed into a formula as a constant; where
the issuer's base is not one row, the share applies pro rata to each row that
carries part of it, the comment saying so.
- **The default driver seed is the analyst's: guidance, else the latest year.**
Every forward driver the builds lay takes its `N+1` value from the first of
these rungs that applies, and every later year references the `N+1` cell, so
the assumption is held flat and an override of `N+1` moves the whole line:
1. **Guided.** The shared guidance table from Step 3.5 names the driver for
fiscal year `N+1`: the midpoint of the range, written with `drivers_fixed()`
as a red driver hyperlinked to the earnings-release exhibit, the comment
quoting the issuer's words.
2. **Latest year, on the issuer's normalized basis.** The issuer discloses the
latest year's figure with a distortion removed (organic or constant-currency
growth, a 52-week comparable, a go-forward sales change, a balance net of a
named one-off item): that figure, as a red constant with the disclosure cited
in the comment. The reported historical cell is never changed.
3. **Latest year as reported.** `drivers()` in `scripts/framework.py` writes
`= IF(ISNUMBER(N), N, IF(ISNUMBER(N−1), N−1, IF(ISNUMBER(N−2), N−2, 0)))`:
the most recent numeric value among the three seed years, and **0** when
there is none (a decomposition disclosed only in the base year). The guard is
`ISNUMBER`, never `IFERROR`, so a genuine error still surfaces.
4. **Latest undistorted year.** Only when `N` itself is a disclosed distortion
and no normalized figure exists — a program year in a unit roll, a 53-week
`N` under a growth driver, the first comparison after an acquisition — the
seed references the latest clean year among `N−1` and `N−2`, and the comment
names the distortion.
No build averages years into an `N+1` seed, drops a year on its own judgment, or
seeds a CAGR. The one stated exception is the comparable-sales builds' `N+2`
rate, which reverts to the historical average comp as the long-run rate after a
guided or recency-seeded first year; elsewhere a multi-year average appears only
where a file lays it as a memo. The latest year
is the convention because it carries no mechanical step at `N+1`: the first
forward year continues the last actual, and the analyst's view goes on top as an
override. The driver's cell comment states which rung set it; the build does not
stop or ask.
- **Episodic cost lines are the one exception to the default seed.** Impairments,
restructuring and closing charges, settlements and asset gains or losses are seeded
by the rule in `costs/episodicLines.md`, which every cost build reads at its driver
methodology step: zero for a one-off, a run-rate for a recurring program line, the
disclosed remaining cost where the filing states one. The renderer writes those
seeds with `drivers_fixed()` and `drivers_runrate()`; the default seed is never
applied to a line in that class.
- **One seed for every driver; the residual is a ratio driver.** No build seeds a
driver on a CAGR: a named revenue stream's `YoY %` takes the
default seed above, and a historical CAGR appears only as an informational memo
where a build lays one. A revenue build's residual plug (`Other Revenue`, the
reported total less the named components) cannot stay a plug in projection,
because there is no reported total to plug against, and it is never seeded to zero
dollars, which would step projected revenue down at `N+1`. It is projected as a
**share of modeled revenue**: a driver row `Other Revenue — % of modeled revenue`,
tagged `(% of modeled revenue)`, holds the historical ratio of the residual to
the named components the build projects (never to a total that contains the
residual, which would be circular) and takes the default seed in projection; the
residual is then that driver times those components. The row's historical cells
are the build's fit diagnostic (low single digits expected) and are visible, not a
memo.
## Data source
See `../_common/data-source-rules.md` for the MCP-vs-REST routing table and the
canonical inventory of deepKPI operations. This skill uses `query_company_id`
(to resolve the company) and `list_kpis` (to discover the full, shared KPI
inventory once); the `industry-classification` sub-skill it invokes at Step 1.2
uses `get_company_summary` once; the `guidance` sub-skill it invokes at Step 3.5
uses `list_sec_filing_markdowns` and one `get_sec_filing_markdown` to read the
latest earnings-release exhibit once; the section sub-skills then use
`search_kpis` and the statement operations against that shared inventory.
## Hard stop if deepKPI access fails
See `../_common/connection-failure.md`. If deepKPI is unreachable, STOP and ask
the user before falling back to any other source.
## Opening line
Before doing anything else, say:
**"Let me resolve the company in deepKPI and set up the operating model
scaffold."**
## Step 1 — Resolve the company and classify its industry
### 1.1 — Resolve the company
The user provides **either a ticker or a company name**. Detect which:
- **Looks like a ticker** — 1–5 characters, all uppercase, no spaces (e.g.
`DRI`, `CMG`, `AAPL`): map it to the **official registrant name** first. Use
your knowledge of the common ticker → issuer mapping, and if there is any
ambiguity, ask the user to confirm the company before proceeding. `query_company_id`
resolves by **name**, not ticker.
- **Looks like a company / brand name** (e.g. `Darden`, `Olive Garden`,
`Chipotle`): use it directly. If the user named a **brand or subsidiary**, map
it to the **parent registrant** first (e.g. "Olive Garden" → "Darden
Restaurants").
Then call **`query_company_id`** with the official `company_name` (set
`num_of_res` to 3 so you can disambiguate). Take the `company_id` (SEC CIK for US
companies) from the response. If multiple plausible matches come back, show them
and ask the user to pick. **The `company_id` is required by every sub-skill**, so
carry it forward.
### 1.2 — Classify the industry (supported: restaurant, retail, software)
Invoke **`industry-classification/industry-classification.md`**, passing the
resolved `company_id` and the official company name. It makes one
`get_company_summary` call, applies its criteria in fixed precedence, and hands
back either **`industry: restaurant | retail | software`** with a one-line basis,
or **`UNSUPPORTED — <basis>`**.
- **Supported** → carry the industry and its basis forward — and, for software,
the **revenue model** the skill also returns. The `revenue` router, the `costs`
router and the `net-working-capital` build receive the industry in Step 4 and
act on it; the software revenue router also receives the revenue model. None of
them re-classifies.
- **`UNSUPPORTED`** → **stop here.** Tell the user, in one short paragraph, that
this skill currently models **restaurant, retail and software** issuers, what
the company was classified as and why (the basis), and that no workbook was
created. Do not scaffold, do not run `list_kpis`, do not route anywhere, and do
not improvise a generic build. The classification runs before Step 2 precisely
so that an unsupported company costs nothing and leaves nothing behind.
## Step 2 — Build the empty, styled workbook scaffold
Produce **one** `.xlsx` workbook with a **single worksheet** containing the five
sections **stacked vertically in this exact order**:
1. `REVENUE`
2. `COSTS`
3. `CONSOLIDATED FINANCIALS`
4. `DEPRECIATION SCHEDULE`
5. `NET WORKING CAPITAL`
**Fix the model timeline first.** The renderer cannot render anything, the scaffold
included, without a configured timeline, and it writes every fiscal year-end into the
period header row on every render. So before the scaffold, set a **provisional `N`**:
the fiscal year of the latest annual filing in the free `list_sec_filing_markdowns`
listing (one call, no credits), with the issuer's fiscal year-end convention read from
the same listing. Pass `hist = N−3 … N`, `proj = N+1 … N+5` and the year-end dates to
`configure(...)`. `N` is provisional because the listing can run a year ahead of the
annual series deepKPI holds; the revenue build confirms it (Step 4, item 1).
Produce it with the renderer — `scaffold()` in `scripts/framework.py` after
`configure(...)` (see **Build mechanics**) — which applies
**`../_common/revelata-excel-styling-rules.md`** for all layout and visual
conventions. The scaffold is what the renderer produces when no section has been
assembled yet. Concretely:
- **Columns A and B are hidden** (audit-trail columns); leave **A1:B1 empty**.
- **C1** = the workbook title: **bold, Calibri, 12pt, sentence case**, containing
the **full official registrant name** (from `query_company_id`) plus what the
sheet is, e.g. `Darden Restaurants, Inc. — operating model (deepKPI)`. Merge
rightward (C1:D1) if long.
- **Row 2 is fully blank** (spacer between the title and the body).
- **Gridlines off** for the sheet; font **Calibri** throughout.
- Lay down the five **ALL-CAPS section header rows** in the label column (Col C),
each **bold and underlined and font size 16**, separated by at least one blank
spacer row. The renderer places each section's rows beneath its header on every
render; nothing is inserted into this file later.
- **The period header row carries the timeline's year-end dates**, written by the
renderer from `configure(...)`: the four historical fiscal years followed by the
five projection years, per the **Model timeline** above. Nothing else is filled in:
no data, line items or values. The sub-skills lay the rows beneath the headers.
- Apply **freeze panes** consistent with the styling rules (Cols A–D and the
title region stay visible).
Save the scaffold to the user's workspace folder and report the saved path, as a link
where the runtime offers one, so the user can see the skeleton before sections are
filled.
## Step 3 — Discover the full KPI inventory (once, shared)
**After** the scaffold is laid down and **before** routing to any sub-skill,
discover **every KPI deepKPI has for this company** in a single call, so all the
sub-skills draw from **one shared inventory** instead of each re-discovering KPIs
on their own.
Call **`list_kpis`** with the resolved `company_id`. See
`../_common/deepkpi-api.md` (the `/v1.0/list_kpis` entry and "Step 2 — Discover
available KPIs") for the exact call. Key points:
- **This call is free** — no credits — so always run it here rather than letting
each sub-skill spend `search_kpis` credits to find the same names.
- Omit the `source` filter so the inventory spans **all filing types** (both
annual 10-K and quarterly 10-Q metrics).
- The response is a JSON object keyed by **category** — `Company KPIs`, `Segment
Financials`, `Consolidated Income Statement`, `Consolidated Balance Sheet`,
`Consolidated Cash Flow`, `Capital Structure`, `Taxes`, `Miscellaneous`,
`Off-Balance Sheet Items` — with each value an array of exact KPI name strings.
Read through the full inventory and **understand what is available** before
proceeding: note which categories are populated, which segments/brands appear,
and which KPI names map to each of the five sections. **Retain this inventory in
context as the shared KPI map** and pass it to every sub-skill in Step 4.
If `list_kpis` returns nothing or fails, follow `../_common/connection-failure.md`
(STOP and ask) — do not silently fall back to per-section discovery or other
sources.
**Which filing a historical value comes from.** A series' points are each taken from
some filing, and an issuer may present the same year differently in a later one: a
line reclassified, a figure restated, a line absorbed into another. Every stage applies
one rule: **a historical value is carried on the latest filing's presentation.**
- Before a stage takes a series for any line, it **lists every inventory series whose
caption names that line** (the face caption, a `Total …` prefix, the note total, a
segment or geography variant), across **every category** of the inventory and not
only the category the line was found in; the inventory is in hand and the scan
costs nothing.
When that scan finds **two or more series for one caption**, compare their
overlapping years with the latest 10-K and take the series that matches it; where
the latest 10-K does not present the year, the provenance-id test below decides.
Never take the first series found, and never skip the scan because the first
series looked complete.
- When a later filing **restates** an earlier year, the restated figure is the value and
the as-filed figure goes in the cell comment ("as filed in the FY<year> 10-K: <value>").
Which filing a point comes from is read from its provenance id, not from the filing
text: a deepKPI point carries a date and a docviewer id, and every point sharing one
id was taken from one filing, so **the latest-dated point under an id is that
filing's own year**. For year `Y`, the point whose id also carries a later year came
from the later filing and is the restated value. Where the competing ids both end at
`Y`, the FY`N` 10-K read in Step 3 settles it; where `Y` is outside that 10-K's
window and the ids still tie, carry the point whose id ends at `Y`, the other in the
comment, and log the choice.
- When a line reported in earlier years is later **absorbed into another line**, the
receiving line is on a consistent basis only if the absorbed line is carried as its own
row in the years it was separate, dashes after, so the totals agree across the window;
where the inventory has no series for it, carry the latest presentation and state the
break in a Notes line ("<Section>: FY<a>–FY<b> <line> excludes <absorbed line>, which
the issuer folded into it from FY<c>"). A line reported in some years only is never
omitted on that ground alone.
- Every such choice is one line in `BUILD_LOG.md`; a historical cell that differs from
the as-filed figure gets the Notes line above.
## Step 3.5 — Pull the issuer's guidance (once, shared)
After the inventory and before any section is built, invoke
**`guidance/guidance.md`**, passing the `company_id`, the company name and the
model timeline. It lists the company's 8-K filings, pulls the **latest
earnings-release exhibit** once, and hands back a **guidance table** for fiscal
year `N+1`: each guided item from its fixed list with the low, high and midpoint,
the basis the issuer states, the verbatim phrase and the citation. An issuer that
guides nothing hands back an empty table, which is a normal outcome, not a stop.
**Retain the table in context and pass it to every sub-skill in Step 4** beside
the inventory: it is the first rung of the default driver seed (Build mechanics)
and the source of the guidance check in CONSOLIDATED FINANCIALS. No sub-skill
pulls a filing to look for guidance on its own.
## Step 4 — Hand off each section to its sub-skill
After the scaffold exists **and the shared KPI inventory and guidance table are in
hand**, build the sections **in order** by invoking the sibling sub-skills, passing
each the resolved `company_id`, the official company name, the workbook path, **the
shared KPI inventory from Step 3 and the guidance table from Step 3.5** so they can
go straight to the relevant KPI names (using `search_kpis` only for the specific
series they need, not to re-discover the catalog) and seed guided drivers without
re-reading any filing. Every sub-skill inherits the **Model timeline** defined
above — the four historical fiscal years (`N−3 … N`) plus the five projection
years (`N+1 … N+5`) — and must not define its own:
1. **`revenue`** — routes by industry and builds the revenue model beneath the
`REVENUE` header. Pass it the **industry determination from Step 1.2** — and,
for software, the **revenue model** — in addition to the standard inputs; it
routes on those and does not classify.
**Capture, when the revenue build returns, the segment map** from its hand-back
report ("The segment map", above) and, **for retail, the revenue build method**
(which revenue sub-build ran) — the costs router and the consolidated section need
them.
**Confirm `N`** when the revenue build returns: its first annual series shows the
last fiscal year with complete actuals. If that differs from the provisional `N`
of Step 2, re-run `configure(...)` with the corrected timeline, re-run Step 3.5
(its table is keyed on `N+1`), re-run the revenue stage against the corrected
years, and render again; rendering is whole-workbook, so nothing provisional
survives. No later stage has run at that point.
2. **`depreciation-schedule`** — builds the PP&E roll-forward beneath the
`DEPRECIATION SCHEDULE` header, including the model's single D&A source (the
contract row `Depreciation & Amortization`). **Runs after `revenue`** (its capex
and Other rows are a % of `Total Revenue`) **and before `costs`**, because the
cost build's forward D&A and the consolidated financials' D&A reference it. On
the sheet it still sits fourth; see **Build mechanics**.
3. **`costs`** — builds the cost structure beneath the `COSTS` header. **Runs only
after the `revenue` build is fully done** (it depends on the revenue lines and
on the industry classification established during the revenue build) **and
after the depreciation schedule** (its total-D&A line's projected cells reference
the schedule). Pass it the **industry determination from Step 1.2, the segment map and, for
retail, the build method captured from Step 4, item 1** in addition to the
`company_id`, company name, workbook path, and shared KPI inventory, so the
costs router can route by industry **without re-classifying**.
4. **`consolidated-financials`** — builds the consolidated statements beneath the
`CONSOLIDATED FINANCIALS` header, as references to the REVENUE and COSTS rows and
to the depreciation schedule's `Depreciation & Amortization` row. Pass it the
**segment map** captured at item 1 and the cost build's hand-back report (which
path it ran).
5. **`net-working-capital`** — builds the net working capital schedule. Pass it the
**industry determination from Step 1.2** as well: it reads that industry's driver
map (`net-working-capital/restaurants/`, `retail/` or `software/`) for which
current operating lines to expect and which upstream rows its drivers divide by;
its mechanics are the same for every industry.
**Order dependency (non-negotiable):** `revenue` first; the `depreciation-schedule`
before `costs`; `costs` before `consolidated-financials`; `net-working-capital`
last. The industry classified once in Step 1.2 is handed to both the revenue
router and the costs router, so the two sections are always built for the same
company profile; neither router re-derives it.
The build order deliberately differs from the sheet order: the renderer places every
section under its scaffold header regardless of when it was built, so a section's
position on the sheet never changes.
Each sub-skill is responsible for its own deepKPI pulls, period columns,
formulas, and styling (all of them also follow
`../_common/revelata-excel-styling-rules.md` and
`../_common/revelata-excel-units-and-scale.md`). Build the whole model **end to
end** by default; pause after the scaffold and confirm between sections only when
the user asks to review the build section by section.
### 4.1 — Vocabulary for the two loops below
A **stage** is one unit of the build that lays rows: the location build, a revenue
sub-build, the franchise block, the depreciation schedule, a cost build, the
consolidated section, net working capital. A build is the ordered list of stages actually
run, in the Step 4 order. A **router** (`revenue/revenue.md`, `costs/costs.md`, the
industry routers beneath `revenue/`) picks which stage file runs in a slot and runs its
section's stages in order; it lays nothing. This orchestrator runs the sections in order,
keeps the build's one log, and is the only thing that ever re-runs a stage. A stage or
router returns an **outcome** by finishing its own hand-back section — `COMPLETE` with its
hand-back report, or one of the fixed strings below.
### 4.2 — A row missing upstream: the correction loop
The routers' `ABORTED` loop (their Step 4.1) handles a stage that cannot be built. This
step handles the other case: a stage finds that **a row it is entitled to expect from an
earlier stage does not exist**. No stage ever lays, edits or plugs another stage's row;
it reports, and the owner is re-run.
**What a stage may report, and how it knows.** A stage may report only a row its own file
declares in its **Dependencies** table: the row as that file names it, the stage that owns
it, the **existence condition** under which the owner lays it, and the **fallback** when
the condition is false (the net-working-capital driver maps are the model). Contract rows
are declared once in the table above with a fixed key and condition. Diagnosis is three
mechanical steps: the row is in the stage's Dependencies table; its existence condition
evaluates **true** on evidence the stage may read (the shared inventory, a hand-back
report, or the filing where the stage's own text already reads it); and no row in the
owner's module matches it — the renderer reports the key unresolved, or the stage's own
check finds none. Condition **false** is not a finding: the stage takes its declared
fallback. A reference that is in no Dependencies table and does not resolve is not a
finding either: the build stops with `RE-ENTRY FAILURE — undeclared dependency: <stage>,
<row>` and the pair is a skill defect to report, not something to work around.
**Outcomes.**
| Outcome | Returned by | Meaning |
|---|---|---|
| `DEFECT UPSTREAM — <owner>: no <row> for <scope>, <columns>; <evidence>` | any stage; returned **unchanged** by every router it passes through | a declared row whose condition is true does not exist. `owner` = the section and, where relevant, the stage within it, as the Dependencies table names it; `scope` = a brand / banner / segment as the owner's hand-back report spells it, or `consolidated`; `evidence` = the series or filing citation, informational only |
| `RE-ENTRY FAILURE — <reason>` | the re-run owner, or this orchestrator | the row could not be laid; or the finding repeats one already acted on; or the dependency is undeclared |
| `OUT OF ORDER — <section>` | a stage's prerequisite guard | this orchestrator's own error: build that section, then resume |
**The loop.**
1. **Report.** The stage stops at the check that found the row missing and returns the
record to the file that invoked it, keeping its module as laid so far. A router
returns the record unchanged as its own outcome, so every finding reaches this
orchestrator by one or two hops; no file decides a level.
2. **Repeat check.** Keep one **ledger** in `BUILD_LOG.md`, keyed by (owner, row,
scope). A record whose key is already there is **not** re-run — the owner would
produce the same module from the same text — and the stage that reported it receives
`RE-ENTRY FAILURE — unresolvable` (step 5). This is the only termination guard and it
is sufficient: findings travel backward in build order, re-runs forward, the possible
keys are finite, and a re-run is deterministic. Log the record.
3. **Re-run the owner.** Tell the owner's section router to re-run its section **from the
owner stage forward** (re-run a cross-section builder directly). The owner reopens its
own file — permitted under Golden Rule 1, the workflow now routes there — applies its
own text to the finding, trying its own paths in its own order and testing after each
whether a row matching the record now exists in its module, and commits the first path
that satisfies it. **Satisfy or leave untouched:** if none does, it changes nothing and
returns `RE-ENTRY FAILURE — <reason>` in its own terms ("gross split disclosed at the
consolidated level only"). It re-pulls at most the series the finding needs; the
inventory is never re-listed. It re-issues its hand-back report.
4. **Re-run everything after the owner, in build order.** The router finishes its section
from the owner forward — its route selection runs again as part of that, so routing
re-evaluates itself — and this orchestrator then re-runs every later section. Each
stage runs again from its own file against the rows that now exist, so every
row-presence test is re-evaluated. Stages between the owner and the one that reported
never see the finding; they are simply run again. There is no partial re-render: a
forward re-run is always correct and is one to a few stages.
5. **On `RE-ENTRY FAILURE`**, re-run the stage that reported with the reason. It does one
of three things, in this order and nothing else: its declared fallback for that
situation; its own `ABORTED` to its router, if it is a revenue sub-build that cannot
build without the row; otherwise the build stops naming the row and the reason.
6. **New information is a new finding.** A stage may report against the same owner again
only with a different row or scope; the identical record is what step 2 refuses.
7. **Log, not Notes.** Every event goes to `BUILD_LOG.md`. The workbook's Notes block
carries only the **resulting modeling facts**, stated exactly as they would be had no
loop occurred ("Company Openings for <banner> are 10-K overrides, store activity table
FY<n>"). A resolved loop leaves no trace in the workbook. The loop never asks the user.
**Two cases this loop does not cover.** A *wrong value* upstream is the owner's own
pre-delivery checklist's job — a downstream stage has no independent evidence about an
upstream cell — and is never a finding. A customer roll-forward built on the *wrong
population* is caught by the software router before it dispatches the ARPU build
(`revenue/software/software.md`, Step 4), not downstream.
## Step 5 — Final QC and correction pass
After all five sections are built, run the **unit audit** defined in **Rule 6** of
`../_common/revelata-excel-units-and-scale.md` across the finished workbook. Run it **in
code**, not by reading the sheet: all of checks 1–5 are mechanically computable, and
a by-eye pass is how unit defects reach delivery in the first place.
This step runs **once, at the end**, because the checks that matter most are
cross-section: a sub-skill cannot see what display scale a sibling section chose.
### 5.1 — Run the audit
Run `scripts/audit.py` against the recalculated workbook, naming every stage file the
build ran (the files `BUILD_LOG.md` records, in build order):
```
python3 scripts/audit.py <workbook> --last-hist-col <letter of N> --stages <stage file> <stage file> …
```
It evaluates Rule 6 checks 1–5 over every row of the data region (Col E onward), for
every section, and prints the stored and displayed `Total Revenue` for check 6, which
needs one lookup against the cited filing. With `--stages` it also runs three checks
against the stage files' own row tables:
- **7 row presence** — every row a stage specifies with a `Lays when` that holds on this
sheet (the vocabulary above) exists in that stage's section, and a required row whose
historical cells are all dashes counts as missing unless its value is
`ALWAYS_DASHES_OK`. The audit prints the stage list and the required set it derived,
so an omitted stage file is visible in the output.
- **8 memo content** — every row whose label ends `(must = 0)` reads zero within
rounding in every historical column, and every `(must ≈ 0)` row within half a
percent of that column's `Total Revenue`.
- **9 margin reads** — `Gross margin` equals gross profit over revenue and
`Operating margin` equals operating income over revenue in the last historical column.
- **10 vocabulary** — no Notes row and no cell comment contains a section number, a
route, case, mode, tier, layout or approach name, a driver color, or skill vocabulary
such as contract row, spine or hand-back (Build mechanics, "Three places"). Runs with
or without `--stages`.
- **11 header rows** — a row with a label, no Col D tag and no value or formula in any
period cell is a header: its period cells must be empty, never dashes, and its label
bold and underlined (styling contract). A data row that is empty in a period shows a
dash and carries a tag. Runs with or without `--stages`.
### 5.2 — Report findings before changing anything
Emit a findings table:
| Row | Col C label | Check | Observed | Expected |
|---|---|---|---|---|
If the audit is clean, say so explicitly — do not pass over it in silence.
### 5.3 — Correct, within scope
**Fix only:** missing or malformed Col D tags; wrong or missing number formats;
`General`-format cells in the data region; display scales inconsistent within a
family; and scale constants in formulas — fixed by correcting the **stored value**
per Rule 1 and then deleting the constant, never by adjusting the constant.
**Do not:** restructure rows, add or remove line items, change modeling drivers, or
revisit any routing or classification decision. If a finding cannot be corrected
without making a modeling judgment, leave it in place and surface it in 5.4.
A **row-presence** or **memo-content** finding (checks 7 and 8) is never corrected
here: it means a stage did not lay a row its own text specifies, or laid one that does
not foot. Re-run the owning stage from its file against the rows that now exist, as
Step 4.2 re-runs an owner, then re-run every later stage in build order and render
again. A stage that cannot lay the row returns `RE-ENTRY FAILURE` naming it and its
reason. The build does **not** stop: record the finding and the reason in
`BUILD_LOG.md`, carry both into the report (5.4) as an unresolved finding, and finish.
A reason of the form "my own text omits this row for this case" is a `Lays when`
defect in that file's row table — report it as a skill-text issue, not as a model
defect.
**Never edit a stored value to make a check pass.** A stored number that disagrees
with the filing is a data problem, not a formatting one — report it.
### 5.4 — Re-run, then report
Re-run the audit until checks 1–11 return zero findings, or only the findings recorded
as unresolved under 5.3. Then report to the user: what was corrected, any unresolved
finding with the owner's reason, and any judgment items needing their call — for example a bare
`(%)` whose denominator is not clear from its row label, or a family whose
conventional display scale is genuinely debatable.
## Notes
- **Derived cells must be live `=` formulas** wherever sub-skills compute values
(margins, totals, imputed quarters) — never hardcoded results. This is enforced
per section but the orchestrator should remind the user the model is built to
recalc.
- If the user only wants **one** section (e.g. just a revenue build), you may
skip the **workbook scaffolding** (Step 2) — but **still do Step 1 (resolve the
company and classify its industry) and Step 3 (discover the full KPI inventory
via `list_kpis`)** before routing. The KPI discovery always runs first so the single sub-skill receives
the same shared KPI inventory; only the scaffold is skipped. Then route straight
to that section's sub-skill, passing it the `company_id`, company name, and the
shared inventory (or use the `kpi` skill for a raw pull).
- **Supported industries are restaurant, retail and software; there is no generic
build.** Adding an industry means four things, all of them: a criterion in
`industry-classification/`, a revenue build under `revenue/`, a cost build under
`costs/`, and a net-working-capital driver map under `net-working-capital/`.
Referenced files: 38 — file references are metadata, not package contents.
pressure-test9.62 KB
View saved version →
---
name: pressure-test
description: >
Pressure-test any analyst report on a US public company. Upload a PDF of a sell-side
report and say "Pressure test this" — the skill extracts the analyst's key arguments,
pulls granular SEC filing data via deepKPI to build supporting and counter-evidence
for each claim, then produces an interactive HTML report with Revelata dark branding,
Chart.js visualizations (blue palette), source attribution (amber = analyst, cyan = SEC),
and clickable provenance hyperlinks to the exact filing passages. Use whenever the user
mentions "pressure test", "pressure-test", "critically analyze", "triangulate", "fact-check",
or "challenge" in the context of an analyst report, research note, or investment thesis.
Also triggers when the user uploads a PDF that looks like a sell-side report and asks
for analysis, counterpoints, or a second opinion on it.
---
# pressure-test
Take an analyst report and rigorously test every argument against SEC filing data.
The output is an interactive HTML report that a portfolio manager can open in a browser
and use to form an independent view — every number is clickable back to its source.
## Data source
See `../_common/data-source-rules.md` for the runtime routing table and the canonical
inventory of deepKPI operations.
## Hard stop if deepKPI access fails
See `../_common/connection-failure.md`. STOP and ask before falling back.
## When this skill triggers
- User says "pressure test this" (or similar) with an uploaded analyst report
- User uploads a sell-side PDF and asks for critical analysis, counterpoints, or fact-checking
- User asks to "triangulate" or "challenge" an investment thesis
## Chat pacing and verbosity
Keep the **conversation** thin; put the depth in the **HTML report**.
- After reading the PDF, state the **4–6 main arguments** you will pressure-test
(short titles or one line each — this is the roadmap for the user).
- Treat each request as self-contained: **do not reference, compare to, or borrow from any other analyst reports** you may have analyzed earlier in the same chat/session unless the user explicitly asks you to do so. Assume you are pressure-testing **only the report(s) uploaded with the current request**.
- Also state you'll pull **market reaction context** (price on report date vs today),
then move straight into data pulls.
- Do **not** narrate every tool call, intermediate table, or reasoning step in chat.
Avoid dumping raw deepKPI responses into the thread.
- While pulling data, use a **brief progress line per argument**, e.g.
`Pulling SEC data for 2/5: [short argument title]…` then continue silently until
the next argument or until the HTML is ready.
- **Automatically build and save the HTML report** when pulls and drafting are
complete. Do **not** ask whether to generate the file unless the user explicitly
asked for a non-HTML deliverable only.
- **Before presenting the HTML**, run the **pressure-test double-check** pass
(see below). In chat, a single line is enough, e.g. `Running pressure-test QA on the draft HTML…`
- After the artifact is **double-checked** and any **Critical** issues are
addressed (or explicitly accepted by you as the authoring agent): **present**
the file (`present_files` or equivalent) and add **1–2 sentences** on the
sharpest takeaway — no full recap.
## High-level workflow
1. **Read the report** — extract the analyst's 4-6 key arguments/claims
2. **Identify the company** — resolve the ticker/name to a CIK via deepKPI
3. **Pull SEC data** — for each argument, search deepKPI for the most granular
metrics that can confirm or complicate the claim
4. **Build evidence pairs** — for each argument, write a "supports the thesis"
card and a "complicates the thesis" card, grounded in specific numbers
5. **Write the synthesis** — 2 paragraphs that weave the evidence into a nuanced
take the analyst didn't give you
6. **Generate the HTML** — an interactive, dark-themed report with Chart.js charts,
source attribution, and provenance links (**default deliverable — produce it without asking**)
7. **Double-check the draft HTML** — mandatory QA pass before the user sees the file;
structured review only (see **Step 7**)
8. **Revise and iterate** — you decide which findings to fix; re-run the double-check
after material edits until **Overall: Pass** or remaining issues are only **Suggested** / **Optional**
9. **Deliver** — present the final HTML to the user (**short wrap-up only**)
Each of these steps is detailed below. Follow **Chat pacing and verbosity** for what to say in-thread vs. what belongs only in the HTML.
## Dependencies
This skill relies on the `kpi` skill for **KPI** retrieval and on the `filing`
skill when you need **verbatim SEC filing passages** or **full filing markdown**
(not just structured metrics). Consult the `kpi` skill for metrics workflow,
provenance, and gaps; consult the `filing` skill when claims must be checked
against exact filing language. Use the `company-summary` skill for **business
description**, **segment structure**, or **thematic** lists when helpful (not
required for every run). Use the `implied-metric` skill when filling Q4 or
segment gaps. This skill handles the analytical framework and HTML output.
For the mandatory QA rubric on the finished HTML, read **`references/double-check.md`**
(internal to this skill — not a separate user-facing skill).
## Scope
This skill works for **US public companies** with SEC filings indexed in deepKPI.
If the report covers a non-US company or a private company, tell the user that
deepKPI doesn't have coverage and offer to do a qualitative analysis instead
(without the data-backed evidence cards and provenance links).
---
## Step 1: Read the analyst report
Read the uploaded PDF. Extract:
- **Company name and ticker**
- **Report metadata**: author/firm, date, rating action (upgrade/downgrade/initiation),
price target if applicable. Store the firm name internally for source pills and claim
banners, but **do not surface the firm name in the footer or synthesis**.
- **4-6 key arguments** the analyst makes. These are the claims you'll pressure-test.
Look for: revenue/growth commentary, margin trends, segment performance, balance
sheet / leverage, competitive positioning, specific catalysts or risks.
Write each argument as a 1-2 sentence summary. You'll use these as the section
headers in the final report. **In chat**, share this list once as the identified
main arguments; do not pre-write full evidence cards in the thread.
## Step 1.5: Market reaction context (price move since publication)
Get the stock **close price** on:
- **Report date** (use the report's publication date; if only a date is given, use that day's close)
- **Current date** (most recent close)
Compute percent move: \(\\Delta\\% = (P_\\text{now}/P_\\text{report} - 1)\\times 100\\).
### Reporting rules (keep concise)
- Add a small **\"Price since report\"** box near the top of the HTML with the two closes and the \(\\Delta\\%\\).
- Keep the commentary **blunt and non-narrative**: factual finance language only.
- If price data cannot be retrieved reliably in the current environment, state that in the HTML box and continue with the SEC pressure test.
## Step 2: Resolve the company in deepKPI
Use the `kpi` skill workflow:
1. Call `query_company_id` with the company's official registrant name (not brand)
2. Call `list_kpis` (free) to see every available metric
3. Scan the KPI list to plan your searches — map each analyst argument to the
most granular KPIs available
## Step 6: Generate the HTML report
Read `references/html-template.md` in this folder for the complete HTML/CSS/JS template.
The report structure includes:
- Header link label: **\"Analysis Pressure Test (GitHub)\"**
- Title: \"[Company] ([Ticker]): Pressure-Testing the [Firm] [Action]\"
## Step 7: Double-check the draft HTML (mandatory)
After the draft HTML file is written (saved path known), you **must** run the
**pressure-test double-check** before `present_files` / showing it to the user.
1. **Read** `references/double-check.md` (internal QA reference for this skill).
2. **Execute the procedure** in that document against the draft HTML:
- If your host can invoke a **separate reviewer agent**, give it that doc plus
the HTML path (and the analyst PDF path when available) and collect its output.
- Otherwise, **perform the same pass yourself** in a distinct step: read the HTML
end-to-end, build the claim graph, run rules **R1–R12**, and emit the **exact
output format** specified there (structured review — **do not rewrite** the
HTML inside the double-check output).
3. Pass the **source PDF path** into the double-check whenever you still have it;
otherwise R1 and R8 must appear under **`Not checked`** in the review.
**You** (the authoring agent) **own revisions**: use the review's severity-ranked
findings to patch the HTML. Typical loop:
- Fix all **Critical** before delivery. **Fail** overall → treat as blocking until
addressed or the user explicitly waives QA (only if they say so).
- **Needs Revision** → fix Critical, then re-run Step 7 on the updated file.
- **Pass** with only **Suggested** / **Optional** → you may still polish, then deliver.
Re-running Step 7 after substantive edits is **recommended** so regressions do not
ship.
---
## Reference files
| File | When to read |
|------|-------------|
| `references/html-template.md` | Always — contains the full HTML/CSS/JS template |
| `references/chart-patterns.md` | When building charts — palette, dash patterns, examples |
| `references/double-check.md` | **Always after draft HTML exists** — mandatory internal QA rubric and output format |
Referenced files: 4 — file references are metadata, not package contents.
revelata7.82 KB
View saved version →
---
name: revelata
description: >
Broad orchestrator for Revelata deepKPI research on US public companies — use for
complex multi-step queries that cross multiple workflows (KPIs + filing text + segments,
pressure-test plus follow-up data pulls, peer benchmarks plus seasonality, etc.).
Routes to narrow skills (kpi, company-summary, filing, seasonality, implied-metric,
pressure-test, benchmark, ideas, operating-model) when a single workflow fits. Pulls SEC metrics
(10-K/10-Q/8-K): segments, unit KPIs (stores, comps, ARPU, users), statements, cash
flow; filing markdown and verbatim quotes; implied Q4/per-unit, seasonality, Excel.
For "pull data", historicals, "find the KPI", seasonality, .xlsx, filing excerpts,
pressure-test analyst PDFs, peer benchmarks, idea generation, or company summaries
across multiple needs.
metadata:
openclaw:
emoji: "📊"
requires:
env:
- DEEPKPI_API_KEY
bins:
- curl
primaryEnv: DEEPKPI_API_KEY
---
# Revelata deepKPI orchestrator
Query structured KPIs, SEC filing text, and company summaries for US public
companies, powered by [Revelata](https://www.revelata.com).
This is the **broad orchestrator** for the Revelata bundle. Use it when a request
crosses multiple workflows. For a single, well-scoped task, route to the narrow
skill below.
## Data access
See `../_common/data-source-rules.md` — single source of truth for how to call
deepKPI from MCP-capable runtimes (Claude, ChatGPT) vs non-MCP runtimes
(OpenClaw). Every skill below depends on this.
## Hard stop on connection failures
See `../_common/connection-failure.md`. If deepKPI access fails, STOP and ask
the user before falling back to other sources. This rule applies to **every**
skill in the bundle.
## Sub-skill routing
**Read the relevant narrow skill before doing any work.** Multiple skills may
apply to a single request — chain through all that fit.
| User need | Skill |
|-----------|-------|
| No specific company or thesis — "what should I invest in?", "what stock should I buy?", **new ideas**, **interesting companies**, boredom / open exploration | `ideas` — interactive funnel + `company_summary_search` screen; then `kpi` for deep pulls |
| Pull historical KPIs / financials from deepKPI | `kpi` |
| **What a company does**, **segments / geographies**, or **thematic** "who does X?" lists | `company-summary` — named company: `get_company_summary` / `get_company_segments`; discovery: `company_summary_search` then hydrate as needed |
| Pull **full filing markdown** into chat, **verbatim SEC text**, quotes, MD&A / risk-factor language, or "what did they say" | `filing` — **before any web/SEC.gov**: `list_sec_filing_markdowns` (free) → `get_sec_filing_markdown` (10 credits). Return full markdown when they want the document; blockquoted excerpts for quote-style asks; no paraphrase as quotes |
| Derive missing Q4 numbers, a segment remainder, or per-unit economics (ASP, ARPU, AUV, take rate) | `implied-metric` |
| Split annual forecasts into quarterly estimates / seasonality patterns | `seasonality` |
| **Build an operating model** in Excel — scaffold plus revenue, costs, consolidated financials, depreciation, NWC | `operating-model` — orchestrates section sub-skills; read `kpi` for raw pulls only when the user wants data without the full model workflow |
| Produce an Excel workbook (.xlsx) from deepKPI data (not a full operating model) | Whichever data skill applies (`kpi`, `seasonality`, etc.); then follow `../_common/revelata-excel-styling-rules.md` |
| Pressure-test a sell-side / analyst report against SEC filing data (HTML report, Chart.js, provenance links) | `pressure-test` |
| **Peers / comps**, **benchmark** a company, **most similar** public companies, or operational comp sets | `benchmark` — always read the `kpi` skill first for KPI mechanics |
| Tearsheet of a company | `tearsheet` |
**Default entry points by intent:**
- For **metrics and modeling feeds**, start with `kpi` (it chains to `implied-metric`,
`seasonality`, and `../_common/revelata-excel-styling-rules.md` as needed).
- For **business description, segment structure, or thematic company lists**, start
with `company-summary`.
- For **filing markdown (full or excerpts) and verbatim quotes**, start with `filing`.
If a request mixes these, run **every** narrow skill that applies.
## Sub-skill summary
**`kpi`** — Primary workflow for **structured KPIs** (company ID, `list_kpis` /
`search_kpis`, gap handling including Q4 derivation), provenance rules, in-chat
tables, and the mandatory post-pull Excel offer.
**`company-summary`** — `get_company_summary` (what they do) and
`get_company_segments` (how they break out segments / geos);
`company_summary_search` for **thematic** "who does X?" discovery. Not for KPI
time series.
**`filing`** — **SEC filing markdown** in chat: full filing or excerpts,
verbatim quotes, MD&A / risk language. Tools: `list_sec_filing_markdowns` →
`get_sec_filing_markdown`, source hierarchy (tool-first, not web), blockquote
discipline for quote-style asks. Distinct purpose from KPI time series.
**`implied-metric`** — Compute metrics that deepKPI doesn't report directly from
data that IS reported: `Q4 = FY − (Q1+Q2+Q3)`, missing segment = total − known
segments, per-unit economics (revenue per store, ARPU, ASP), take rates,
penetration rates, geographic mix percentages. Standalone so custom user-built
skills can invoke it.
**`seasonality`** — Compute each quarter's typical share of the full fiscal year
from 2–3 years of actuals, then apply those ratios to split an annual projection
into quarterly estimates.
**`pressure-test`** — End-to-end workflow for uploaded analyst PDFs: extract
4–6 claims, pull granular deepKPI metrics per claim, build paired "supports" /
"complicates" evidence with mandatory provenance hyperlinks, and emit a
Revelata-branded interactive HTML report (follow that skill’s html-template and
chart-patterns references). After the draft HTML exists, run that skill’s
mandatory **draft HTML QA** rubric (double-check), then revise and re-check as
needed before presenting the file. **Always** read `kpi` for KPI mechanics; use
`filing` when the test needs verbatim filing passages; use `company-summary` for
business description / segment structure / thematic lists when useful; use
`implied-metric` when filling Q4 or segment gaps.
**`benchmark`** — Curated **operational** benchmark sets (not trading multiples):
`company_summary_search` for candidates, KPI fingerprint + alignment, diff-driven
segment sub-benchmarks, chat 1-pager, optional HTML (follow that skill’s
html-template reference), Excel via
`../_common/revelata-excel-styling-rules.md`. **Always** read `kpi` first.
**`ideas`** — Onramp when the user has **no ticker and no thesis**: rapid
interview (multiple-choice by default), `company_summary_search` for a 10–15
name screen with thesis-matched KPIs, then deep pulls on 1–2 chosen names per
`kpi`. Use `list_kpis` / `search_kpis` for diagnostic KPI concepts. **No**
default Excel offer. Not for users who already named a company or a specific
metric — use `kpi` instead.
**`tearsheet`** - A one-pager tearsheet that an analyst would find useful for
earnings call prep. Contains a company summary, snapshot of key financials
and operational KPIs, etc.
**`operating-model`** — Full operating-model orchestrator: resolve the company,
lay down a styled Excel scaffold (Revenue, Costs, Consolidated Financials,
Depreciation Schedule, Net Working Capital), discover the KPI inventory once via
`list_kpis`, then hand each section to its sub-skill (`revenue/`, `costs/`,
`consolidated-financials/`, etc.). Industry-specific revenue and cost builds
(e.g. restaurants) route automatically. Uses `../_common/revelata-excel-styling-rules.md`
and the xlsx skill throughout. For a single section only, still resolve the
company and run KPI discovery before routing.
seasonality10.1 KB
View saved version →
---
name: seasonality
description: >
Computes seasonal quarterly ratios from historical actuals and applies them to
split annual projections into quarterly estimates. Trigger for: quarterly
breakdown of annual forecasts, in-progress fiscal year projections, any request
involving quarterly splits or seasonal patterns. Always retrieve underlying data
via the kpi skill first. For `.xlsx`/CSV workbook exports, follow
../_common/revelata-excel-styling-rules.md (plus the xlsx skill).
---
# seasonality
Computes each quarter's typical share of the full fiscal year, then applies those
ratios to project quarterly values from annual forecasts.
**All historical inputs must come from deepKPI** (via the `kpi` skill). Call
`list_kpis` first to discover exact KPI names for both the quarterly and annual
series before searching. Never use hardcoded or assumed figures.
**Do not** apply to balance sheet items (cash, receivables, inventory) — these
are point-in-time stocks; use the fiscal year-end value as Q4 directly.
## Data source
See `../_common/data-source-rules.md`. Deep KPI data flows through the standard
`query_company_id` / `list_kpis` / `search_kpis` workflow used by the `kpi` skill.
## Hard stop if deepKPI access fails
See `../_common/connection-failure.md`. STOP and ask before falling back.
## Core formula
```
Q_ratio(year, q) = Q_value(year, q) / FY_value(year)
seasonal_ratio(q) = simple average of Q_ratio across most recent 3 years
projected_Q_value = projected_FY × seasonal_ratio(q)
```
Four ratios must sum to ~100%. If materially off, normalize before applying.
## Steps
1. **Retrieve** annual + quarterly actuals from deepKPI (minimum 2 years; 3 is
standard). Capture the provenance URL for every value returned.
2. **Always derive Q4** — companies don't file a Q4 10-Q so Q4 will never be
directly in deepKPI. Always compute `Q4 = FY − (Q1+Q2+Q3)` for every year
in the series. Use the `implied-metric` skill. Put Q4 **in the quarterly
revenue (metric) row**, Q4 column—**not** a separate "Q4 (derived)" row. Flag in
**Notes**.
3. **Compute per-year ratios** — divide each quarter by its fiscal year total.
4. **Average** across the 3 most recent complete years.
5. **Flag anomalies** — if one year is distorted (product launch, acquisition,
COVID), note it in the Notes column and consider excluding from the average.
6. **Apply** ratios to each projected annual figure.
## In-progress fiscal year
When Q1–Q2 are actuals and you need to project Q3–Q4 only:
```
remainder = projected_FY − Q1_actual − Q2_actual
Q3_reweighted = Q3_ratio / (Q3_ratio + Q4_ratio)
Q4_reweighted = Q4_ratio / (Q3_ratio + Q4_ratio)
projected_Q3 = remainder × Q3_reweighted
projected_Q4 = remainder × Q4_reweighted
```
The same re-weighting logic applies for any subset of remaining quarters.
## Output format (in-chat tables)
Follow the `kpi` and `implied-metric` skills for how tables read in chat.
**From the `kpi` skill (data pull):**
- **Provenance:** Every numeric figure uses a clickable deepKPI link —
`[value](exact-url)` — per the `kpi` skill's **Provenance — non-negotiable rules**
(copy URLs exactly as returned; never reconstruct or guess). For **derived**
values, link the **operands** or state sources as that skill requires.
- **KPI identity:** Merge into one series when deepKPI returns the **same KPI**
(same metric, unit, aggregation); use **separate** rows or tables when they
differ.
- **Order:** **Annual figures first**, chronological (**oldest at top**), then a
**clear break** (rule, blank line, or subheading), then **quarterlies** (oldest
first). Do not interleave annual and quarterly without separation.
- **Tall vs wide:** For ordinary historical feeds, prefer **tall** tables — **each
row is one period**, first column **Period**, then **one column per metric** when
periods align — per the `kpi` skill's **In-chat table template (data pull)**. **Do
not** use a wide date axis "many period columns" layout for `.xlsx`-style modeling
in chat; that layout is for files (`../_common/revelata-excel-styling-rules.md`).
**Actuals block (aligned quarters + Q4 across years)** — use the `implied-metric`
skill's **Multi-period table** pattern (wide grid is allowed **here** for this
derivation layout):
- **Time flows left to right** — column headers are period labels (`FY22-Q1` …
`FY22-Q4`), earliest at left.
- **One row per time series** — each operand row maps 1:1 to one deepKPI series; do
not fragment into separate rows per quarter (e.g. not "Revenue - Q1" / "Revenue -
Q2").
- **Imputed Q4 in-stream** — the quarterly **metric row** carries Q1–Q3 from
deepKPI and **Q4 as imputed** (formula or linked value) in the Q4 columns—**no**
extra line item for "Q4 (derived)". **Annual** row stays separate when it is a
different series. **Bold** imputed cells only if it helps; **Notes** documents
imputation.
- **Links embed in cell values** — `[$248.0M](exact-deepkpi-url)`; no separate Source
column.
- **Notes** is the **bottom row** of the block; populate only in columns where
something non-standard occurred (derivation, anomaly, re-weighting, etc.).
Show **all work**: operands (FY, Q1–Q3) visible with links; Q4 appears **in the
quarterly row**. Ratios block still shows per-year math. Per the `implied-metric` skill.
**Ratios** and **Projections** blocks can use compact tables; still hyperlink any
cell that is a direct deepKPI-reported value.
If the reply includes these **data** tables and the user did **not** ask for CSV /
Excel / `.xlsx` in the same turn, end with the **bold** workbook offer per the
`kpi` skill (**Mandatory: Excel offer after in-chat data pull**).
Three blocks in sequence: **Actuals → Ratios → Projections**.
```
## Seasonality — [Metric] ([Ticker])
Units: $M unless noted
ACTUALS
| FY22-Q1 | FY22-Q2 | FY22-Q3 | FY22-Q4 | FY23-Q1 | FY23-Q2 | FY23-Q3 | FY23-Q4 | FY24-Q1 | FY24-Q2 | FY24-Q3 | FY24-Q4 |
-------------------|---------------|---------------|---------------|------------|---------------|---------------|---------------|------------|---------------|---------------|---------------|------------|
Revenue (quarterly)| [$248](url) | [$301](url) | [$292](url) | **$355** (FY−Q1−Q2−Q3) | [$267](url) | [$325](url) | [$297](url) | **$495** (same) | [$248](url) | [$301](url) | [$292](url) | **$715** (same) |
Revenue (annual) | | | |[$1,196](url)| | | |[$1,384](url)| | | |[$1,556](url)|
Notes | | | | imputed Q4 | | | | imputed Q4 | | | | imputed Q4 |
SEASONAL RATIOS
| FY2022 | FY2023 | FY2024 | 3-yr avg |
-----|--------|--------|--------|----------|
Q1 | 20.7% | 19.3% | 15.9% | 18.6% |
Q2 | 25.1% | 23.5% | 19.3% | 22.6% |
Q3 | 24.4% | 21.5% | 18.8% | 21.6% |
Q4 | 29.7% | 35.8% | 45.9% | 37.1% |
Sum | 100% | 100% | 100% | 100% |
Notes| Q4 from imputed quarter / FY | (same) | (same) | |
PROJECTIONS (FY20XXE = $X)
| FY2XE-Q1 | FY2XE-Q2 | FY2XE-Q3 | FY2XE-Q4 |
------------------|----------|----------|----------|----------|
Projected ($M) | | | | |
Notes | | | | |
```
The Notes row must be populated for: derived Q4 values, excluded anomaly years,
re-weighted in-progress quarter projections, stock items used as Q4 directly, or
any year where the ratio was estimated rather than computed from deepKPI data.
## Exporting to Excel or CSV
Use `../_common/revelata-excel-styling-rules.md` for all spreadsheet layout,
styling, numeric date headers, freeze panes, column grouping, hyperlinks on
**value cells** (green, no underline — **no** separate Sources sheet/rows for
filings), number formats, and the pre-delivery checklist. Implement with the
**xlsx** skill.
**Seasonality model addenda (on top of that spec):**
- **Three ALL CAPS section blocks** in document order: **ACTUALS**, **SEASONAL
RATIOS**, **PROJECTIONS**. Row semantics match the markdown template earlier in
this skill; **period columns** follow `../_common/revelata-excel-styling-rules.md`
(**annual block**, blank separator, **quarterly block** — not quarterlies-left as
in legacy examples).
- **Every ratio and every projected quarter** is a live `=` formula. Do not
hardcode seasonal ratios or projection numbers. Examples:
- **Q4 actual (in the quarterly metric row, Q4 column):**
`=FY_cell - Q1_cell - Q2_cell - Q3_cell` — **not** a separate "Q4 derived" row
- **Per-year Q ratio:** `=Q_cell / FY_cell`
- **Average ratio:** `=AVERAGE(ratio_year1, ratio_year2, …)`
- **Projected quarter:** `=projected_FY_cell * avg_ratio_cell`
- **In-progress re-weighting:** `=Q3_ratio_cell / (Q3_ratio_cell + Q4_ratio_cell)`
- The model must **recalculate** when FY projection assumptions change.
- **CSV:** Cannot store live formulas or real hyperlinks. Add `provenance_url` and
`formula` (raw strings) as needed; warn the user and recommend `.xlsx`.
Run the pre-delivery checklist in `../_common/revelata-excel-styling-rules.md` before
sharing the file; add seasonality-specific checks: all ratios/projections are
formulas; Notes row populated per this skill's rules where applicable.
## Caveats to flag in Notes column
- **Imputed Q4**: flag in Notes every year where Q4 = FY − Q1 − Q2 − Q3 (not directly reported), even though the value sits **in the quarterly row**
- **Anomaly year excluded**: identify the year, the distortion, and that it was excluded
- **Shifting pattern**: if the quarterly mix is trending over time, note the direction
- **Non-December fiscal year**: match Q1–Q4 labels to the company's fiscal calendar
(e.g., AAPL ends September, NVDA ends January)
- **In-progress re-weighting**: note which quarters are actuals vs. projected
- **Geographic/segment splits**: same technique applies; note the series used
tearsheet8.35 KB
View saved version →
---
name: tearsheet
description: >
Generate a one-pager company summary, including a snapshot of key financials and
operational KPIs, useful for meeting or earnings prep. Triggers only when the user
explicitly asks for a "tearsheet". Do not trigger on broad questions such as
"What does company X do?" because that is handled by the `company-summary` skill.
---
# tearsheet
Generate a short, concise company tearsheet that an analyst would typically look up before an earnings call.
Since this tearsheet is used for the quarterly earnings call, you should only output quarterly KPIs, not annual.
## Data source
See `../_common/data-source-rules.md`. Deep KPI data flows through the standard
`query_company_id` / `list_kpis` / `search_kpis` workflow used by the `kpi` skill.
## Hard stop if deepKPI access fails
See `../_common/connection-failure.md`. STOP and ask before falling back.
## Workflow
Follow these steps:
### 1. Company Summary
Use the output from `company-summary` skill to generate a concise summary of the company. It should be 3-5 bullets.
### 2. Key Financials
Use the `kpi` skill to pull the following key financial KPIs. Pull the last 8 quarters to enable YoY calculations. Do NOT pull in annual numbers, except to use to calculate Q4 numbers where necessary.
- Revenue
- Gross Profit
- Operating Income
- EBITDA (if not reported, compute as Operating Income + D&A using the `implied-metric` skill and call it "EBITDA (derived)")
- Net Income
- Diluted EPS
- Operating Cash Flow
- CapEx (PPE purchases)
- Free Cash Flow (compute as Operating Cash Flow - CapEx using the `implied-metric` skill. Call it "FCF (derived)")
### 3. Operational KPIs
Find the top 3-5 KPIs that are consequential **revenue drivers**, i.e., operational numbers that move revenue and earnings. NEVER include financial statement items (D&A, share count, buybacks, dividends) in this section.
Pull the last 8 quarters to enable YoY calculations. Do NOT pull in annual numbers, except to use to calculate Q4 numbers where necessary.
First, think about what the most important KPIs are for THIS specific company based on its business model and segments, and what drives its valuation. For example:
- **SaaS/cloud**: ARR, net revenue retention, RPO/cRPO, major customers, cloud gross margin
- **Consumer tech**: DAU/MAU, ARPU, engagement metrics, installed base, paid subscribers
- **E-commerce/marketplace**: GMV, take rate, active buyers/sellers, order frequency
- **Retail**: same-store sales, store count, average ticket, transactions
- **Telecom/media**: subscribers, churn, ARPU, content spend
- **Hardware**: units shipped, ASP, attach rate, installed base, products vs services gross margin split
- **Financial services**: AUM, NIM, loan growth, credit quality metrics
- **Pharma/biotech**: pipeline stage, patient starts, scripts, market share
- **Industrials/energy**: backlog, book-to-bill, utilization, production volumes
Then, search for those specific KPIs using the `kpi` skill. Also search for:
- Segment/product revenue breakdown
- Geographic revenue breakdown
If there are very few operational KPIs found, say that the company does not report granular operational KPIs.
### 3b. Capital Return
Pull share count, share repurchases, and dividends paid for the same periods. This is a separate section from operating KPIs — it shows how the company is returning cash to shareholders.
<!-- ## 4. Compute Key Ratios
Using the `implied-metric` skill where appropriate, show trend over the last 4 quarters with YoY change for EACH quarter (not just the earliest):
- Gross Margin %
- Operating Margin %
- EBITDA Margin %
- Net Margin %
- Revenue Growth (YoY)
- EPS Growth (YoY)
If the company has strong seasonality (e.g., retail Q4, back-to-school, etc.), add a brief note flagging it so YoY comparisons are read in context rather than sequential QoQ.
Only attempt to calculate a ratio if the inputs are available from **deepKPI**. If ANY input is unavailable for a particular ratio calculation, DO NOT calculate and instead return "incalculable". -->
### 4. Recent Developments
Use the `filing` skill to look up the most recent 2 quarters of filings. Try multiple keyword searches to get coverage:
- First search: company name + "results" or "record" for earnings highlights
- Second search: "outlook" or "guidance" or "expect" for forward-looking commentary
- Third search: strategy-specific terms relevant to the company (e.g., "AI", "cloud", "subscribers", "margin")
- If a search returns empty, try broader single-keyword searches before giving up
Extract:
- Key recent developments or announcements
- Management's top priorities or strategic focus areas
- Any notable management quotes, quoted verbatim with document citations
Keep this brief — 3-5 bullet points max.
### 5. Five Key Tensions
Identify the 5 most critical bull/bear debates for this stock. Each tension is a single line that frames both sides. Alternate between bullish-leaning and bearish-leaning tensions. Every tension must reference a specific data point from the analysis above.
Format as a numbered list:
1. "[Bullish factor] vs [Bearish factor]" — cite the specific metric
2. "[Bearish factor] vs [Bullish factor]" — cite the specific metric
...etc.
This goes at the top of the report, right after the Company Overview — it gives the reader the bull/bear framing before they dive into the data.
### 6. News Snapshot
When web search is available, run 2 searches to gather recent context and cite the sources:
1. `"{TICKER} {company_name} news {current_year}"` — recent headlines
2. `"{TICKER} catalysts risks {current_year}"` — forward-looking events
Distill into **3-5 key events** from the last 6 months, reverse chronological. Each event: date, one-line headline, sentiment tag (Positive / Negative / Mixed / Upcoming). Keep it tight — this is a tearsheet, not a research note.
### 7. What to Watch
Build a **Quantitative Monitors** list — 5 metrics with explicit thresholds:
- Format: "Metric: current value → bull threshold / bear threshold"
- Example: "Gross Margin: 45.2% → above 46% confirms pricing power / below 43% signals cost pressure"
Choose the 5 metrics that matter most for THIS company's thesis based on the data you pulled above. These should be actionable — an analyst should be able to check these next quarter and know whether the thesis is intact.
### 8. Save Report
Save to `reports/{TICKER}_tearsheet.html` using the template below. Write the full analysis as styled HTML with the design system CSS inlined. This is the final deliverable — no intermediate markdown step needed.
Structure the report with these sections:
```
<h1>{Company Name} ({TICKER}) — Tearsheet</h1>
<p>Generated: {date}</p>
<h2>Company Overview</h2>
{2-3 sentence description from filings}
<h2>Five Key Tensions</h2>
{numbered list of 5 bull/bear debates with data citations}
<h2>Key Financials (Last 4 Quarters)</h2>
<table>
| Metric | Q(oldest) | Q | Q | Q(latest) |
{table with deepKPI provenance links; derived metrics marked (calc.)}
</table>
<h2>Segment / Geographic Breakdown</h2>
{segment revenue table or geographic revenue table, whichever is more relevant}
<h2>Key Operating KPIs</h2>
<table>
| KPI | Q(oldest) | Q | Q | Q(latest) |
{table with deepKPI provenance links — ONLY business-driver metrics, NOT financial items}
{if few KPIs available, note the disclosure gap}
</table>
<h2>Capital Return</h2>
<table>
| Metric | Q(oldest) | Q | Q | Q(latest) |
{share count, buybacks, dividends — separate from operating KPIs}
</table>
<h2>Margins & Growth</h2>
<table>
| Metric | Q(oldest) | Q | Q | Q(latest) |
| Gross Margin % | X% | X% | X% | X% |
| ... | ... | ... | ... | ... |
| Rev Growth YoY | X% | X% | X% | X% |
| EPS Growth YoY | X% | X% | X% | X% |
{each cell shows the YoY change for THAT quarter}
{note on seasonality if applicable}
</table>
<h2>Recent Developments</h2>
<ul>{bullet points from filings with document citations}</ul>
<h2>News Snapshot</h2>
{3-5 recent events with date, headline, sentiment tag}
<h2>What to Watch</h2>
{5 quantitative monitors with current value and bull/bear thresholds}
```
Tell the user where the HTML report was saved.
Give a 2-3 sentence summary of the company's current state, including an honest assessment: What is the single biggest risk or concern? Does the current valuation (price, implied multiples) seem warranted given the growth trajectory? What would make you cautious about owning this stock?