Skip to content

Partner imports find their booking, and patient links behind the QR (0.1.5) - #37

Merged
BackStacked merged 3 commits into
mainfrom
claude/project-thread-w4wng5
Nov 11, 2026
Merged

BackStacked merged 3 commits into
mainfrom
claude/project-thread-w4wng5

Conversation

@BackStacked

@BackStacked BackStacked commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

This PR does two things: it finds the booking a partner PDF belongs to, and it adds patient links (the QR on reports and bills opens the patient's page).

1. Partner imports: find the booking

  • ExternalReport.candidates(first = 5): finds patients by the printed name (the whole name first, then each word of three or more letters). It takes their bookings from the last 90 days that are not cancelled, keeping only those in branches where the caller has report:import. Each booking gets a score from 0 to 100, built from:

    • the name (same, partly the same, or same first name);
    • sex;
    • age, within ±1 year;
    • partner tests that map to booked tests (a catalog search restricted to the booking's codes, so LIVER FUNCTION TEST maps to the booking's LFT);
    • how recently it was booked.

    strong means name, sex and age agree and at least one test maps. Each candidate carries reasons, patientMismatches and per-test mappings.

  • Auto-match on upload: an upload without bookingId is matched when exactly one strong candidate in the upload's branch stands out (no other strong one within 20 points). It is audited as external_report.auto_match. Nothing is applied automatically.

  • suggestedTestCode: prefers the matched booking's tests.

2. Patient links (docs/21-patient-links.md)

  • Public fields (no sign-in):
    • patientPage(token) shows the lab, the patient's shortened name ("Ravi K. S."), the booking number, the branch, issued bills, released reports and tests still in the lab;
    • patientDocument(token, kind, invoiceId) returns one of those bills, or the released reports, as a PDF.
  • How they behave:
    • Unknown, expired, revoked and malformed tokens all answer null.
    • Every call counts against the caller's IP (PATIENT_LINK_PER_IP, default 60 per 10 minutes, shared through Redis) and answers RATE_LIMITED with retryAfterMs when over.
    • patientDocument counts as a document field in the cost limit.
  • Staff fields:
    • patientLink(bookingId) needs booking:read + report:read;
    • revokePatientLinks(bookingId) needs booking:update and is audited as patient_link.revoke.
  • Tokens:
    • A token is HMAC-SHA256(PATIENT_LINK_SECRET, nonce) in base64url.
    • db-kit 2.2.0 stores only its SHA-256 and the nonce.
    • Opening a link looks it up by hash, then does a constant-time HMAC check.
    • A booking keeps one live link, so printing twice prints the same QR.
  • Printing: with PATIENT_LINK_SECRET (32+ characters) and PATIENT_LINK_BASE_URL (https) set, bookingReportDocument and a booking's invoiceDocument put the link in their QR. Without them, nothing changes.

Kits

db-kit ^2.2.0 (patientLinks storage), report-kit ^2.3.0 and invoice-kit ^2.2.0 (QR value and caption, UPI pay QR), and client ^1.12.0. kits.json and the lockfile are updated to match. The lockfile entries have no hash yet; npm fills it in on the next install.

CI needs these tags published first: client v1.10.0, client v1.12.0 (after client#19), db-kit v2.2.0, report-kit v2.3.0, then invoice-kit v2.2.0.

Checks

npm run typecheck. TEST_BACKENDS=pg npm test: 281 passed. New checks cover:

  • imports: auto-match, the audit entry, candidate ranking, booking-aware suggestions, isolation between organisations;
  • the link itself: one 43-character token under the base URL, the same link on a second call, and only the hash stored, never the token;
  • access: another organisation gets NOT_FOUND, and no sign-in gets UNAUTHENTICATED;
  • the public page and both PDFs;
  • tokens that are unknown, too short or too long all get null;
  • a bill id from another booking gets NOT_FOUND;
  • the view count;
  • revoking: done by front desk, the old link stops opening, and the next print makes a new link that opens;
  • the audit entry and the rate limit.

MongoDB can't be downloaded in my container; CI runs both databases. Schema, reference and llms-full are regenerated.


🤖 Generated with Claude Code

https://claude.ai/code/session_018jBohjC7uBfVPopxyZBtS1

…ch on upload (0.1.5)

- ExternalReport.candidates(first): bookings of patients found by the printed name, scored by name,
  sex, age ±1, partner tests that map to booked tests, and recency; reasons, mismatches, mappings
- An upload without a booking is matched on its own when exactly one strong candidate stands out
  (audited external_report.auto_match); staff still review and apply
- suggestedTestCode prefers the matched booking's tests
- Lockfile: client 1.10.0 (published from client v1.10.0; hash filled on the next install)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jBohjC7uBfVPopxyZBtS1
@BackStacked BackStacked self-assigned this Nov 11, 2026
…page

- patientPage(token) and patientDocument(token, kind, invoiceId): public, rate-limited per IP
  (PATIENT_LINK_PER_IP), null for any token that does not open; one booking's issued bills and
  released reports only, the patient's name shortened.
- patientLink(bookingId) (booking:read + report:read) and revokePatientLinks(bookingId) (booking:update, audited).
- Tokens are HMAC(PATIENT_LINK_SECRET, nonce); db-kit stores only the SHA-256 and the nonce.
- Printed reports and a booking's bills carry the link in their QR when PATIENT_LINK_SECRET and
  PATIENT_LINK_BASE_URL are set.
- Kits: db-kit ^2.2.0, report-kit ^2.3.0, invoice-kit ^2.2.0, client ^1.12.0.
- docs/21-patient-links.md, configuration, example, schema, reference and llms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jBohjC7uBfVPopxyZBtS1
@BackStacked BackStacked changed the title Partner imports: find the booking a PDF belongs to, auto-match on upload (0.1.5) Partner imports find their booking, and patient links behind the QR (0.1.5) Nov 11, 2026

Copy link
Copy Markdown
Contributor Author

ci fails at npm ci with ETARGET: No matching version found for @mgpl-diagnostics/report-kit@2.3.0. The code isn't the problem: the kit versions this PR needs aren't published yet. With those kits installed locally from their PR branches, TEST_BACKENDS=pg npm test passes (281 tests).

CI goes green once these tags are published, in this order:

  • client v1.10.0 and v1.12.0
  • db-kit v2.2.0
  • report-kit v2.3.0
  • invoice-kit v2.2.0

Generated by Claude Code

@BackStacked
BackStacked merged commit ee44611 into main Nov 11, 2026
0 of 2 checks passed
@BackStacked
BackStacked deleted the claude/project-thread-w4wng5 branch November 11, 2026 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants