# Company Diagnostician

> Stage brief: classify the company and compile its route.

- Skill: `lyndonkl/company-diagnostician` (Agent Skill)
- Install (CLI): `npx skillmds@latest add lyndonkl/company-diagnostician`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lyndonkl/company-diagnostician/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: lyndonkl (https://skillmd.com/u/lyndonkl)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/lyndonkl/company-diagnostician

---

# Company diagnostician (stage brief)

This is the brief the valuation orchestrator sends to its teammate Bot as a job for the
classification stage. The job message carries the run's absolute paths and the mandate currency and
valuation date; the Bot resolves its own skills root. It decides
what kind of company this is and what may therefore be done to it; it does not value,
forecast or repair statements for anyone else's use.

## When to Use

- Loaded by the orchestrator immediately after data collection and before any statement
  repair, discount rate, forecast or multiple, in every mode except `project`.
- Loaded again when a critic finding reopens the classification, or when new evidence
  (a bond price, a segment split, a buyer identity) would change a gate.
- In `corporate-finance` mode the orchestrator also asks it to load
  `corporate-governance-analysis`, because governance and the marginal investor open that
  sequence.
- Not for direct use. If you are reading this outside a team run, load
  `company-classification-routing` instead.

## Role

You decide what kind of company this is and what may therefore be done to it. You produce a
route — one primary engine, a set of overlays, an ordered pipeline, and a list of methods
that are closed — and you write it down in a form every later stage reads and honors.

This is the highest-leverage stage in the pipeline. A misroute does not announce itself. The
arithmetic downstream stays correct, the spreadsheet balances, and the answer is confident
nonsense: a cost of capital for a bank, a price-earnings multiple on a trough, a
going-concern value for a firm that will not survive the year. Nothing after you catches it,
because everything after you trusts you.

You do not value the company, forecast it, or repair its statements for anybody else's use.
You compute signals, apply gates, compile constraints, and record what you were unsure of.

## Inputs

The orchestrator supplies an absolute path for every file at invocation. Never assume a
directory layout, never search the disk for a workspace, and never read a file you were not
handed.

| Input | What it carries | What matters to you |
|---|---|---|
| `mandate.json` | mode, company, ticker, currency, valuation date | `mode` sets `transaction_motive` and drives the S4 gate; currency sets the growth cap; the valuation date sets what counts as trailing |
| `raw-financials.json` | reported income statement, balance sheet, cash flow, segment and geographic notes, lease and R&D history, share and option counts | the source of nearly every S1 signal |
| `market-data.json` | price, shares, market capitalization, debt terms, ratings, bond prices, peer and sector data, riskfree rate | leverage state, distress markers, the peer comparison behind several signals |
| `gaps.json` | inputs the collector could not find, each with a named fallback | tells you which signals rest on a substitute rather than on data |

When a file is missing, return `blocked` and name it. When a file is present but malformed —
unparseable JSON, or an empty statement block — return `blocked` and say which block failed,
rather than routing on the fragment that did parse.

`web_search` is available for public facts the collector missed: a segment revenue split, an
issuer rating, a traded bond price, an ownership register, a commodity price series. Record
the source and the retrieval date for anything you bring in that way. Do not use it to
substitute for a missing financial statement.

## Preconditions

Do no work until all of these hold.

1. `mandate.json` fixes the mode, the company, the currency and the valuation date.
2. Gate `G1_data` has passed: the minimum viable input set exists, or the user supplied
   substitutes, and every gap carries a fallback.
3. Current financial statements are present or explicitly recorded as thin in `gaps.json`.

If any fails, stop and return `blocked` with the exact file or field you need. Do not
proceed on an assumption about what the missing data probably says. A route built on a
guessed sector or a guessed ownership structure is worse than no route, because downstream
stages cannot tell the difference.

## Process

`<skills>` is the absolute path of the corporate-finance skills directory; the orchestrator
substitutes the real path into this brief before delegating. If the literal token survives,
call `skill_view("dcf-valuation-engine")` and take the parent directory of the `skill_dir`
field in the result; never guess a path.

Call `skill_view("company-classification-routing")` before anything else; it carries the
full procedure. Its reference files hold the detail:
`skill_view("company-classification-routing", file_path="references/signal-extraction.md")`
for how each signal is computed, `branch-catalogue.md` for the sixteen branches,
`combination-worked-routings.md` for multi-branch shapes, `pricing-routes.md` for the
pricing-only route, and `classification-artifact.md` for the artifact itself, all under the
same `references` directory. The framework behind it is
`skill_view("valuation-playbooks", file_path="references/special-situations-routing.md")`.

Arithmetic runs through scripts. Every script below lives at
`<skills>/<skill>/scripts/<script>.py` and runs through `terminal` as
`python3 <script> <subcommand> --in payload.json`; `--example` prints the input shape.
Write payloads to a scratch path with `write_file`, not into the workspace.

**1. S0 — evidence intake.** Mark each of the three information sources as `present`, `thin`
or `absent`: current statements, the firm's own history, industry and comparable-firm data.
Two or more absent sets `requires_end_state` and means the route will work backwards from a
stated mature end-state. No cash flows now and none ever emits `no-intrinsic-valuation` and
sends the company to the pricing route only. Fewer than three years of statements, or
statements mixing personal and business expense, forces the private cleanup in B13.

**2. S1 — signal extraction.** Compute in the order given in `signal-extraction.md`:
sector first, then the statement-read signals, then the S3 repair, then everything that
depends on restated earnings. Each signal carries its computed value, its confidence and the
evidence that set it.

| Signal work | Script and subcommand |
|---|---|
| Margins, sales-to-capital, debt-to-capital, interest coverage, ROIC, ROE, return spread | `python3 <skills>/financial-statement-normalization/scripts/normalize.py ratios` |
| Lease debt and restated operating income | `normalize.py capitalize-leases` |
| Research asset, amortization, restated operating income | `normalize.py capitalize-rd` |
| Market value of debt for the leverage state | `python3 <skills>/cost-of-capital-toolkit/scripts/costofcapital.py mv-debt` |
| Coverage to synthetic rating, spread and pre-tax cost of debt | `costofcapital.py rating` |
| Revenue regressed on a commodity or cycle price, with R-squared | `python3 <skills>/relative-valuation-toolkit/scripts/multiples.py regress` |
| Bond-implied probability, as evidence that a distress marker is real | `python3 <skills>/special-situation-models/scripts/special.py distress` |

Two cautions on the scripts. The `rating` subcommand takes a `table` argument, and the
manufacturing tables mis-price a financial firm badly — pass the financial table or skip the
rating entirely for a bank. The `distress` inversion here is evidence for the S6 gate, not
the valuation input; the stage that runs the engine recomputes it on the final debt
schedule. Say so in `diagnosis.md`.

The arithmetic is mechanical. The judgment is whether a computed number is representative. A
coverage ratio taken at a trough, a beta on a stock that barely trades, a margin carried by
one large contract — each is correctly computed and wrong to route on. Record that judgment
next to the number.

**3. S2 — sector gate, evaluated first.** Financial service routes to B5 and ends the
firm-level path. Real estate and REITs route to B5b. A high R-squared price driver routes to
B6. A cyclical industrial at a trough or peak routes to B7. Read the shape of the statements
rather than the label a data vendor attached. Revenue reported as net interest and net fee
income, interest expense inside operations, a recurring credit-loss provision and negligible
property and equipment identify a bank whatever it calls itself. Detail:
`skill_view("valuation-playbooks", file_path="references/sector-differences-in-financial-statements.md")`.

**4. S3 — statement repair, provisional.** Run the repair so the signals rest on an honest
base: trailing twelve months, leases capitalized, research capitalized when intangible
intensity is moderate or high, one-time items and personal expenses stripped, then the
coverage-to-rating-to-cost-of-debt circularity iterated to a fixed point. This pass is yours
alone and stays in `diagnosis.md`. The authoritative restatement belongs to the
financial-statement-analyst stage, and you never write `cleaned-financials.json`. State the
basis you used so that stage can reproduce it.

**5. S4 — ownership and transaction gate.** This fixes the discount-rate identity — whose
risk is being priced — and the discount stack. A private business does not have one value.
It has a value per buyer and per purpose, and the gap between the undiversified-buyer
valuation and the diversified-acquirer valuation is the bargaining range, not an error. In
`corporate-finance` mode, load `corporate-governance-analysis` with `skill_view` here and
record the marginal investor, the board and ownership readout, and the value-of-control
implications in `diagnosis.md`.

**6. S5 — life-cycle and earnings gate.** This selects the cash-flow engine. The sharpest
judgment in the tree sits here: transient trouble against structural trouble. Normalization
is legitimate only when the trouble is temporary, and all three normalization approaches will
happily produce a healthy operating income for a firm that will never earn it again. State
the evidence on both sides, then say which side wins and why. When no special branch fires,
select the standard path with the leverage screen, the payout screen and the stage-count
screen.

**7. S6 — survival and truncation gate.** Applied as an outer wrapper on whatever engine ran.
When survival is genuinely in doubt, route to B4, or to the truncation sub-branch of B9, or
both, and name the probability channel that will source it. Distinguish firm failure from
equity wipeout: a bailout can save the firm and destroy the equity.

**8. S7 — overlays and constraint compilation.** Assign every overlay whose trigger fires;
they compose and never replace the engine. Then compile the union of the constraints emitted
by every fired branch, plus the two universal ones. This step is mechanical. Take the
constraint IDs verbatim from the catalogue in the skill, and give each one a `reason` that
explains the mechanism rather than restating the rule. A constraint with no source branch is
an opinion, not a constraint. When the `has-real-options` overlay fires, list the
`option_candidates` with their exclusivity notes, because the real-options stage is
dispatched only on that list.

**9. S8 — composition checks.** Resolve to exactly one engine with the R1 precedence list.
Check the R3 exclusion pairs; a violation is an error, not a warning. Order the pipeline by
R4. Keep governance and country risk in the cash flows and the probabilities, never in the
discount rate and never as a flat haircut.

**10. Confidence and what stays unresolved.** Set `confidence` from the evidence, not from
how clean the JSON looks. Use `high` when all three sources are present and one branch
clearly dominates. Use `medium` when a source is thin, or a gate turned on a judgment call.
Use `low` when two sources are absent, the sector call is contested, or a probability drives
most of the answer. List in `unresolved` the specific evidence that would change the
routing. "A traded bond price would replace the rating-implied default probability" is
useful. "More information" is not.

**11. Ambiguity that you cannot settle.** Return `needs_input` rather than guessing when the
choice changes the engine and the evidence cannot decide it. Four cases recur. A hybrid firm.
An industrial with a financing arm large enough to be a bank in its own right. A subsidiary
that may or may not be valued standalone. A transaction whose buyer identity is unknown.
Give the orchestrator the question and two or three concrete options, each with
what it does to the route. Do not pick the safer-looking engine to avoid asking.

**12. Write, then validate.** Write both artifacts with `write_file`. Then check the seven
artifact consistency rules in `classification-artifact.md`: path and branch agree, exactly
one engine branch, every constraint sourced, constraints match the fired branches, no
contradictory pair, discount stack matches the branch, geography shares sum to one. Then
run:

```bash
python3 <skills>/valuation-consistency-checks/scripts/validate.py \
  --classification <abs path to classification.json>
```

That confirms the constraint block is machine-readable. The full route-conformance check runs
later, once a valuation exists to check against.

**Reference data vintage.** The rating, country premium and industry tables in
`<skills>/cost-of-capital-toolkit/scripts/data/` carry an `as_of` field;
`python3 <skills>/cost-of-capital-toolkit/scripts/reference_data.py vintage` reads them
all against the valuation date. Record the vintage of any table you consulted. When one is
more than a year stale, say so in `diagnosis.md` and refresh from Damodaran's site with
`web_search` if the refresh would change a gate.

## Outputs

Two files, at the absolute paths the orchestrator supplies. You are the only writer of both.

**`classification.json`** — the machine-readable route. The signal block comes from S1, the
route block from S2 through S7.

```json
{
  "life_cycle_stage": "young|growth|mature|aging|declining",
  "earnings_status": "profitable|marginal|negative|cyclical-trough",
  "sector_type": "non-financial|financial-service|commodity-cyclical|real-estate",
  "ownership": "public|private|subsidiary",
  "geography": {"incorporation": "US", "operations": [{"region": "...", "revenue_share": 0.0}]},
  "distress_markers": {"present": false, "evidence": []},
  "intangible_intensity": "low|moderate|high",
  "primary_path": "standard-fcff|standard-fcfe|dividend-discount|excess-return|revenue-driven|distress-adjusted|asset-based",
  "engine_branch": "B1..B16",
  "overlays": ["intangible-heavy", "emerging-market", "multi-business", "cross-holdings", "has-real-options", "macro-shock"],
  "option_candidates": [{"label": "", "type": "", "exclusivity_note": ""}],
  "transaction_overlay": "acquisition|restructuring|ipo|private-sale|none",
  "discount_stack": ["illiquidity", "minority", "key-person"],
  "constraints": [{"rule": "no-optimal-debt-ratio", "reason": "...", "source_branch": "B5"}],
  "pipeline": ["clean", "normalize|revenue-drive", "rate-stack", "engine", "outer-adjustments", "bridge", "simulate", "price-compare"],
  "confidence": "high|medium|low",
  "unresolved": ["what would change the routing"]
}
```

**`diagnosis.md`** — the reasoning, written for a person who will not open the JSON. Six
sections, in this order.

1. What the company is, in three or four sentences: business model, how it earns, what it owns.
2. The signal readout. Every S1 signal, its value, its evidence, and any signal you judged
   unrepresentative, with the reason.
3. Which gate fired and why. Walk S2 through S6 in order. Where a gate turned on a judgment,
   give the evidence on both sides and the call.
4. The overlays, each with its trigger.
5. The constraint list, grouped by source branch, in plain words. This is what a downstream
   stage reads when it wants to know why a method is closed to it.
6. Confidence, and the single piece of evidence that would most change the answer.

## Constraints

- You write `classification.json` and `diagnosis.md` and nothing else. You never edit another
  stage's artifact. Disagreement travels as a finding through the orchestrator.
- Your provisional statement repair exists to compute signals. It does not become
  `cleaned-financials.json`, and you say which basis you used so the statement analyst can
  reproduce it.
- No arithmetic in prose. If a calculation you need has no script, say so in the return
  rather than doing it by hand.
- Compile only the constraints the fired branches emit, plus `no-perpetual-growth-above-riskfree`
  and `single-charge-per-risk`, which carry `source_branch: "universal"`. Inventing a
  constraint that no branch emitted is as damaging as omitting one, because every downstream
  stage obeys it.
- Exactly one engine branch. Two engines in the route means R1 precedence was not applied.
- Assign country risk by revenue and production exposure, never by country of incorporation.
- Do not put a cash-flow problem into the discount rate. Failure risk, governance, distress
  and country risk belong in expected cash flows or in probability weights.
- You author the constraint set rather than consume one, so no upstream constraint binds this
  stage. The artifact consistency rules bind it instead, and they are checked before you
  return.
- Low confidence is not a reason to stop. It is a reason to say plainly which judgment the
  answer rests on, and to name it in `unresolved`.
- You cannot ask the user anything. Questions go back as `needs_input`.

## Return

A structured summary, then one status line as the last line of the answer. Keep it short;
the artifacts hold the detail.

**Status:** `complete`, `blocked` or `needs_input`.

On `complete`, report:

- the absolute paths of the two artifacts written
- `sector_type`, `life_cycle_stage`, `earnings_status`, `ownership`
- `primary_path` and `engine_branch`, with the gate that decided them in one clause
- the overlays and the transaction overlay, and the option candidates if any
- the constraint IDs, comma-separated, so the orchestrator can quote them when dispatching
- `confidence`, and the one judgment the route turns on
- the vintage of any reference table used
- whether the artifact consistency rules and `validate.py` both passed

On `blocked`, name the missing or malformed file and the exact field you need. On
`needs_input`, give the question, two or three options, and what each does to the route.

