fhirsmith 0.11.2 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +377 -2
  2. package/README.md +1 -1
  3. package/config-template.json +31 -1
  4. package/library/languages.js +191 -34
  5. package/library/path-safety.js +51 -0
  6. package/library/request-token.js +56 -0
  7. package/package.json +2 -2
  8. package/packages/packages.js +17 -118
  9. package/publisher/publisher.js +208 -30
  10. package/publisher/validation.js +210 -0
  11. package/server.js +24 -15
  12. package/stats-db.js +127 -0
  13. package/stats.js +172 -6
  14. package/translations/Messages.properties +5 -1
  15. package/tx/README.md +21 -0
  16. package/tx/cs/cs-api.js +30 -1
  17. package/tx/cs/cs-api.md +1 -1
  18. package/tx/cs/cs-cs.js +1 -1
  19. package/tx/cs/cs-icd11.js +805 -0
  20. package/tx/cs/cs-lang.js +331 -85
  21. package/tx/cs/cs-loinc.js +45 -4
  22. package/tx/cs/cs-mimetypes.js +492 -21
  23. package/tx/cs/cs-snomed.js +141 -58
  24. package/tx/cs/cs-ucum.js +33 -3
  25. package/tx/cs/icd11-expressions.js +243 -0
  26. package/tx/importers/export-icd11-codesystem.js +823 -0
  27. package/tx/importers/icd11-schema.md +285 -0
  28. package/tx/importers/import-icd11.js +861 -0
  29. package/tx/importers/import-loinc.module.js +14 -7
  30. package/tx/importers/import-sct.module.js +10 -1
  31. package/tx/library/codesystem.js +21 -27
  32. package/tx/library/conceptmap.js +39 -0
  33. package/tx/library/nesting-limit.js +79 -0
  34. package/tx/library/operation-outcome.js +97 -3
  35. package/tx/library/parameter-check.js +139 -0
  36. package/tx/library/valueset.js +16 -20
  37. package/tx/library.js +23 -1
  38. package/tx/ocl/cache/cache-utils.cjs +5 -3
  39. package/tx/ocl/cs-ocl.cjs +73 -17
  40. package/tx/ocl/jobs/background-queue.cjs +6 -3
  41. package/tx/ocl/mappers/concept-mapper.cjs +9 -1
  42. package/tx/ocl/shared/patches.cjs +48 -1
  43. package/tx/ocl/vs-ocl.cjs +15 -13
  44. package/tx/operation-context.js +220 -10
  45. package/tx/params.js +226 -175
  46. package/tx/sct/expressions.js +677 -15
  47. package/tx/tx.fhir.org.yml +3 -2
  48. package/tx/tx.js +72 -92
  49. package/tx/workers/batch-validate.js +11 -18
  50. package/tx/workers/batch.js +16 -28
  51. package/tx/workers/cache-control.js +87 -17
  52. package/tx/workers/closure.js +3 -8
  53. package/tx/workers/compare.js +6 -34
  54. package/tx/workers/expand.js +108 -60
  55. package/tx/workers/lookup.js +35 -40
  56. package/tx/workers/read.js +6 -40
  57. package/tx/workers/search.js +2 -8
  58. package/tx/workers/subsumes.js +58 -41
  59. package/tx/workers/translate.js +472 -47
  60. package/tx/workers/validate.js +83 -78
  61. package/tx/workers/worker.js +92 -11
  62. package/tx/xml/xml-base.js +264 -77
  63. package/tx/xversion/xv-conceptmap.js +81 -0
  64. package/tx/xversion/xv-parameters.js +8 -0
  65. package/tx/xversion/xv-valueset.js +39 -1
  66. package/utilities/merge-audit.py +178 -0
  67. package/utilities/why-merge-audit.md +39 -0
  68. package/tx/data/snomed-subset.txt +0 -84
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env python3
2
+ """merge-audit.py — find content silently lost in merge commits.
3
+
4
+ For every merge commit in range, compares the *recorded* merge result against a
5
+ mechanical re-merge of its parents (git's --remerge-diff). Lines the mechanical
6
+ merge would have kept but the recorded merge dropped are "vanished" content —
7
+ either a deliberate conflict resolution, or an accident nobody chose.
8
+
9
+ For each vanished line the script then checks:
10
+ 1. is it still absent from the tip of the branch? (if it came back, ignore)
11
+ 2. did any *later regular commit* touch that content? (if someone deliberately
12
+ removed/reworded it afterwards, it's probably intentional — the explaining
13
+ commit is shown so a human can judge)
14
+
15
+ Findings are classified:
16
+ SILENT - the file was NOT listed in the merge commit's "# Conflicts:" trailer:
17
+ git merged it cleanly, yet the recorded result differs. Nobody chose
18
+ this. Highest suspicion.
19
+ CONFLICT - the file was listed as conflicted; content was dropped during manual
20
+ resolution. May be intentional, still worth review.
21
+
22
+ Requirements: git >= 2.36 (for --remerge-diff), python3. Run against a full
23
+ (non-shallow) clone for best results; on a shallow clone, history checks near the
24
+ boundary will attribute old content to the boundary commit.
25
+
26
+ Usage:
27
+ python3 merge-audit.py /path/to/repo --since 2026-01-01 [--tip master]
28
+ python3 merge-audit.py /path/to/repo --range v6.0..master
29
+ python3 merge-audit.py /path/to/repo --since 2026-06-01 --path-prefix source/
30
+
31
+ Exit code is 1 if any SILENT finding with still-missing content exists (useful
32
+ as a CI guard on new merges: --range 'origin/master@{1}..origin/master').
33
+ """
34
+ import argparse, json, re, subprocess, sys
35
+
36
+ MARKER = re.compile(r"^(<{7}|={7}|>{7}|\|{7})")
37
+
38
+ def norm(s):
39
+ return "".join(s.split())
40
+
41
+ def git(repo, *args, check=True):
42
+ r = subprocess.run(["git", "-C", repo, *args], capture_output=True, text=True)
43
+ if check and r.returncode != 0:
44
+ sys.exit(f"git {' '.join(args[:3])}... failed: {r.stderr.strip()[:300]}")
45
+ return r
46
+
47
+ def parse_remerge_log(text):
48
+ merges, cur, cur_file = [], None, None
49
+ def close_file():
50
+ nonlocal cur_file
51
+ if cur is not None and cur_file is not None:
52
+ slot = cur["files"].setdefault(cur_file["path"], {"removed": [], "added": []})
53
+ slot["removed"] += cur_file["removed"]
54
+ slot["added"] += cur_file["added"]
55
+ cur_file = None
56
+ def close_merge():
57
+ nonlocal cur
58
+ close_file()
59
+ if cur is not None:
60
+ merges.append(cur)
61
+ cur = None
62
+ for line in text.splitlines():
63
+ m = re.match(r"^commit ([0-9a-f]{40})", line)
64
+ if m:
65
+ close_merge()
66
+ cur = {"sha": m.group(1), "author": "", "date": "", "subject": None,
67
+ "conflicts": set(), "files": {}}
68
+ continue
69
+ if cur is None:
70
+ continue
71
+ if line.startswith("Author: "):
72
+ cur["author"] = line[8:]; continue
73
+ if line.startswith("Date: "):
74
+ cur["date"] = line[6:].strip(); continue
75
+ if line.startswith("diff --git "):
76
+ close_file()
77
+ m2 = re.match(r"^diff --git a/(.*) b/(.*)$", line)
78
+ cur_file = {"path": m2.group(2) if m2 else line, "removed": [], "added": []}
79
+ continue
80
+ if cur_file is not None:
81
+ if line.startswith("-") and not line.startswith("---"):
82
+ body = line[1:]
83
+ if not MARKER.match(body.lstrip()):
84
+ cur_file["removed"].append(body)
85
+ elif line.startswith("+") and not line.startswith("+++"):
86
+ cur_file["added"].append(line[1:])
87
+ else:
88
+ st = line.strip()
89
+ if cur["subject"] is None and st and not st.startswith("Merge: "):
90
+ cur["subject"] = st
91
+ if st.startswith("#") and "/" in st and "Conflicts" not in st:
92
+ cur["conflicts"].add(st.lstrip("#").strip())
93
+ close_merge()
94
+ return merges
95
+
96
+ def main():
97
+ ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
98
+ ap.add_argument("repo")
99
+ ap.add_argument("--since", help="only merges after this date, e.g. 2026-01-01")
100
+ ap.add_argument("--range", dest="range_", help="rev range, e.g. v6.0..master")
101
+ ap.add_argument("--tip", default="master", help="branch whose tip defines 'still missing' (default master)")
102
+ ap.add_argument("--path-prefix", default="", help="only report findings under this path")
103
+ ap.add_argument("--min-line", type=int, default=4, help="ignore vanished lines shorter than this (normalized)")
104
+ ap.add_argument("--json", dest="json_out", help="also write findings as JSON to this file")
105
+ args = ap.parse_args()
106
+
107
+ ver = git(args.repo, "version").stdout.strip()
108
+ m = re.search(r"(\d+)\.(\d+)", ver)
109
+ if m and (int(m.group(1)), int(m.group(2))) < (2, 36):
110
+ sys.exit(f"{ver}: --remerge-diff needs git >= 2.36")
111
+
112
+ log_args = ["log", "--merges", "--remerge-diff", "--no-color"]
113
+ if args.since:
114
+ log_args.append(f"--since={args.since}")
115
+ log_args.append(args.range_ if args.range_ else args.tip)
116
+ print(f"re-merging all merge commits in range (this can take a few minutes)...", file=sys.stderr)
117
+ merges = parse_remerge_log(git(args.repo, *log_args).stdout)
118
+
119
+ tip_cache = {}
120
+ def tip_content(path):
121
+ if path not in tip_cache:
122
+ r = git(args.repo, "show", f"{args.tip}:{path}", check=False)
123
+ tip_cache[path] = r.stdout if r.returncode == 0 else None
124
+ return tip_cache[path]
125
+
126
+ findings = []
127
+ for mg in merges:
128
+ for path, fd in mg["files"].items():
129
+ if args.path_prefix and not path.startswith(args.path_prefix):
130
+ continue
131
+ added_norms = {norm(a) for a in fd["added"]}
132
+ vanished = [r for r in fd["removed"]
133
+ if norm(r) not in added_norms and len(norm(r)) > args.min_line]
134
+ if not vanished:
135
+ continue
136
+ tip = tip_content(path)
137
+ if tip is None:
138
+ still, note = vanished, "file absent at tip"
139
+ else:
140
+ tip_norms = {norm(l) for l in tip.splitlines()}
141
+ still = [v for v in vanished if norm(v) not in tip_norms]
142
+ note = ""
143
+ if not still:
144
+ continue
145
+ probe = max(still, key=lambda s: len(norm(s))).strip()
146
+ hist = git(args.repo, "log", "--format=%h %an %ad %s", "--date=short",
147
+ "-S", probe, args.tip, "--", path, check=False).stdout.splitlines()
148
+ hist = [h for h in hist if not h.startswith(mg["sha"][:8])]
149
+ findings.append({
150
+ "sha": mg["sha"][:10], "date": mg["date"], "author": mg["author"],
151
+ "subject": mg["subject"] or "", "path": path,
152
+ "kind": "CONFLICT" if path in mg["conflicts"] else "SILENT",
153
+ "vanished": len(vanished), "still_missing": len(still), "note": note,
154
+ "history": hist[:4], "lines": [s.strip() for s in still[:12]],
155
+ })
156
+
157
+ findings.sort(key=lambda x: (x["kind"] != "SILENT", -x["still_missing"]))
158
+ print(f"\n# Merge-loss audit: {len(merges)} merges scanned, "
159
+ f"{len(findings)} file-level findings with content still missing at {args.tip}\n")
160
+ for f in findings:
161
+ print("=" * 100)
162
+ print(f"{f['kind']:8s} merge {f['sha']} {f['date']} {f['author']}")
163
+ print(f" {f['subject'][:100]}")
164
+ print(f" {f['path']} (still missing {f['still_missing']} of {f['vanished']} dropped lines) {f['note']}")
165
+ if f["history"]:
166
+ print(" later commits touching this content (may explain an intentional removal):")
167
+ for h in f["history"]:
168
+ print(f" {h[:140]}")
169
+ for l in f["lines"]:
170
+ print(f" - {l[:160]}")
171
+ print(f" inspect: git show --remerge-diff {f['sha']} -- '{f['path']}'")
172
+ if args.json_out:
173
+ with open(args.json_out, "w") as fh:
174
+ json.dump(findings, fh, indent=1)
175
+ sys.exit(1 if any(f["kind"] == "SILENT" for f in findings) else 0)
176
+
177
+ if __name__ == "__main__":
178
+ main()
@@ -0,0 +1,39 @@
1
+ # Why add a merge-loss audit to your repository
2
+
3
+ ## The problem
4
+
5
+ When someone merges one branch into another and resolves conflicts by hand, they can accidentally revert other people's work — most commonly by keeping their whole version of a file, which throws away changes in regions of the file that were never in conflict. Git records whatever resolution they commit, with no distinction between "what the merge algorithm produced" and "what the human changed afterwards."
6
+
7
+ This class of error is uniquely invisible. The lost content doesn't appear in the pull request's "files changed" view, or appears buried in a list of hundreds of incidentally-touched files that no reviewer can realistically audit. Standard forensics miss it too: `git log -S` and `git blame` skip merge commits by default, so even when someone later notices content is gone, the search for *when* and *who* comes up empty. Worse, the blame usually lands on the wrong person — whoever's later, entirely innocent merge first makes the absence visible in a diff.
8
+
9
+ Any repository where humans (or, increasingly, coding agents) resolve merge conflicts has this exposure. That includes every workflow where contributors maintain local clones and pull with divergence — regardless of whether the project merges PRs by merge commit, squash, or rebase.
10
+
11
+ ## A real incident
12
+
13
+ In HL7/fhir, a validation invariant (qrs-4, FHIR-55973) was added to the QuestionnaireResponse resource in April 2026. In June, a routine "merge master into my branch" commit silently reverted it — the file wasn't even listed as conflicted in the merge. The loss rode into master inside an unrelated PR, went unnoticed for two months, and was eventually blamed on the author of a *different* PR whose merge happened to make the absence visible. A retrospective audit of that repository found the same merge had silently reverted content in ~25 files, and found several additional losses from other merges, including one introduced by an AI coding agent resolving PR conflicts.
14
+
15
+ Every one of those losses was detectable, mechanically, on the day it happened.
16
+
17
+ ## What the check does
18
+
19
+ Git 2.36 added `--remerge-diff`: for any merge commit, git redoes the merge mechanically and shows only what the *recorded* result changed beyond that. An empty result means the merge was exactly what the algorithm produced. A non-empty result is precisely the set of decisions the human made — a handful of lines, instead of a hundred-file merge diff.
20
+
21
+ The audit script (`tools/merge-audit.py`) runs this over a range of commits and classifies each deviation. Content dropped from a file that was **not** conflicted is flagged as a *silent drop* — nobody chose it, and it is almost always an accident. Content dropped during a genuine conflict resolution is listed for review with lower urgency. The accompanying GitHub Actions workflow runs the script on each push to the main branch, over just the newly pushed commits, and posts a warning and job summary when it finds a silent drop. It is advisory only — it never fails a build — and it also supports manual runs over a longer window as a periodic sweep.
22
+
23
+ For contributors, there is a one-line self-check before pushing any hand-resolved merge:
24
+
25
+ git show --remerge-diff HEAD
26
+
27
+ If that shows anything you didn't consciously decide, the merge is wrong.
28
+
29
+ ## The cost
30
+
31
+ Near zero. The CI job needs a full-history checkout and a few minutes of compute per push; the script has no dependencies beyond git ≥ 2.36 and python3. False positives are rare and self-explaining: deliberate resolutions show up with the commit that explains them, and are waived by reading the warning. There is nothing to configure beyond the path prefix that scopes which files matter (e.g. `source/`).
32
+
33
+ ## Why this isn't already standard practice
34
+
35
+ `--remerge-diff` is recent (2022) and little-known, and no packaged CI action popularized it the way linters and secret-scanners were popularized. The failure mode is rare per-merge, invisible when it occurs, and expensive to diagnose months later — exactly the profile of a problem that never causes enough pain in any one place to produce standard tooling. That is an argument for adopting the check, not against it: the first time it fires, it will have paid for itself.
36
+
37
+ ## Adoption
38
+
39
+ Copy `tools/merge-audit.py` into the repository, add the workflow as `.github/workflows/merge-audit.yml`, and set the `--path-prefix` argument to the directory tree whose content matters. For an initial baseline, run it once by hand over the repository's recent history (`python3 tools/merge-audit.py . --since <date>`) — it will tell you whether you already have losses you don't know about.
@@ -1,84 +0,0 @@
1
- 900000000000526001 |REPLACED BY association reference set (foundation metadata concept)|
2
- 1539003 |Acquired trigger finger (disorder)|
3
- 1204474000 |Product containing precisely barium sulfate 600 milligram/1 milliliter conventional release oral suspension (clinical drug)|
4
- 86299006 |Tetralogy of Fallot (disorder)|
5
- 430983003 |Brucella abortus, vaccinal strain RB51 (organism)|
6
- 608785007 |Open reduction of fracture of radius and ulna with internal fixation (procedure)|
7
- 370049004 |No tumor invasion (finding)|
8
- 370050004 |No tumor invasion of adjacent tissue (finding)|
9
- 174826008 |Arterial switch operation (procedure)|
10
- 11687002 |Gestational diabetes mellitus (disorder)|
11
- 897148007 |Alcoholic beverage intake (observable entity)|
12
- 816080008 |International Patient Summary (foundation metadata concept)|
13
- 303248007 |Blood film specimen (specimen)|
14
- 162980001 |Cardiovascular system not examined (situation)|
15
- 297250002 |No family history of stroke (situation)|
16
- 162001003 |No cardiovascular symptom (situation)|
17
- 281302008 |Above reference range (qualifier value)|
18
- 281300000 |Below reference range (qualifier value)|
19
- 281301001 |Within reference range (qualifier value)|
20
- 268677005 |[X]Other delirium (disorder)|
21
- 155728006 |Appendicitis (disorder)|
22
- 74400008 |Appendicitis (disorder)|
23
- 155729003 |Appendicitis (disorder)|
24
- 307530000 |Appendicitis NOS (disorder)|
25
- 42463004 |Genus Opisthorchis (organism)|
26
- 71650008 |Opisthorchis viverrini (organism)|
27
- 116676008 |Associated morphology (attribute)|
28
- 128045006 |Cellulitis (disorder)|
29
- 20946005 |Fracture, closed (morphologic abnormality)|
30
- 28012007 |Closed fracture of shaft of tibia (disorder)|
31
- 363698007 |Finding site (attribute)|
32
- 447139008 |Closed fracture of tibia (disorder)|
33
- 52687003 |Bone structure of shaft of tibia (body structure)|
34
- 56459004 |Foot structure (body structure)|
35
- 64572001 |Disease (disorder)|
36
- 6990005 |Fracture of shaft of tibia (disorder)|
37
- <<128241005 |Inflammatory disease of liver (disorder)|
38
- <<10200004 |Liver structure (body structure)|
39
- <<90140006 |Structure of intrahepatic biliary tree (body structure)|
40
- <<364159005 |Liver observable (observable entity)|
41
- <<227014008 |Liver pate (substance)|
42
- <<249565005 |Liver finding (finding)|
43
- <<406459008 |Product containing halibut liver oil (medicinal product)|
44
- <<776168003 |Product containing only halibut liver oil (medicinal product)|
45
- <<309399009 |Hospital-based dietitian (occupation)|
46
- <<421813008 |Class Hepaticopsida (organism)|
47
- <<441802002 |Computed tomography of liver (procedure)|
48
- <<119383005 |Specimen from liver (specimen)|
49
- 129433002 |Inspection|
50
- 367430006 |Repair of tendon of hand (procedure)|
51
- 119657005 |Hand repair (procedure)|
52
- 274059009 |Hand tendon operation (procedure)|
53
- 281760001 |Repair of tendon of upper limb (procedure)|
54
- 18701002 |Repair of tendon of hand with graft (procedure)|
55
- 214433003 |Repair of ruptured tendon of hand (procedure)|
56
- 243234005 |Repair of tendon of hand by transfer or transplantation (procedure)|
57
- 26731003 |Repair of tendon of hand by suture (procedure)|
58
- 27106001 |Reconstruction of tendon pulley of hand (procedure)|
59
- 45810006 |Reattachment of tendon of hand (procedure)|
60
- 709291000 |Repair of flexor tendon of hand (procedure)|
61
- 712638006 |Repair of extensor tendon of hand (procedure)|
62
- 76340004 |Plication of tendon of hand (procedure)|
63
- 90650008 |Lengthening of tendon of hand (procedure)|
64
- 90907001 |Shortening of tendon of hand (procedure)|
65
- 91092007 |Myotenontoplasty of hand (procedure)|
66
- 118632007 |Structure of tendon within hand (body structure)|
67
- 13024002 |Tendon structure (body structure)|
68
- 245098002 |Structure of tendon within upper limb (body structure)|
69
- 85562004 |Hand structure (body structure)|
70
- #Referenced in request
71
- 734139008 |Anatomy structure and part association reference set (foundation metadata concept)|
72
- #For snomed-procedure-in-display test
73
- #Required by: validate-code-isa-in-response.json
74
- 41271000119108 |Infective arthritis caused by virus and co-occurrent with hepatitis (disorder)|
75
- #Required by: expand-ecl-ancOrSelf-response.json, expand-ecl-ancestors-response.json, expand-ecl-parents-response.json, expand-ecl-parentsOrSelf-response.json
76
- 303270005 |Liver and biliary structure (body structure)|
77
- 699602007 |Structure of viscus of abdomen proper (body structure)|
78
- #Required by: expand-ecl-ancOrSelf-response.json, expand-ecl-ancestors-response.json
79
- 420473002 |Structure of subregion of trunk (body structure)|
80
- 49596003 |Digestive organ (body structure)|
81
- 86762007 |Digestive system (body structure)|
82
- 1197039003 |Structure of lymphatic vessel of liver (body structure)|
83
- #Required by: lookup-procedure-pc-response.json
84
- 24028007 |Right (qualifier value)|