# Ma Deal Workbench

> Produce structured JSON deliverables (issue registers, deviation matrices, committee escalation memos, closing/transition packages) from an M&A deal workbench API against a supplied answer template. Use whenever a task gives a deal_id, a workbench base URL, and an answer_template.json to conform to.

- Skill: `prism-shadow/ma-deal-workbench-3` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add prism-shadow/ma-deal-workbench-3`
- Raw SKILL.md: https://api.skillmd.com/api/skills/prism-shadow/ma-deal-workbench-3/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: prism-shadow (https://skillmd.com/u/prism-shadow)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/prism-shadow/ma-deal-workbench-3

---


# M&A deal workbench → structured JSON deliverable

These tasks all have the same shape: a prompt names one deal, points at a read-only
M&A workbench, and requires **only JSON** conforming to an `answer_template.json`.
Output is graded by field-level exact comparison, so a defensible-but-invented value
scores the same as a wrong one. Everything you emit must be traceable to a record you
actually read, or to arithmetic on those records.

## The one rule that matters most

**Derive, don't infer.** The workbench is the whole world. Do not add issues, IDs,
amounts, or classifications from general M&A practice. If a record does not say it,
it is not in the answer.

Its corollary, **filter, don't pad**, is the single highest-value habit: a list field
named "required", "blocking", or "closing condition" holds *only* the records whose
own flag says they qualify. Listing every record and tagging each with a type is
measurably worse than listing only the qualifying ones — non-qualifying records belong
in the template's non-blocking / excluded / notice-only field, if it has one, and
nowhere otherwise.

## Workflow

### 1. Read the answer template before touching the API

The template is the contract and the strongest hint about the expected answer. Extract:

- **Required top-level keys** and their exact spelling. Emit exactly these.
- **`allowed_enums`** — every enum-typed value must be a verbatim member. Watch case:
  workbench records store `"High"`/`"yes"`, templates usually want `"HIGH"`/`true`.
- **Pinned ID lists** (`possible_issue_ids`, `stable_issue_ids`, `stable_redline_ids`,
  an `issue_id` "one of:" list). When the template pins IDs, that list *is* the
  expected membership — produce every ID, no more and no fewer. When it does not,
  include an entry only where a record evidences it.
- **Enums that only make sense nested** (`fee_model`, `tax_allocation_method`, `forum`,
  `condition_type`) — these are telling you the vocabulary for the free-form
  `*_normalized` / `must_have_terms` objects. Use those exact strings there.
- **Units** — integer dollars, percent-point decimal places, integer months. The
  prompt sometimes overrides the template; the prompt wins.
- **Named final positions.** A `final_position` / `recommended_action` enum whose values
  map 1:1 onto the issue IDs is a decoded answer key — pair them by name and let the
  wording drive the row. These names are often self-describing, encoding the target
  number, the fallback, and the condition attached to it; read them as instructions.

### 2. Pull the deal bundle

`scripts/fetch_deal.py <base_url> <deal_id> <out_dir>` fetches the deal record, follows
its `links` map, and pulls the governing playbook or policy named on the deal. It
discovers routes from the deal record, so it does not depend on a hardcoded route list.

The workbench holds dozens of decoy deals whose project names differ from the target's by
a syllable or a suffix, with cloned supporting rows carrying similar-looking amounts.
**Filter on the exact `deal_id` every time**, including in SQL. Never let a similarly
named project supply a number.

### 3. Establish the governing standard

The deal record names exactly one:

| Field on deal | Standard | Key columns |
|---|---|---|
| `playbook_id` | `playbook_rules` | `preferred_position`, `fallback_position`, `limit_value`, `limit_unit`, `basis`, `risk_default` |
| `policy_id` | `policy_thresholds` | `policy_standard`, `threshold_value`, `restricted_flag`, `approval_required`, `basis` |

Parse the numeric preferred/fallback out of the prose positions — `limit_value` alone is
ambiguous (it is sometimes the preferred bound, sometimes the fallback).

### 4. Select the rows

Use only `draft_terms` with `staleness_flag == "current"`. Then, per template shape:

- **Issue register / deviation matrix** — one row per template-pinned issue ID. Map each
  to the current draft term(s) sharing its `category`; a pinned ID with no matching
  current term is `missing_required_term` with empty `source_term_ids`.
- **Committee escalation** — include a term only if *all* hold: current, its category has
  a governing threshold, the draft breaches it, and the threshold is in scope for the
  requested body (`restricted_flag == "yes"` / `approval_required` matches). Stale,
  in-policy, and lower-approval rows are deliberate distractors — put them in the
  template's `excluded_*` fields, not in the escalation list.

`reference/classification_rules.md` has the status / risk / action decision tables and the
record-flag filters (consents, material contracts, employees).

### 5. Compute the numbers

- Base = the deal's `headline_value` unless the term or rule `basis` explicitly names
  another basis. `headline_value` also equals `upfront_cash + stock_value` in these deals;
  a draft term that states its own dollar figure confirms the base — check it.
- `amount = round(base * percent / 100)`, emitted as an **integer**.
- Deltas run in the direction that harms your client (see polarity table in
  `reference/classification_rules.md`). Report the gap to the *fallback* when the template
  asks for one gap, and both fallback and preferred gaps when it asks for two.
- Exposure ranges come from `risk_estimates` rows, matched by `category`. Count each
  category **once** in an aggregate even when several issues cite it, and list which
  categories you included/excluded if the template has fields for that.
- Every total must equal the sum of the components you actually emitted.

### 6. Validate, then emit

Run `scripts/validate_answer.py <candidate.json> <answer_template.json>`. It checks
required keys, enum membership, integer-dollar fields, percent rounding, and pinned-ID
coverage. Emit raw JSON only — no prose, no code fences, no trailing commentary.

## Pitfalls

- **Padding a required/blocking list with non-qualifying records.** The costliest
  recurring error. `required_for_closing == "no"` and `consent_required == "notice only"`
  are never blockers.
- **Inventing IDs.** Prefer stable workbench IDs (`CNS_*`, `MAT_*`, `EMP_*`, `TERM_*`,
  `RSK_*`, `FND_*`). Only synthesize one where the template explicitly permits it
  (e.g. "synthetic regulatory id").
- **Free-styling risk ratings.** Inherit `risk_default` from the governing rule, or the
  record's own `risk_rating`, before making a judgment call. Normalize to the enum's case.
- **Ignoring `client_side`.** It flips `draft_exceeds_playbook` vs `draft_below_playbook`
  and flips which party a term protects.
- **Double-counting an exposure category** across several issues in an aggregate total.
- **Trusting `limit_value`** instead of the preferred/fallback prose.
- **Wrong percent precision.** Two decimals, one decimal, and four-decimal holder
  percentages all appear; re-read the prompt for each task.
- Adding keys the template does not list is harmless; omitting listed keys is not.

