# Interview Records

Interview Records is a static site that presents public interview records in chronological order by person, including age-at-interview context.

## Repository map (strict purpose)

| Path | Type | Purpose | Source of truth |
| --- | --- | --- | --- |
| `index.html` | Runtime app file | Semantic page structure and content sections. | No |
| `styles.css` | Runtime app file | Visual system, layout, spacing, typography, and motion. | No |
| `app.js` | Runtime app file | UI behavior: fetches `data.bundle.json`, handles person selection, chronology rendering, URL state, and age calculation. | No |
| `data.bundle.json` | Runtime app file (generated) | Browser-loadable JSON dataset fetched by `app.js` at runtime. | **Generated only** |
| `favicon.svg` | Runtime app file | Site icon. | No |
| `data/people/*.json` | Data source | Canonical person records. | **Yes** |
| `data/interviews/*.json` | Data source | Canonical interview records. | **Yes** |
| `schemas/person.schema.json` | Schema | JSON Schema contract for each person file. | N/A |
| `schemas/interview.schema.json` | Schema | JSON Schema contract for each interview file. | N/A |
| `scripts/data/validate-core.mjs` | Build/validation code | Shared validation engine (schema + cross-file checks). | N/A |
| `scripts/data/validate.mjs` | Build/validation code | CLI validation entrypoint. | N/A |
| `scripts/data/build.mjs` | Build/validation code | Generates `data.bundle.json` from source JSON after successful validation. | N/A |
| `scripts/data/lib.mjs` | Build/validation code | Shared file-system/data helpers for scripts. | N/A |
| `package.json` | Tooling config | Node scripts and dependencies for validation/build. | N/A |
| `vercel.json` | Deploy config | Forces validation/build check during Vercel build. | N/A |
| `.gitignore` | Git config | Ignores local artifacts and dependencies. | N/A |

## Data source schema

### `data/people/<slug>.json`

Each file must match `schemas/person.schema.json`.

Example:

```json
{
  "slug": "jamie-dimon",
  "name": "Jamie Dimon",
  "birth_date": "1956-03-13"
}
```

Field rules:

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `slug` | string | yes | kebab-case, `^[a-z0-9]+(?:-[a-z0-9]+)*$`, 3-80 chars |
| `name` | string | yes | 2-120 chars |
| `birth_date` | string | yes | ISO date (`YYYY-MM-DD`) |
| `photo_url` | string | no | URI format, `http` or `https`, max 500 chars |

File rule:
- Filename must exactly match `slug` (example: `jamie-dimon.json` has `"slug": "jamie-dimon"`).

### `data/interviews/<id>.json`

Each file must match `schemas/interview.schema.json`.

Example:

```json
{
  "id": "jamie-aei-2018",
  "people": ["jamie-dimon"],
  "title": "A conversation with Jamie Dimon, chairman and CEO of JPMorgan Chase & Co.",
  "date": "2018-10-02",
  "format": "Print",
  "source": "AEI",
  "summary": "Transcript of a public conversation on the economy, leadership, regulation, and the state of American business.",
  "topics": ["economy", "leadership", "regulation"],
  "links": [
    {
      "label": "Read transcript",
      "url": "https://www.aei.org/wp-content/uploads/2018/09/181002-Dimon-event-transcript.pdf?x85095="
    }
  ]
}
```

Field rules:

| Field | Type | Required | Constraints |
| --- | --- | --- | --- |
| `id` | string | yes | kebab-case, `^[a-z0-9]+(?:-[a-z0-9]+)*$`, 3-120 chars |
| `people` | string[] | yes | min 1, unique slugs, every slug must exist in `data/people` |
| `title` | string | yes | 3-300 chars |
| `date` | string | yes | ISO date (`YYYY-MM-DD`) |
| `format` | string | yes | 3-40 chars |
| `source` | string | yes | 2-120 chars |
| `summary` | string | yes | 10-1000 chars |
| `topics` | string[] | yes | min 1, each 2-80 chars |
| `links` | object[] | yes | min 1 |
| `links[].label` | string | yes | 2-80 chars |
| `links[].url` | string | yes | URI format, `http` or `https` |

File rule:
- Filename must exactly match `id` (example: `jamie-aei-2018.json` has `"id": "jamie-aei-2018"`).

## Generated runtime schema

`data.bundle.json` is generated and must not be manually edited.

Runtime shape:

```json
{
  "people": [
    { "slug": "jamie-dimon", "name": "Jamie Dimon", "birth_date": "1956-03-13" }
  ],
  "interviews": [
    {
      "id": "jamie-aei-2018",
      "people": [{ "name": "Jamie Dimon", "slug": "jamie-dimon" }],
      "title": "A conversation with Jamie Dimon, chairman and CEO of JPMorgan Chase & Co.",
      "date": "2018-10-02",
      "format": "Print",
      "source": "AEI",
      "summary": "Transcript of a public conversation on the economy, leadership, regulation, and the state of American business.",
      "topics": ["economy", "leadership", "regulation"],
      "links": [{ "label": "Read transcript", "url": "https://www.aei.org/wp-content/uploads/2018/09/181002-Dimon-event-transcript.pdf?x85095=" }],
      "year": 2018
    }
  ]
}
```

Generation rules:
- `year` is derived from `date`.
- `people` in interviews are expanded from slug-only source to `{ name, slug }`.
- output is deterministic (sorted by date, then title, then id).

## Validation rules beyond JSON Schema

`scripts/data/validate-core.mjs` also enforces:
- person slug uniqueness
- interview id uniqueness
- every interview person slug exists in `data/people`
- valid ISO calendar dates
- filename/id and filename/slug exact alignment
- URL protocol check (`http` or `https`)

## Local workflow

1. Edit source files only:
   - `data/people/*.json`
   - `data/interviews/*.json`
2. Validate:
   - `npm run data:validate`
3. Regenerate runtime file:
   - `npm run data:build`
4. Run combined gate:
   - `npm run data:check`

## Deployment gating

- Vercel runs `npm run data:check` during build (`vercel.json`) before deployment.
- If validation fails, deployment is blocked.
