Skip to content

feat: assignment validation against nmrshiftdb2 quickcheck - #147

Merged
vcnainala merged 4 commits into
developmentfrom
feat/assignment-validation
Oct 2, 2026
Merged

vcnainala merged 4 commits into
developmentfrom
feat/assignment-validation

Conversation

@vcnainala

@vcnainala vcnainala commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Closes #15.

What

POST /latest/validate/assignments checks a structure's ¹H and ¹³C assignments against nmrshiftdb2 quickcheck predictions. It gives a quality mark per nucleus, laid out like the nmrshiftdb2 quality report, and an assignment check for each assigned signal. The work happens in a new nmr-cli validate-assignments command (JSON on stdin). The router pipes the request to it with docker exec -i.

The response has two layers built from the same author assignments, so they always agree:

  • reports["13C"|"1H"]: how well the assigned shifts fit, atom by atom. Mark 1–10, accept/revise/reject, per-atom observed, predicted and deviation values, HOSE spheres and codes, and an in_database_likely flag. Each atom carries the author's assigned shift and the status of its assignment_check row. A nucleus with a red atom is at best revise, whatever its mark.
  • assignment_check: the author's atom-to-shift mapping compared with the prediction. Each assignment gets ok/review/fail. It also includes swap suggestions, equivalence violations, missing signals, solvent peaks, proton counts and a referencing offset.
  • verdict: combines both layers, with ¹³C first.

How the servlet is used

Both nuclei go in one request. The servlet only accepts a v1;v2;... shift list and does its own matching of values to atoms, which can differ from the author's. For 2,6-dimethylphenol it put the OH shift on the aromatic CHs. The command therefore uses the servlet for predictions per atom, and uses its status only where its matching agrees with the author's assignment. It:

  • strips explicit H atoms, which otherwise shift the servlet's atom numbering, and maps the servlet's atom numbers back to the submitted molfile;
  • maps servlet H numbers to their heavy atoms (they are appended per heavy atom after the heavy atoms; checked against HOSE codes for nicotine and 2-chloropyridine);
  • sends one value per distinct ¹H signal, nudging identical values by ±0.001 ppm so the servlet keeps both (reported under adjustments).

Chemist-facing rules

  • A prediction with fewer than 4 HOSE spheres can lead to review, never to fail.
  • A diastereotopic CH₂ pair is compared by its mean shift and shown as an a/b pair.
  • A swap is suggested only when both predictions are reliable and swapping clearly reduces the error.
  • nmrshiftdb2 does not publish its mark formula, so the mark is an approximation and is flagged with mark_is_approximate.

Router

  • Pydantic request models.
  • CLI exit code 2 maps to 422 and exit code 3 (servlet unavailable) to 503; a missing container gives 500.
  • A 24 h in-memory cache keyed by the request hash, so the shared public servlet isn't hit repeatedly. Errors are not cached.
  • Docs: docs/src/modules/validation.md.

Tests

  • app/scripts/nmr-cli: npm test, 12 tests using recorded servlet responses (test/fixtures/quickcheck-responses.json) for 3,4,5-trimethoxybenzaldehyde (Mnova export with an explicit H) and nicotine. They cover:
    • H stripping and renumbering, and H ownership;
    • a correct set (mark 10, consistent) and a wrong C-4 (red, fail, reject);
    • swapped C-1/C-4 (red in the report, fail, swap suggested);
    • nicotine H-2′ at 3 spheres (review, not fail) and a/b pair rows;
    • the report following the author's assignments when the servlet re-matches values to other atoms;
    • one non-matching proton keeping ¹H from accept;
    • missing-signal labels, value nudging and invalid input.
  • tests/test_validate.py: 7 router tests with subprocess.run mocked. They cover stdin piping, the cache, 422/503/500 mapping and schema validation.
  • Verified end to end against the live servlet with the built CLI, including 2,6-dimethylphenol from an Mnova/ChemDraw SDF.

Consumer

Used by NFDI4Chem/nmrxiv#1579 (submission-flow Quickcheck and the public /quickcheck page).

Adds nmr-cli validate-assignments and POST /validate/assignments. The command
checks 1H/13C assignments of a structure against nmrshiftdb2 quickcheck in one
request and returns a quality report per nucleus (mark, per-atom deviation,
HOSE spheres) plus an assignment check (per-row status, swap suggestions,
equivalence, missing signals, referencing offset) and a combined verdict.
The quickcheck servlet only receives a shift list and matches it to atoms
itself, so the quality report could pair a value with another atom than
the author did (2,6-dimethylphenol: the OH shift landed on the aromatic
CHs) and disagree with the assignment check. Observed values and statuses
now come from the assignment check rows, and a nucleus with a red atom is
at best 'revise'.
@hamed-musallam

Copy link
Copy Markdown
Collaborator

I’m generally fine with the structure, but I cannot fully follow the code or review it in detail. It’s quite large

Keep validate-assignments on the ESM nmr-cli entrypoint, and give the
validation modules .js import specifiers so the NodeNext build can load them.
@vcnainala
vcnainala merged commit 7576c98 into development Oct 2, 2026
1 check passed
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