aimedicalcoding 0.3.5__tar.gz → 0.3.6__tar.gz

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 (73) hide show
  1. {aimedicalcoding-0.3.5/aimedicalcoding.egg-info → aimedicalcoding-0.3.6}/PKG-INFO +1 -1
  2. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/adapters/ccda/codes.py +12 -0
  3. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/serialize.py +7 -1
  4. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/loinc/axes.py +42 -1
  5. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6/aimedicalcoding.egg-info}/PKG-INFO +1 -1
  6. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/docs/GAP_SCHEMA.md +4 -2
  7. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/setup.py +1 -1
  8. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/LICENSE +0 -0
  9. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/MANIFEST.in +0 -0
  10. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/README.md +0 -0
  11. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/README_PYPI.md +0 -0
  12. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/__init__.py +0 -0
  13. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/__main__.py +0 -0
  14. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/adapters/__init__.py +0 -0
  15. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/adapters/base.py +0 -0
  16. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/adapters/ccda/__init__.py +0 -0
  17. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/adapters/ccda/adapter.py +0 -0
  18. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/adapters/ccda/narrative.py +0 -0
  19. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/adapters/ccda/sections.py +0 -0
  20. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/adapters/custom_json/__init__.py +0 -0
  21. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/adapters/custom_json/adapter.py +0 -0
  22. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/adapters/fhir/__init__.py +0 -0
  23. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/adapters/fhir/adapter.py +0 -0
  24. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/adapters/fhir/codes.py +0 -0
  25. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/adapters/fhir/mapping.py +0 -0
  26. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/api.py +0 -0
  27. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/core/__init__.py +0 -0
  28. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/core/checkdigits.py +0 -0
  29. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/core/models.py +0 -0
  30. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/core/system_registry.py +0 -0
  31. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/pipeline/__init__.py +0 -0
  32. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/pipeline/orchestrator.py +0 -0
  33. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/recovery/__init__.py +0 -0
  34. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/recovery/engine.py +0 -0
  35. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/__init__.py +0 -0
  36. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/base.py +0 -0
  37. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/cvx/__init__.py +0 -0
  38. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/cvx/handler.py +0 -0
  39. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/cvx/normalize.py +0 -0
  40. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/cvx/patterns.py +0 -0
  41. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/icd10cm/__init__.py +0 -0
  42. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/icd10cm/handler.py +0 -0
  43. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/icd10cm/icdformat.py +0 -0
  44. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/icd10cm/patterns.py +0 -0
  45. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/loinc/__init__.py +0 -0
  46. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/loinc/handler.py +0 -0
  47. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/loinc/normalize.py +0 -0
  48. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/loinc/patterns.py +0 -0
  49. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/rxnorm/__init__.py +0 -0
  50. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/rxnorm/handler.py +0 -0
  51. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/rxnorm/normalize.py +0 -0
  52. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/rxnorm/patterns.py +0 -0
  53. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/snomed/__init__.py +0 -0
  54. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/snomed/handler.py +0 -0
  55. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/snomed/hierarchy.py +0 -0
  56. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/snomed/patterns.py +0 -0
  57. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/systems/snomed/qualifiers.py +0 -0
  58. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/terminology/__init__.py +0 -0
  59. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding/terminology/service.py +0 -0
  60. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding.egg-info/SOURCES.txt +0 -0
  61. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding.egg-info/dependency_links.txt +0 -0
  62. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding.egg-info/entry_points.txt +0 -0
  63. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding.egg-info/requires.txt +0 -0
  64. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/aimedicalcoding.egg-info/top_level.txt +0 -0
  65. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/docs/ARCHITECTURE.md +0 -0
  66. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/docs/OUTPUT_GUIDE.md +0 -0
  67. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/docs/patterns/CVX_pattern_analysis.md +0 -0
  68. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/docs/patterns/ICD10CM_pattern_analysis.md +0 -0
  69. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/docs/patterns/LOINC_pattern_analysis.md +0 -0
  70. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/docs/patterns/RxNorm_pattern_analysis.md +0 -0
  71. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/docs/patterns/SNOMED_pattern_analysis.md +0 -0
  72. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/pyproject.toml +0 -0
  73. {aimedicalcoding-0.3.5 → aimedicalcoding-0.3.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aimedicalcoding
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: Detect missing/broken clinical codes (LOINC / SNOMED CT / RxNorm / ICD-10-CM / CVX) in CCDA and FHIR documents, with recovery evidence
5
5
  Author: Aakash Kag, Vinay Shankar Miryala, Siva Karthik, Mohammed Rayaan
6
6
  License: MIT
@@ -186,6 +186,18 @@ def extract_companion(obs_el: etree._Element) -> dict:
186
186
  unit = (low.get("unit") if low is not None else None) or (
187
187
  high.get("unit") if high is not None else None)
188
188
  out["reference_range"] = f"{lo}-{hi}" + (f" {unit}" if unit else "")
189
+
190
+ # method: the LOINC Method axis, carried structurally as <methodCode>.
191
+ # Prefer the displayName (e.g. "Automated") over the raw code.
192
+ method = obs_el.find(q("methodCode"))
193
+ if method is not None and (method.get("displayName") or method.get("code")):
194
+ out["method"] = method.get("displayName") or method.get("code")
195
+
196
+ # body site: <targetSiteCode> (usually SNOMED, e.g. "Right arm").
197
+ site = obs_el.find(q("targetSiteCode"))
198
+ if site is not None and (site.get("displayName") or site.get("code")):
199
+ out["body_site"] = site.get("displayName") or site.get("code")
200
+ out["body_site_code"] = site.get("code")
189
201
  return out
190
202
 
191
203
 
@@ -87,14 +87,20 @@ def _qualifiers(stmt: ClinicalStatement) -> dict:
87
87
  unit = stmt.value.unit if stmt.value else None
88
88
  q = snomed_q.extract_qualifiers(stmt, stmt.raw_text)
89
89
  out = {
90
- "specimen": {"value": spec_hint, "source": spec_src, "confidence": spec_conf},
90
+ "specimen": {"value": spec_hint, "source": spec_src, "confidence": spec_conf,
91
+ # explicit specimen disagrees with the analyte text (data-quality signal)
92
+ "conflict": loinc_axes.specimen_conflict(stmt)},
91
93
  "property": loinc_axes.property_from_unit(unit),
92
94
  "scale": loinc_axes.scale_from_value(stmt),
95
+ "method": stmt.companion.get("method"),
96
+ "body_site": stmt.companion.get("body_site"),
93
97
  "laterality": q["laterality"],
94
98
  "severity": q["severity"],
95
99
  "course": q["course"],
96
100
  # structural negationInd wins over text cues ("denies", "no known")
97
101
  "negation": ("negated" if stmt.companion.get("negation") else q["negation_context"]),
102
+ "interpretation": stmt.companion.get("interpretation"),
103
+ "reference_range": stmt.companion.get("reference_range"),
98
104
  "panel": (stmt.panel.code if stmt.panel else None),
99
105
  "effective_time": stmt.effective_time,
100
106
  }
@@ -14,11 +14,26 @@ from .normalize import normalize_analyte
14
14
 
15
15
  # UCUM unit -> LOINC Property hint
16
16
  _UNIT_PROPERTY = {
17
+ # mass concentration
17
18
  "mg/dl": "MCnc", "g/dl": "MCnc", "ug/dl": "MCnc", "ng/ml": "MCnc",
18
- "mmol/l": "SCnc", "meq/l": "SCnc", "umol/l": "SCnc", "u/l": "CCnc",
19
+ "mg/l": "MCnc", "ug/l": "MCnc", "ng/dl": "MCnc", "pg/ml": "MCnc",
20
+ "g/l": "MCnc",
21
+ # substance concentration
22
+ "mmol/l": "SCnc", "meq/l": "SCnc", "umol/l": "SCnc", "nmol/l": "SCnc",
23
+ "pmol/l": "SCnc", "mol/l": "SCnc",
24
+ # catalytic (enzyme) concentration
25
+ "u/l": "CCnc", "iu/l": "CCnc", "u/ml": "CCnc",
26
+ # arbitrary / international-unit concentration
27
+ "iu/ml": "ACnc", "miu/ml": "ACnc", "iu/dl": "ACnc",
28
+ # number concentration (counts per volume) — CBC analytes (platelets, WBC, RBC)
29
+ "10*3/ul": "NCnc", "10*6/ul": "NCnc", "10*9/l": "NCnc", "10*12/l": "NCnc",
30
+ "10*3/l": "NCnc", "/ul": "NCnc", "/l": "NCnc", "k/ul": "NCnc",
31
+ "m/ul": "NCnc", "cells/ul": "NCnc", "/mm3": "NCnc",
32
+ # fractions / other
19
33
  "%": "MFr", "fl": "EntVol", "pg": "EntMass",
20
34
  "mm[hg]": "Pres", "/min": "NRat", "cel": "Temp", "kg": "Mass",
21
35
  "cm": "Len", "kg/m2": "Ratio", "[degf]": "Temp", "/min)": "NRat",
36
+ "s": "Time", "mm/h": "Vel",
22
37
  }
23
38
 
24
39
  _SPECIMEN_HINTS = {
@@ -143,6 +158,11 @@ def infer_specimen(stmt: ClinicalStatement) -> tuple[Optional[str], str, str]:
143
158
 
144
159
 
145
160
  def method_from_text(stmt: ClinicalStatement) -> Optional[str]:
161
+ # Structural <methodCode> (LOINC Method axis) is more reliable than text
162
+ # cues — use it first (e.g. "Automated"), then fall back to text keywords.
163
+ method = stmt.companion.get("method")
164
+ if method:
165
+ return method
146
166
  text = (stmt.raw_text or "").lower()
147
167
  for kw in ("fasting", "post", "challenge", "poc", "point of care", "random"):
148
168
  if kw in text:
@@ -150,6 +170,24 @@ def method_from_text(stmt: ClinicalStatement) -> Optional[str]:
150
170
  return None
151
171
 
152
172
 
173
+ # specimen words that name a specimen in analyte text (reuse the hint map)
174
+ def specimen_conflict(stmt: ClinicalStatement) -> bool:
175
+ """True when an EXPLICIT specimen disagrees with a specimen named in the
176
+ analyte text — a data-quality signal (LOINC_pattern_analysis §1.2). E.g. an
177
+ explicit "Urine specimen" on a "Platelets ... in Blood" observation."""
178
+ explicit = None
179
+ if stmt.companion.get("specimen") or stmt.companion.get("specimen_code"):
180
+ explicit = _specimen_to_hint(stmt.companion.get("specimen"),
181
+ stmt.companion.get("specimen_code"))
182
+ if not explicit:
183
+ return False
184
+ text = (stmt.raw_text or "").lower()
185
+ for word, hint in _SPECIMEN_HINTS.items():
186
+ if word in text and hint != explicit:
187
+ return True
188
+ return False
189
+
190
+
153
191
  def build_match_key(stmt: ClinicalStatement, expand_abbrev: bool = False) -> str:
154
192
  """Composite disambiguation key (component + specimen + property + scale + method).
155
193
 
@@ -191,9 +229,12 @@ def evidence_packet(stmt: ClinicalStatement, expand_abbrev: bool = False) -> dic
191
229
  "specimen_hint": specimen_hint,
192
230
  "specimen_source": specimen_source, # explicit | text | panel | none
193
231
  "specimen_confidence": specimen_conf, # high | medium | low | none
232
+ "specimen_conflict": specimen_conflict(stmt), # explicit vs analyte text
194
233
  "method_hint": method_from_text(stmt),
195
234
  "value": stmt.value.raw if stmt.value else None,
196
235
  "reference_range": stmt.companion.get("reference_range"),
236
+ "interpretation": stmt.companion.get("interpretation"),
237
+ "body_site": stmt.companion.get("body_site"),
197
238
  "panel": (stmt.panel.code if stmt.panel else None),
198
239
  "context": stmt.context,
199
240
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aimedicalcoding
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: Detect missing/broken clinical codes (LOINC / SNOMED CT / RxNorm / ICD-10-CM / CVX) in CCDA and FHIR documents, with recovery evidence
5
5
  Author: Aakash Kag, Vinay Shankar Miryala, Siva Karthik, Mohammed Rayaan
6
6
  License: MIT
@@ -63,9 +63,11 @@ Each record separates four concerns:
63
63
  "codings": [ ... ]
64
64
  },
65
65
  "qualifiers": { // disambiguation context (superset; nulls where N/A)
66
- "specimen": { "value","source","confidence" }, // LOINC
67
- "property": "MCnc", "scale": "Qn", // LOINC
66
+ "specimen": { "value","source","confidence","conflict" }, // LOINC; conflict=explicit specimen disagrees with analyte text
67
+ "property": "MCnc", "scale": "Qn", // LOINC (property from UCUM unit; NCnc for counts, etc.)
68
+ "method": "Automated", "body_site": "Right arm", // LOINC Method axis (<methodCode>) / <targetSiteCode>
68
69
  "laterality": null, "severity": null, "course": null, "negation": null, // SNOMED
70
+ "interpretation": "N", "reference_range": "150-400 10*3/uL", // observation companion data
69
71
  "panel": "24323-8", "effective_time": null
70
72
  }
71
73
  }
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="aimedicalcoding",
5
- version="0.3.5",
5
+ version="0.3.6",
6
6
  author="Aakash Kag, Vinay Shankar Miryala, Siva Karthik, Mohammed Rayaan",
7
7
  description=(
8
8
  "Detect missing/broken clinical codes (LOINC / SNOMED CT / RxNorm / "
File without changes