# Example: a three-interview corpus

`examples/interview-01/` shows one transcript taken through every layer.
This folder shows what has to exist *between* transcripts for the same
structure to work at 100 interviews: chat, longitudinal comparison, and
qualitative research with a bigger picture.

Three synthetic interviews:

| | Date | Who | Why it is in the corpus |
|---|---|---|---|
| `tx_001` | 2026-09-14 | person_A, ops reporting lead, org_A | The interview from `interview-01`, unchanged. |
| `tx_002` | 2026-09-22 | person_B, finance analyst, org_B | An independent organisation that **contradicts** several tx_001-derived hypotheses: automation succeeded, no interest in the proposed tool, different cause of the same problem, a different bottleneck (reconciliation). |
| `tx_003` | 2026-12-10 | person_A again | A **follow-up** three months later: metrics moved, a guess got confirmed, a secondhand figure was replaced by a firsthand one, a proposed date slipped, a team member left. |

Open `viewer.html`. It starts in **CORPUS** view. Any `ev_` id anywhere jumps into **TRANSCRIPT** view of the interview that contains it.

**First time?** Hover anything with a dotted underline, any tag, any verdict, or any id. A plain-language explanation appears — what the thing is and why it exists, not how it is implemented. The explanations live in one `HELP` dictionary near the top of the script in `viewer.html`; edit the wording there.

```bash
python examples/corpus/validate.py
```

```bash
python examples/corpus/build_viewer.py
```

## The per-transcript layer (unchanged)

`transcripts/<tx>.json` and `extractions/<tx>/` — same shape as `interview-01`.
Unit ids are globally unique (`ev_0xx`, `ev_1xx`, `ev_2xx`) so anything in the
corpus can cite anything. Only tx_001 has a `coverage_audit.json`: the manifest
records the audit as `full` / `none` per transcript, because the audit is a
sampled quality check, not a per-transcript requirement.

## The corpus layer (new)

| File | What it adds | Why you need it at N=100 |
|---|---|---|
| `corpus.json` | Manifest: date, participants as entity ids, guide version, **segments with evidence pointers**, extraction provenance (model, prompt version, audit level), saturation series. | Filtering and cross-tabs ("ops leads at orgs with no scripting capacity"). Knowing which extractor version produced what. Knowing when to stop interviewing. |
| `entities.json` | Persistent people, organisations, and **concepts** that collect per-transcript referents. Attributes with `as_of` dates. | Without this, `spk_1` in tx_003 is a stranger and "the consolidation process" is a string match. |
| `codebook.json` + `codings.json` | A controlled vocabulary with definitions and inclusion/exclusion rules, applied to units in a separate pass. Each coding carries **salience** (volunteered / prompted / pushback). | This is what makes counts legitimate. Re-code without re-extracting when the codebook changes. Unprompted mentions weigh more. |
| `hypotheses.json` | The board. One verdict per interview: supports / weak_support / qualifies / contradicts / **not_probed**. Tally, status, status history. | The customer-discovery bigger picture is a tally with a denominator, split by segment. `not_probed` stops silence being read as a "no". |
| `cross_relations.json` | Edges between units in different transcripts: `updates` (same source, later), `resolves`, `corroborates` (independent), `contrasts_with`, `absent_in`. Each marked `independent` or not. | The same person saying something twice is not corroboration. Different orgs giving different causes is variation, not contradiction. |
| `observations.json` | Series: one entity, one metric, points over time, each with `observed_at`, `refers_to`, evidence id, and the **modality** of that evidence. | Longitudinal reads. Shows a secondhand estimate being replaced by a firsthand one, a guess becoming a report. |
| `followups.json` | Backlog of open questions per participant, closed with the evidence that answered them. Items open across several interviews are their own finding. | The next interview's guide comes from here. |
| `interview_guide.json` | Versioned guide; probes point at guide question ids; each version records *why* it changed. | Which interviews were asked what. Which questions never get good answers. Findings feeding back into method. |
| `chat_sessions.json` | Question → retrieval (filters, unit ids) → answer that cites units, counts **independent** sources, lists what was never asked, labels weak modality → persisted as a learning. | This is "chat with my interviews". The answer contract is what makes it trustworthy. |
| `learnings.json` | tx_001's five learnings plus the three chat answers as `kind: chat_answer` with multi-transcript scope and `corpus_size_when_answered`. | An answer given at n=3 is a different object from one at n=100; a re-ask can diff. |

## Things to look at in the viewer

- **H2** (would people pay for remapping). Verdicts: weak support → contradicts → supports. Status: *segment-dependent*, with the segment named. Click the `002 contradicts` cell.
- **H5** (reconciliation). Emergent from tx_002. Two `not_probed` verdicts and a guide change in v3 as the action item.
- **obs_04** (security review weeks). 6 → 9, but the modality column says the real story: secondhand → firsthand, and two different reviews.
- **chat_02** (what changed for person A). One source, eight changes, three absences listed. Click any cited id in the answer.
- **fu_007** (the "worse parts of the week"). Open across two interviews. Never asked. That is a finding about the interviewing, not the interviewee.
- **person_priya**. An entity whose status changed, and whose departure flips another unit's `source_of_knowledge`.
- Open **tx_003** in transcript view: every unit shows its cross-transcript relations in purple, its codes, and — in the detail pane — which hypothesis verdicts and chat answers cite it.

## What this does not settle

- Entity resolution is hand-done here. At scale it is its own model pass with its own error rate, and merges need to be reversible.
- The codebook was built by reading three interviews. Real codebooks are iterated with inter-coder checks; the model-as-coder needs the same discipline (code twice, measure agreement).
- Hypothesis `status` is a judgement. Who gets to change it, and whether the model may propose changes, is a workflow question.
- The chat answer contract is stated, not enforced. `validate.py` checks that cited units were retrieved and that the source count is not inflated; it cannot check that the prose is faithful.
- Retrieval itself (embeddings vs. code/segment filters vs. both) is not modelled — the sessions just list what was retrieved.
