Stage 2 replaces structured contacts with human text. Intake converts one contact into one typed report. The dispatcher reads this report.

From raw contacts to structured reports

The design

Intake is not an agent loop. Intake makes one model call for each contact.

flowchart LR
    T["one contact"] --> L["intake model"] --> R["typed report"] --> V["code validation"]

The model interprets the text. Code validates the output. Code also copies facts that are already in the contact envelope.

What is provided

The starter provides:

  • the model-call code in extract_report;
  • the parser for model output;
  • the report schema;
  • the zero-token path for structured reports; and
  • the accepted fixtures from the subagents and evals activity, if your team merged them.

What Claude implements

Claude changes only these parts:

  1. Claude completes prompts/intake_system.md.
  2. Claude extends _finalize in intake.py.
  3. Claude enables _finalize for the human-text path.
  4. Claude adds a small fixture grader.
  5. Claude adds focused human-text scenarios.

The finalizer must:

  • copy contact_id from the contact;
  • copy reported_location from the contact;
  • allow only fire, unknown, or none as incident_type;
  • use an empty string when notes are absent; and
  • set severity and headcount to null for a none report.

What you decide and check

You approve the extraction rules. Review examples before Claude writes the prompt.

Check these distinctions:

  • A stated fire or stated flames means fire.
  • An explicit denial or a clear non-fire event means none.
  • Smoke, smell, or an alarm without proof of fire means unknown.

Review the prompt as carefully as code. Ask Claude to use clear decision rules. Do not use the sealed instructor labels during prompt development.

What you do not build

Do not add:

  • tools to intake;
  • memory between contacts;
  • incident matching; or
  • dispatch rules that hide intake errors.

Prompt Claude

Paste this prompt into Claude Code:

Complete Stage 2 intake as one text-to-JSON model call.

Read intake.py, the report schema, prompts/intake_system.md, features/intake.feature, and a representative sample of datasets/contacts_unlabeled.jsonl. Read the accepted fixtures from the subagents and evals activity if they are present. Do not read the sealed instructor labels.

Before you edit, show me three difficult contacts. For each contact, propose values for incident_type, severity, headcount, and event time. Explain the rule for each value.

Create a short decision table for the extraction prompt. Separate stated fire, explicit non-fire, and uncertain evidence. Show me the table before you edit.

After I approve the rules, implement only these items:

  • Complete prompts/intake_system.md.
  • Extend _finalize in intake.py.
  • Enable _finalize for human text.
  • Add a small grader for our accepted fixtures.
  • Add scenarios for a stated fire, a denial, uncertain smoke, and one difficult sentence.

Keep intake stateless. Do not give intake tools. Keep the zero-token path for structured reports. Copy envelope facts in code. Validate model output in code.

Run the intake scenarios. If parsing fails, show me the raw model output before you edit the parser or prompt.

At the end, show one contact, the raw model output, and the finalized report. Mark every field that code changed.

Check the result

Run:

uv run behave features/intake.feature
uv run hadr-runner stage2-language@londone

Run the fixture grader that Claude added. Report results for each field. Do not report only one total score.

Compare the live result with Stage 1. The dispatch code did not change. Trace a new failure from the contact text to the finalized report before you change the dispatch prompt.

The intake review prompt gives a more detailed boundary check.

Completion criteria

  • Human text produces a valid typed report.
  • Code enforces envelope facts and schema rules.
  • The prompt contains explicit extraction rules.
  • You have per-field evaluation results.