Skip to content

Commit b92209c

Browse files
ipasechnikovclaude
andcommitted
Fix check-errors skill docs + inspect-error script
Inspect output used wrong unmappedCodes field names (code/system/display); derive real fields plus mappingType from embedded Task and surface taskId. Skill's resolve curl used JSON body with code/display; endpoint expects form-urlencoded resolvedCode/resolvedDisplay. LOINC lookup endpoint /api/terminology/loinc does not exist; use ValueSet/$expand. CLAUDE.md: loosen test:local to before-commit (not after-every-change) and promote inspection scripts over raw file reads. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 84e5a37 commit b92209c

3 files changed

Lines changed: 52 additions & 17 deletions

File tree

.claude/skills/check-errors/SKILL.md

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,42 +34,72 @@ Pick the playbook below by the `Status` line from Step 2.
3434
### `parsing_error` — sender sent malformed HL7v2
3535

3636
1. From the inspect overview, identify what's wrong: missing MSH, invalid encoding chars (MSH-1/2), truncated segments, wrong line endings, embedded binary.
37-
2. **No code/config fix.** Sender must correct the message format.
38-
3. Offer to defer if it needs sender coordination: `POST http://localhost:3000/defer/<id>`.
39-
4. Only mark for retry if the sender has already fixed and will resend with the same ID.
37+
2. **No code/config fix.** Sender must correct the message format. Defer = resolved on our side — do it:
38+
```sh
39+
curl -sf -X POST 'http://localhost:3000/defer/<id>'
40+
```
41+
3. Only mark for retry if the sender has already fixed and will resend with the same ID.
4042

4143
### `conversion_error` — parsed OK, missing/invalid data for FHIR
4244

4345
1. Read the error field. Common cases:
46+
- **`HTTP 422` prefix:** Aidbox rejected the FHIR bundle — treat as an Aidbox validation failure (see sub-case below).
4447
- **PV1-19 missing/empty:** check if PID-18 (account number) can be a fallback
4548
- **PV1 required but absent:** check config for this message type
4649
- **PV1-19 authority conflict:** check CX.4/9/10 values
4750
- **PID missing:** sender issue
4851
- **Unsupported message type:** check MSH-9
52+
53+
#### Sub-case: `conversion_error` with `HTTP 422` (Aidbox FHIR validation rejection)
54+
55+
Do **not** run `hl7v2-inspect --values` unless the mapping is ambiguous. Diagnose from the OperationOutcome alone:
56+
57+
1. Parse `expression` → FHIR resource + field (e.g. `Coverage.period``per-1`).
58+
2. Look up the V2-to-FHIR IG mapping CSV in `specs/v2-to-fhir/mappings/segments/` → identify the HL7v2 source segment/field (e.g. `Coverage.period.start` ← IN1-12, `.end` ← IN1-13).
59+
3. State the fix directly from the constraint + field identity. Only run `hl7v2-inspect --values` when multiple HL7v2 fields feed the same FHIR field and the specific trigger is unclear.
60+
4961
2. Suggest fixes in this order:
50-
- Best: sender populates the missing field — explain what's needed
62+
- Best: sender populates the missing field — explain what's needed; defer = park it out of active queue pending sender action:
63+
```sh
64+
curl -sf -X POST 'http://localhost:3000/defer/<id>'
65+
```
5166
- Workaround: add a preprocessor in `src/v2-to-fhir/preprocessor-registry.ts` + `config/hl7v2-to-fhir.json`
5267
- Last resort: relax config (make a segment optional) — warn about the tradeoff
53-
3. If no clear fix (needs sender coordination, client decision, spec clarification) → offer to defer.
68+
3. If no clear fix (needs client decision, spec clarification) → defer as resolution (same command above).
5469
4. **Wait for approval before implementing.** Then verify with `bun scripts/check-message-support.ts /tmp/hl7v2-<id>.hl7` before retrying.
5570

71+
#### Adding a preprocessor (recipe)
72+
73+
Three files, in order:
74+
75+
1. **`src/v2-to-fhir/preprocessor-registry.ts`** — add key + function to `SEGMENT_PREPROCESSORS`. Function receives the whole segment; the field key in config is only a trigger guard (preprocessor runs when that field is present, except `fallback-rxa3-from-msh7` which runs even when absent).
76+
2. **`src/v2-to-fhir/config.ts`** — add the field slot to `MessageTypeConfig.preprocess.<SEG>` (e.g. `IN1?: { "12"?: SegmentPreprocessorId[] }`).
77+
3. **`config/hl7v2-to-fhir.json`** — add the entry under the relevant message type. Use the `Read` tool for this file — `bun -e` and `python3` fail on JSONC comments.
78+
5679
### `code_mapping_error` — local code has no FHIR mapping
5780
58-
Inspect output lists each unmapped code with `mappingType`.
81+
Inspect output lists each unmapped code with `localCode`, `localDisplay`, `localSystem`, `mappingType`, and `taskId`. Use the printed `taskId` for the resolve call.
5982
60-
1. For `observation-code-loinc`, search LOINC by display text:
83+
1. For `observation-code-loinc`, search LOINC via Aidbox's ValueSet/$expand (the `/api/terminology/*` path does not exist):
6184
```sh
6285
SECRET=$(awk -F': ' '/^[[:space:]]*BOX_ROOT_CLIENT_SECRET:/ {print $2}' docker-compose.yaml)
63-
curl -sf -u "root:$SECRET" 'http://localhost:8080/api/terminology/loinc?q=<term>'
86+
TERM=$(printf '%s' '<term>' | jq -sRr @uri)
87+
curl -sf -u "root:$SECRET" "http://localhost:8080/fhir/ValueSet/\$expand?url=http://loinc.org/vs&filter=${TERM}&count=10" \
88+
| jq '.expansion.contains[] | {code, display}'
6489
```
90+
If the LOINC package is not loaded locally, `.expansion.contains` will be empty — fall back to domain knowledge and cite the LOINC concept by name (e.g. peer OBX codes in the same message, unit of measure, and specimen type).
6591
2. For `patient-class`, `obr-status`, `obx-status`, etc.: look up valid target values in `src/code-mapping/mapping-types.ts`.
6692
3. Present suggestions with confidence: "High: `2823-3` → LOINC `2823-3` (Potassium)" vs "Needs review: `GLU` → `2345-7` (Glucose) — verify with lab".
67-
4. If ambiguous / needs domain expertise → offer to defer.
68-
5. After approval, resolve via the app API:
93+
4. If ambiguous / needs domain expertise → defer = park it out of active queue pending sender action:
94+
```sh
95+
curl -sf -X POST 'http://localhost:3000/defer/<id>'
96+
```
97+
5. After approval, resolve via the app API. Endpoint expects **form-urlencoded** body with `resolvedCode` and `resolvedDisplay` (not JSON). A `302` redirect to `/unmapped-codes?saved=...&replayed=N` signals success:
6998
```sh
70-
curl -sf -X POST 'http://localhost:3000/api/mapping/tasks/<taskId>/resolve' \
71-
-H 'Content-Type: application/json' \
72-
-d '{"code":"<target>","display":"<target display>"}'
99+
curl -s -X POST 'http://localhost:3000/api/mapping/tasks/<taskId>/resolve' \
100+
--data-urlencode 'resolvedCode=<target>' \
101+
--data-urlencode 'resolvedDisplay=<target display>' \
102+
-D - -o /dev/null
73103
```
74104
Message is auto-requeued.
75105
@@ -109,7 +139,6 @@ Inspect output lists each unmapped code with `mappingType`.
109139
110140
- Summary first, then one error at a time.
111141
- Never auto-fix without approval.
112-
- Skip `deferred` unless the user asks about them.
113-
- To defer: `curl -sf -X POST 'http://localhost:3000/defer/<id>'`.
142+
- Skip `deferred` rows in the summary unless the user asks about them.
114143
- Don't hand-count pipes — the inspect script already ran `hl7v2-inspect.sh`. For deeper field lookup use `scripts/hl7v2-inspect.sh --field SEG.N`.
115144
- Use the `hl7v2-info` skill to verify HL7v2 spec compliance when needed.

CLAUDE.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Use `bun`/`bun install`/`bun run` instead of `node`/`npm`/`yarn`/`pnpm`. Unit te
3131

3232
## Testing rules
3333

34-
1. **Run `bun test:local` after any change.** Unit tests + the smoke subset of integration tests (~10s). CI runs the full `bun test:all`; don't also run it locally unless debugging a CI-only failure.
34+
1. **Run `bun test:local` only before committing** — not after every change. Use `bun scripts/check-message-support.ts` or targeted `bun test <file>` to verify a specific fix. CI runs the full `bun test:all`; don't also run it locally unless debugging a CI-only failure.
3535
2. **Smoke tests are tagged by name prefix.** A test (or `describe`) whose name starts with `smoke: ` is included in `test:smoke` via `--test-name-pattern "smoke: "`. Promote by prepending the prefix; demote by removing it. Keep the smoke set small and focused on one happy-path per major flow.
3636
3. **Don't manually run `docker compose` for integration tests.** The test commands auto-start containers, wait for health, and run migrations. Integration tests use a separate test Aidbox on port 8888 via `docker-compose.test.yaml`.
3737

@@ -51,6 +51,8 @@ If `profileConformance.implementationGuides` enables US Core (`hl7.fhir.us.core`
5151

5252
IMPORTANT: Read `.claude/code-style.md` before writing or modifying code.
5353

54+
**Prefer scripts over raw file reads:** Use project scripts for inspection and diagnosis — `scripts/errors/inspect-error.sh`, `scripts/hl7v2-inspect.sh`, `bun scripts/check-message-support.ts`, `bun scripts/hl7v2-ref-lookup.ts` — before reaching for `Read`/`Grep` on source files. Read source files only when you need the code pattern itself (e.g. to write a new function that matches existing style). When you do read multiple source files, fire them in parallel.
55+
5456
Tailwind v4 gotcha: Tailwind utilities are emitted inside cascade layers, while `DESIGN_SYSTEM_CSS` is plain unlayered CSS. Broad unlayered resets override utilities even when the utility selector looks more specific; e.g. `a { color: inherit; }` breaks legacy anchor tabs using `text-white` / `text-gray-*`. Scope resets to unclassed elements (`a:not([class])`) or put them in Tailwind's base layer.
5557

5658
## Before Touching HL7v2

scripts/errors/inspect-error.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,12 @@ UNMAPPED=$(printf '%s' "$JSON" | jq -c '.unmappedCodes // []')
4747
if [ "$UNMAPPED" != "[]" ]; then
4848
echo "### Unmapped codes"
4949
printf '%s' "$JSON" | jq -r '
50+
. as $root |
5051
.unmappedCodes[]? |
51-
"- code=`\(.code // "")` system=`\(.system // "")` display=`\(.display // "")` mappingType=`\(.mappingType // "")`"
52+
. as $u |
53+
($u.mappingTask.reference // "" | sub("^Task/"; "")) as $taskId |
54+
([$root.entries[]? | select(.resourceType == "Task" and .id == $taskId)] | first) as $task |
55+
"- localCode=`\($u.localCode // "")` system=`\($u.localSystem // "")` display=`\($u.localDisplay // "")` mappingType=`\($task.code.coding[0].code // "")` taskId=`\($taskId)`"
5256
'
5357
echo
5458
fi

0 commit comments

Comments
 (0)