agentdata 0.3.26__py3-none-any.whl

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 (120) hide show
  1. agentdata/__init__.py +33 -0
  2. agentdata/__main__.py +4 -0
  3. agentdata/cache.py +115 -0
  4. agentdata/cli.py +173 -0
  5. agentdata/client.py +158 -0
  6. agentdata/company/__init__.py +3 -0
  7. agentdata/company/cf.py +145 -0
  8. agentdata/company/ch_zefix.py +100 -0
  9. agentdata/company/cli.py +243 -0
  10. agentdata/company/co_rues.py +72 -0
  11. agentdata/company/edgar_cik.py +84 -0
  12. agentdata/company/edgar_fts.py +101 -0
  13. agentdata/company/faces.py +746 -0
  14. agentdata/company/fdic_bankfind.py +107 -0
  15. agentdata/company/fi_ytj.py +89 -0
  16. agentdata/company/fr_recherche.py +124 -0
  17. agentdata/company/fts.py +121 -0
  18. agentdata/company/gleif.py +178 -0
  19. agentdata/company/kr_hometax.py +112 -0
  20. agentdata/company/no_brreg.py +124 -0
  21. agentdata/company/ror.py +105 -0
  22. agentdata/company/ted.py +117 -0
  23. agentdata/company/usaspending.py +124 -0
  24. agentdata/company/vies.py +66 -0
  25. agentdata/daterange.py +35 -0
  26. agentdata/economy/__init__.py +10 -0
  27. agentdata/economy/bis.py +132 -0
  28. agentdata/economy/cli.py +199 -0
  29. agentdata/economy/ecb.py +117 -0
  30. agentdata/economy/eurostat.py +163 -0
  31. agentdata/economy/faces.py +573 -0
  32. agentdata/economy/fred.py +92 -0
  33. agentdata/economy/ilo_stat.py +99 -0
  34. agentdata/economy/imf_sdmx.py +133 -0
  35. agentdata/economy/un_comtrade.py +199 -0
  36. agentdata/economy/unsd_sdg.py +154 -0
  37. agentdata/economy/wb.py +208 -0
  38. agentdata/economy/who_xmart.py +177 -0
  39. agentdata/entries.py +30 -0
  40. agentdata/errors.py +38 -0
  41. agentdata/esg/__init__.py +5 -0
  42. agentdata/esg/cli.py +180 -0
  43. agentdata/esg/climatetrace.py +260 -0
  44. agentdata/esg/eiti.py +187 -0
  45. agentdata/esg/epa_frs.py +138 -0
  46. agentdata/esg/euasl.py +75 -0
  47. agentdata/esg/eutl.py +66 -0
  48. agentdata/esg/faces.py +498 -0
  49. agentdata/esg/paris_mou.py +240 -0
  50. agentdata/esg/thetis_mrv.py +67 -0
  51. agentdata/healthcare/__init__.py +7 -0
  52. agentdata/healthcare/au_tga.py +142 -0
  53. agentdata/healthcare/ca_dpd.py +123 -0
  54. agentdata/healthcare/ca_mdall.py +155 -0
  55. agentdata/healthcare/cli.py +375 -0
  56. agentdata/healthcare/ctgov.py +101 -0
  57. agentdata/healthcare/dailymed.py +102 -0
  58. agentdata/healthcare/epmc.py +108 -0
  59. agentdata/healthcare/eudamed.py +124 -0
  60. agentdata/healthcare/faces.py +811 -0
  61. agentdata/healthcare/fda_crl.py +86 -0
  62. agentdata/healthcare/fda_devices.py +199 -0
  63. agentdata/healthcare/fda_drugs.py +156 -0
  64. agentdata/healthcare/fda_recalls.py +137 -0
  65. agentdata/healthcare/gb_mhra.py +137 -0
  66. agentdata/healthcare/gudid.py +84 -0
  67. agentdata/healthcare/mesh.py +89 -0
  68. agentdata/healthcare/rasff.py +164 -0
  69. agentdata/healthcare/rxnav.py +67 -0
  70. agentdata/healthcare/safety_gate.py +175 -0
  71. agentdata/healthcare/shortages.py +107 -0
  72. agentdata/legal/__init__.py +4 -0
  73. agentdata/legal/afdb.py +93 -0
  74. agentdata/legal/ca_sema.py +143 -0
  75. agentdata/legal/cli.py +312 -0
  76. agentdata/legal/co_sic.py +70 -0
  77. agentdata/legal/courtlistener.py +77 -0
  78. agentdata/legal/csl.py +139 -0
  79. agentdata/legal/dgcomp.py +125 -0
  80. agentdata/legal/ecfr.py +129 -0
  81. agentdata/legal/faces.py +700 -0
  82. agentdata/legal/federalregister.py +188 -0
  83. agentdata/legal/gb_cma.py +117 -0
  84. agentdata/legal/gb_tna_caselaw.py +245 -0
  85. agentdata/legal/idb.py +85 -0
  86. agentdata/legal/lda.py +147 -0
  87. agentdata/legal/sipri.py +178 -0
  88. agentdata/legal/ukec.py +148 -0
  89. agentdata/legal/wb.py +92 -0
  90. agentdata/procurement/__init__.py +5 -0
  91. agentdata/procurement/br_pncp.py +126 -0
  92. agentdata/procurement/cli.py +155 -0
  93. agentdata/procurement/co_secop.py +88 -0
  94. agentdata/procurement/faces.py +239 -0
  95. agentdata/procurement/ny_open_data.py +82 -0
  96. agentdata/procurement/ungm.py +137 -0
  97. agentdata/procurement/wb_proc.py +84 -0
  98. agentdata/research/__init__.py +4 -0
  99. agentdata/research/biorxiv.py +66 -0
  100. agentdata/research/cli.py +129 -0
  101. agentdata/research/crossref.py +121 -0
  102. agentdata/research/faces.py +349 -0
  103. agentdata/research/gb_gtr.py +58 -0
  104. agentdata/research/icite.py +52 -0
  105. agentdata/research/nih_reporter.py +143 -0
  106. agentdata/research/nsf.py +68 -0
  107. agentdata/research/openalex.py +145 -0
  108. agentdata/research/opencitations.py +50 -0
  109. agentdata/security/__init__.py +3 -0
  110. agentdata/security/cisa_kev.py +42 -0
  111. agentdata/security/cli.py +37 -0
  112. agentdata/security/faces.py +91 -0
  113. agentdata/security/osv.py +72 -0
  114. agentdata/transport.py +541 -0
  115. agentdata-0.3.26.dist-info/METADATA +266 -0
  116. agentdata-0.3.26.dist-info/RECORD +120 -0
  117. agentdata-0.3.26.dist-info/WHEEL +5 -0
  118. agentdata-0.3.26.dist-info/entry_points.txt +10 -0
  119. agentdata-0.3.26.dist-info/licenses/LICENSE +201 -0
  120. agentdata-0.3.26.dist-info/top_level.txt +1 -0
@@ -0,0 +1,155 @@
1
+ """ca_mdall — Health Canada Medical Devices Active Licence Listing (MDALL) face provider
2
+ (health-products.canada.ca; keyless — the same host as ca_dpd).
3
+
4
+ healthcare domain (0.3.19; admitted 2026-11-02, probed). Shapes verified by first-hand live probing
5
+ 2026-11-02 (the probe corpus lives in tmp/probe-ca-eu-faces/):
6
+ GET /api/medical-devices/company/?lang=en&type=json
7
+ → the MDALL company register in ONE shot (no pagination surface — 7,682 rows measured 2026-11-02;
8
+ rows carry company_id (the MDALL join key) + company_name + company_status A/I + postal address
9
+ + country_cd/region_cd — a rare two-country register: US manufacturers dominate the volume).
10
+ GET /mdall-limh/information?licenceId=111577&type=active&lang=eng
11
+ → the single-licence dossier as a server-rendered HTML page (the LIMH = Licence Listing search
12
+ application the site itself uses). **Pit nailed: the search POST (prepareSearch → option=4) is a
13
+ PRG dance needing a CSRF pairing, but the information GET it redirects to works stateless and
14
+ session-free — the point-lookup face skips the dance entirely.** A browser-looking referer/cookie
15
+ is NOT required (a bare GET returns 200). The page shape:
16
+ h2 Manufacturer → name span + em/span address lines + "Company ID:" span;
17
+ "Licence No.:" h3 → licence span; Type / Device class / Device first issue date / Licence name
18
+ label→span pairs; a "Device details" table whose td[headers*=idname] cells are the device
19
+ identifiers (one row per identifier, grouped under th[namedev*] device names).
20
+ A 200 page WITHOUT the "Licence No.:" block = no such licence at that type (active vs archived
21
+ are separate indexes — an archived licence under type=active returns the empty page, not the record).
22
+ Param-minimalism pitfall (same host family as ca_dpd): unprobed params are silently ignored or dump
23
+ full registers — **the /api/medical-devices/device/ endpoint has NO working server-side filter (a
24
+ licence_no filter is ignored → the full 22.8MB device register), which is why the device face here
25
+ is the limh point lookup, not a device listing** — do not widen.
26
+ Local row slicing (limit/skip) is a post-slice over the single cached full shot.
27
+ TTL: companies 7 days (a slowly-changing register) / licence 7 days (issued licences change slowly);
28
+ polite rate 1 rps (the host publishes no quota; shared host with ca_dpd — the host bucket coordinates).
29
+ """
30
+ import re
31
+
32
+ from ..errors import SdkError
33
+
34
+ provider = {
35
+ "id": "ca_mdall",
36
+ "baseUrl": "https://health-products.canada.ca",
37
+ "rate": {"rps": 1, "burst": 2},
38
+ "ops": {
39
+ "companies": {"path": "/api/medical-devices/company/"}, # params: lang/type fixed for this face
40
+ "licence": {"path": "/mdall-limh/information", "accept": "text"}, # params: licenceId/type/lang (HTML dossier)
41
+ },
42
+ "ttl": {"companies": 7 * 86400, "licence": 7 * 86400},
43
+ }
44
+
45
+ # —— HTML dossier anchors (server-rendered WET page; regex parse — the same style as the ungm row family) ——
46
+ _TAGWS = re.compile(r"<[^>]+>|\s+")
47
+ _MFG = re.compile(r"<h2>Manufacturer</h2>\s*<span\s*>(.*?)</span>", re.S)
48
+ _EM_ADDR = re.compile(r"<em>\s*<span[^>]*>(.*?)</span>\s*</em>", re.S)
49
+ _COMPANY_ID = re.compile(r'Company ID:</span></strong>\s*<span\s*>(.*?)</span>', re.S)
50
+ _LABELLED = re.compile(r'<span\s*>(Licence No\.:|Type:|Device class:|Device first issue date:|Licence name:)</span>\s*(?:</strong>)?\s*<span\s*>(.*?)</span>', re.S)
51
+ # the Licence No. pair sits in its own h3 (no </strong> after it) — a second pattern for that shape:
52
+ _LICNO_H3 = re.compile(r'<h3><span\s*>Licence No\.:</span>\s*<span\s*>(.*?)</span>\s*</h3>', re.S)
53
+ _DEVICE_NAME = re.compile(r'<th id="namedev(\d+)"[^>]*>(.*?)</th>', re.S)
54
+ _DEVICE_ID = re.compile(r'<td headers="datedev(\d+) namedev\d+ idname">(.*?)</td>', re.S)
55
+ _NO_RESULT = re.compile(r'Licence No\.:') # absence of this marker = the empty page
56
+
57
+
58
+ def _txt(x):
59
+ from html import unescape
60
+ return _TAGWS.sub(" ", unescape(x or "")).strip() or None
61
+
62
+
63
+ def licence_from_upstream(html, licence_id):
64
+ """LIMH information page HTML → the licence dossier (pure function, offline-testable).
65
+
66
+ The labelled pairs come in two shapes (Licence No. lives in an h3; the rest after a </strong>) —
67
+ both shapes are collected; device identifiers are flattened with their group device name."""
68
+ if not _NO_RESULT.search(html or ""):
69
+ return None # the 200 empty page = no such licence at this type
70
+ fields = dict(_LABELLED.findall(html))
71
+ lic = _LICNO_H3.search(html)
72
+ if lic:
73
+ fields["Licence No.:"] = lic.group(1)
74
+ names = {int(i): _txt(x) for i, x in _DEVICE_NAME.findall(html)}
75
+ # each identifier row names its device group via the headers attribute (datedevN namedevN idname) — the group index is the join
76
+ identifiers = [{"device_name": names.get(int(g)), "device_identifier": _txt(x)}
77
+ for g, x in _DEVICE_ID.findall(html)]
78
+ return {
79
+ "licence_id": str(licence_id),
80
+ "licence_no": _txt(fields.get("Licence No.:")),
81
+ "licence_name": _txt(fields.get("Licence name:")),
82
+ "licence_type": _txt(fields.get("Type:")), # e.g. Device Family
83
+ "device_class": _txt(fields.get("Device class:")),
84
+ "first_issue_date": _txt(fields.get("Device first issue date:")),
85
+ "manufacturer": None if not _MFG.search(html) else {
86
+ "name": _txt(_MFG.search(html).group(1)),
87
+ "address": [_txt(x) for x in _EM_ADDR.findall(html) if _txt(x)],
88
+ "company_id": _txt(_COMPANY_ID.search(html).group(1)) if _COMPANY_ID.search(html) else None,
89
+ },
90
+ "device_identifiers": identifiers,
91
+ "device_names": [n for n in names.values() if n],
92
+ }
93
+
94
+
95
+ def companies_from_upstream(data):
96
+ """Upstream company array → company rows (pure function, offline-testable)."""
97
+ rows = [{
98
+ "company_id": r.get("company_id"), # the MDALL join key
99
+ "company_name": r.get("company_name"),
100
+ "company_status": r.get("company_status"), # A = active / I = inactive
101
+ "address": {k: r.get(k) for k in ("addr_line_1", "addr_line_2", "addr_line_3",
102
+ "city", "region_cd", "postal_code", "country_cd")},
103
+ } for r in (data or []) if isinstance(r, dict)]
104
+ return {"rows": rows, "count": len(rows)}
105
+
106
+
107
+ def companies_raw(*, limit=200, skip=0, cache=None, no_cache=False):
108
+ """Flat-panel low-level face: the MDALL company register, one full shot (7,682 rows measured
109
+ 2026-11-02) cached whole; limit/skip is a local post-slice.
110
+
111
+ → {rows[], count, total, params, _meta} — total = the whole register size (not the page)."""
112
+ from ..transport import call_provider
113
+
114
+ off = int(skip) if skip is not None else 0
115
+ if off < 0:
116
+ raise SdkError("usage_error", provider="ca_mdall", detail=f"skip must be >=0, got \"{skip}\"")
117
+ size = int(limit) if limit is not None else 200
118
+ if size < 1:
119
+ raise SdkError("usage_error", provider="ca_mdall", detail=f"limit must be >=1 (local post-slice, no upstream cap), got \"{limit}\"")
120
+ params = {"lang": "en", "type": "json"}
121
+ r = call_provider(provider, "companies", params, cache=cache, no_cache=no_cache)
122
+ out = companies_from_upstream(r["data"])
123
+ return {"rows": out["rows"][off:off + size], "count": min(size, max(0, out["count"] - off)),
124
+ "total": out["count"], "params": params, "_meta": r}
125
+
126
+
127
+ def licence_raw(licence_id, *, licence_type="active", lang="eng", cache=None, no_cache=False):
128
+ """Flat-panel low-level face: the MDALL single-licence dossier (LIMH information page, stateless GET).
129
+
130
+ licence_id = the numeric MDALL licence number (e.g. 111577); licence_type = active|archived
131
+ (separate upstream indexes — an archived licence must be asked for with type=archived);
132
+ lang = eng|fra. A 200 page without the licence block = not_found (no such licence at that type).
133
+ → {licence dossier…, params, _meta}."""
134
+ from ..transport import call_provider
135
+
136
+ v = str(licence_id or "").strip()
137
+ if not v.isdigit():
138
+ raise SdkError("usage_error", provider="ca_mdall",
139
+ detail=f"licence_id must be the numeric MDALL licence number (e.g. 111577, echoed from the LIMH listing), got \"{licence_id}\"")
140
+ t = str(licence_type or "active").strip()
141
+ if t not in ("active", "archived"):
142
+ raise SdkError("usage_error", provider="ca_mdall",
143
+ detail=f"licence_type must be active|archived (two separate upstream indexes — an inactive licence lives under archived), got \"{licence_type}\"")
144
+ lg = str(lang or "eng").strip()
145
+ if lg not in ("eng", "fra"):
146
+ raise SdkError("usage_error", provider="ca_mdall", detail=f"lang must be eng|fra, got \"{lang}\"")
147
+ params = {"licenceId": v, "type": t, "lang": lg}
148
+ r = call_provider(provider, "licence", params, cache=cache, no_cache=no_cache)
149
+ dossier = licence_from_upstream(r["data"] or "", v)
150
+ if dossier is None:
151
+ raise SdkError("upstream_not_found", provider="ca_mdall",
152
+ detail=f"no MDALL licence {v} under type={t} (the page returns 200 empty — try licence_type=archived)")
153
+ # the index param echoes under its upstream name "type"; the dossier's own licence_type is the
154
+ # page's "Type:" field (Device Family …) — the two live side by side, no clobbering
155
+ return {**dossier, "type": t, "params": params, "_meta": r}
@@ -0,0 +1,375 @@
1
+ """healthcare cli — domain command face registration (split per domain in 0.2.6, moved in from the root cli.py; purely flat since the 0.3.0 verb-layer + sugar removal).
2
+
3
+ register(v, g): registers flat commands on the domain subparser (g = the root _leaf_globals factory,
4
+ passed in as a parameter to avoid a cli ↔ domain-cli cycle); FLAT = the flat dispatch table, merged by the root cli for main to use.
5
+ """
6
+ import argparse
7
+
8
+ def register(v, g):
9
+ # —— Flat panel commands (healthcare main face: source-qualified names + upstream params under original names + our defaults; the only face family since 0.3.0) ——
10
+ f1 = v.add_parser("au_tga_artg", parents=[g()],
11
+ help="AU ARTG registered products (TGA eBS; --query raw words)",
12
+ description="Australian Register of Therapeutic Goods: product×sponsor rows (97,988 entries; sponsor = Australian marketing accountability entity)")
13
+ f1.add_argument("--query", default=None, help="Free-text words, AND across all fields (upstream param: Query; quoted phrase narrows onto the sponsor)")
14
+ f1.add_argument("--limit", type=int, default=20, help="Rows to return (upstream param: Count; cap 100, default 20)")
15
+ f1.add_argument("--skip", type=int, default=0, help="Rows to skip (upstream param: Start; default 0)")
16
+
17
+ f2 = v.add_parser("au_tga_manufacturers", parents=[g()],
18
+ help="AU medical-device manufacturer licences (TGA MIS, single-shot full register)",
19
+ description="Australian manufacturer licence register (444 licences, one full shot + state filter); licence × manufacturer edge")
20
+ f2.add_argument("--state", default=None, help="State filter, e.g. QLD / NSW / VIC (local post-filter on SiteState)")
21
+ f2.add_argument("--limit", type=int, default=100, help="Rows to return (upstream param: count; cap 500, default 100)")
22
+
23
+ # —— 0.3.19 CA + EU product-safety faces (born flat: upstream param original names + closed-set validation; probed 2026-11-02) ——
24
+
25
+ f3 = v.add_parser("ca_dpd_companies", parents=[g()],
26
+ help="CA DPD company register (one full shot; local limit/skip slice)",
27
+ description="Health Canada Drug Product Database companies: 5,253 DIN owners with company_code join key + postal address (no upstream paging — one cached full register)")
28
+ f3.add_argument("--limit", type=int, default=200, help="Rows to return (local post-slice over the single cached full shot)")
29
+ f3.add_argument("--skip", type=int, default=0, help="Rows to skip (local post-slice)")
30
+
31
+ f4 = v.add_parser("ca_dpd_drug_products", parents=[g()],
32
+ help="CA DPD drug products by market status (status closed set)",
33
+ description="Drug Product Database products: DIN + brand + company rows, filtered by the Read Me status closed set — status is required (an unknown value is silently ignored upstream and dumps the whole 15MB register)")
34
+ f4.add_argument("--status", type=int, required=True, help="Market status, closed set 1=Approved 2=Marketed 3=Cancelled Pre-Market 4=Cancelled Post-Market 6=Dormant (validated: any other value silently dumps the full 15MB register)")
35
+ f4.add_argument("--limit", type=int, default=200, help="Rows to return (local post-slice)")
36
+ f4.add_argument("--skip", type=int, default=0, help="Rows to skip (local post-slice)")
37
+
38
+ f5 = v.add_parser("ca_mdall_companies", parents=[g()],
39
+ help="CA MDALL medical-device company register (one full shot; local limit/skip slice)",
40
+ description="Medical Devices Active Licence Listing companies: 7,682 device makers/importers (a two-country register — US rows carry country_cd US) with company_id join key + active/inactive status")
41
+ f5.add_argument("--limit", type=int, default=200, help="Rows to return (local post-slice over the single cached full shot)")
42
+ f5.add_argument("--skip", type=int, default=0, help="Rows to skip (local post-slice)")
43
+
44
+ f6 = v.add_parser("ca_mdall_licence", parents=[g()],
45
+ help="CA MDALL single-licence dossier (LIMH information page)",
46
+ description="The licence dossier: manufacturer block + Company ID + device class + the device-identifier family — active|archived are separate upstream indexes (ask for inactive licences with --licence-type archived)")
47
+ f6.add_argument("--licence-id", dest="licence_id", required=True, help="Numeric MDALL licence number (upstream param: licenceId; e.g. 111577)")
48
+ f6.add_argument("--licence-type", dest="licence_type", default="active", choices=["active", "archived"], help="Licence index (upstream param: type; default active)")
49
+ f6.add_argument("--lang", default="eng", choices=["eng", "fra"], help="Language (upstream param: lang; default eng)")
50
+
51
+ f7 = v.add_parser("ctgov_studies", parents=[g()],
52
+ help="Clinical-trial search (CT.gov v2; --query.term upstream syntax)",
53
+ description="Clinical trials: raw syntax via --query.term, structured --query.cond/--filter.advanced combine with either; bare call = first page of all studies (legal upstream)")
54
+ f7.add_argument("--query.term", dest="query_term", default=None, help="Raw query expression (upstream param: query.term; AREA[…] syntax, e.g. 'AREA[LeadSponsorName](\"Pfizer\")')")
55
+ f7.add_argument("--query.titles", dest="query_titles", default=None, help="Title-field search (upstream param: query.titles; combinable; aspirin 2983→967)")
56
+ f7.add_argument("--query.intr", dest="query_intr", default=None, help="Intervention-field search (upstream param: query.intr; combinable; insulin 21109→8229)")
57
+ f7.add_argument("--query.cond", dest="query_cond", default=None, help="Condition search (upstream param: query.cond; combinable)")
58
+ f7.add_argument("--filter.advanced", dest="filter_advanced", default=None, help="Advanced filter, AREA[…] syntax (upstream param: filter.advanced; combinable)")
59
+ f7.add_argument("--agg-filters", dest="agg_filters", default=None, help="Facet filter (upstream param: aggFilters; numeric enum values, e.g. phase:3 — PHASE3 → 0 hits)")
60
+ f7.add_argument("--fields", default=None, help="Row slimming, comma-separated (upstream param: fields; e.g. NCTId,BriefTitle)")
61
+ f7.add_argument("--page-size", dest="page_size", type=int, default=20, help="Rows per page (upstream param: pageSize; default 20, no enforced cap)")
62
+ f7.add_argument("--page-token", dest="page_token", default=None, help="Pagination token (upstream param: pageToken; feed next_page_token)")
63
+ f7.add_argument("--sort", default=None, help="Sort (upstream param: sort; e.g. EnrollmentCount:desc)")
64
+ f7.add_argument("--count-total", dest="count_total", action=argparse.BooleanOptionalAction, default=True,
65
+ help="Request totalCount (upstream param: countTotal; default on)")
66
+
67
+ f8 = v.add_parser("dailymed_spls", parents=[g()],
68
+ help="Drug labeling SPL metadata (DailyMed spls.json)",
69
+ description="SPL labeling metadata: setid + title + source URL (full-text XML via the URL); manufacturer/setid filters and page/pagesize/sort pass through upstream verbatim")
70
+ f8.add_argument("--search", default=None, help="Drug name (upstream param: drug_name; raw channel)")
71
+ f8.add_argument("--manufacturer", default=None, help="Labeler filter (upstream param: manufacturer; e.g. Pfizer)")
72
+ f8.add_argument("--setid", default=None, help="Exact SPL setid filter (upstream param: setid)")
73
+ f8.add_argument("--page", type=int, default=None, help="Page number (upstream param: page)")
74
+ f8.add_argument("--pagesize", type=int, default=None, help="Rows per page (upstream param: pagesize; silent clamp at 100 — echoed as elements_per_page)")
75
+ f8.add_argument("--sort", default=None, help="Sort (upstream param: sort; e.g. published_date)")
76
+
77
+ f9 = v.add_parser("epmc_search", parents=[g()],
78
+ help="Literature evidence (Europe PMC; --search upstream syntax)",
79
+ description="Literature search incl. full PubMed: raw query syntax via --search (AFF: for affiliations); cursor pagination via next_cursor_mark")
80
+ f9.add_argument("--search", default=None, help="Raw query expression (upstream param: query; e.g. 'AFF:\"Novartis\" AND \"psoriasis\"')")
81
+ f9.add_argument("--result-type", dest="result_type", default="lite", choices=["lite", "core", "full", "idlist"],
82
+ help="Row depth (upstream param: resultType; default lite)")
83
+ f9.add_argument("--page-size", dest="page_size", type=int, default=25, help="Rows per page (upstream param: pageSize; 1-1000, default 25)")
84
+ f9.add_argument("--cursor-mark", dest="cursor_mark", default="*", help="Pagination cursor (upstream param: cursorMark; * = first page)")
85
+ f9.add_argument("--sort", default=None, help="Sort (upstream param: sort; e.g. 'CITED desc')")
86
+
87
+ f10 = v.add_parser("eudamed_devices_udi_di_data", parents=[g()],
88
+ help="EU device master record (EUDAMED primaryDi exact + upstream pagination)",
89
+ description="EU device record: manufacturer SRN + EU authorised-rep SRN edges (cold ~50s — primaryDi is unindexed upstream)")
90
+ f10.add_argument("--primary-di", dest="primary_di", required=True, help="EU UDI-DI (upstream param: primaryDi)")
91
+ f10.add_argument("--page", type=int, default=0, help="Page number (upstream param: page; default 0)")
92
+ f10.add_argument("--page-size", dest="page_size", type=int, default=5, help="Rows per page (upstream param: pageSize; default 5)")
93
+ f10.add_argument("--iso2-code", dest="iso2_code", default="en", help="Language (upstream param: iso2Code; default en)")
94
+
95
+ f11 = v.add_parser("fda_crl_letters", parents=[g()],
96
+ help="FDA Complete Response Letters (openFDA transparency/crl; upstream-verbatim params)",
97
+ description="Complete Response Letters: company × negative-approval events (approval_status = application outcome "
98
+ "after the letter). company_rep (person name, PII) and text (OCR noise) are dropped at the row-shaping layer. "
99
+ "Zero hits = valid answer (404 NOT_FOUND)")
100
+ f11.add_argument("--search", default=None, help="Raw query expression (upstream param: search; e.g. 'company_name:\"Firm Name\"' — required)")
101
+ f11.add_argument("--limit", type=int, default=20, help="Rows to return (upstream param: limit; 1-1000 hard wall, default 20)")
102
+ f11.add_argument("--skip", type=int, default=0, help="Rows to skip (upstream param: skip; default 0)")
103
+ f11.add_argument("--sort", default="letter_date:desc", help="Sort (upstream param: sort; our default letter_date:desc)")
104
+
105
+ # —— Wave 10b openFDA family, seven faces (2026-10-29; born flat: upstream param original names only, zero sugar, zero deprecation debt) ——
106
+
107
+ f12 = v.add_parser("fda_device_510k", parents=[g()],
108
+ help="FDA device 510(k) clearances (openFDA device/510k; upstream-verbatim params)",
109
+ description="Premarket notification clearances (176,466 rows): k_number × applicant with decision "
110
+ "code/date. contact (person name, PII) and the openfda blob (100+ registration numbers "
111
+ "per row) are dropped at the row-shaping layer. Zero hits = valid answer")
112
+ f12.add_argument("--search", default=None, help="Raw query expression (upstream param: search; e.g. 'applicant:\"Medtronic\"' — required)")
113
+ f12.add_argument("--limit", type=int, default=20, help="Rows to return (upstream param: limit; 1-1000 hard wall, default 20)")
114
+ f12.add_argument("--skip", type=int, default=0, help="Rows to skip (upstream param: skip; default 0)")
115
+ f12.add_argument("--sort", default=None, help="Sort (upstream param: sort; our default decision_date:desc)")
116
+
117
+ f13 = v.add_parser("fda_device_classification", parents=[g()],
118
+ help="FDA device product-code classification (openFDA device/classification; upstream-verbatim params)",
119
+ description="Product-code taxonomy (device_name × product_code × device_class with 21 CFR regulation "
120
+ "number); no date field, so no default sort. product_code joins the 510(k)/PMA/recall "
121
+ "faces. Zero hits = valid answer")
122
+ f13.add_argument("--search", default=None, help="Raw query expression (upstream param: search; e.g. 'device_name:\"syringe\"' — required)")
123
+ f13.add_argument("--limit", type=int, default=20, help="Rows to return (upstream param: limit; 1-1000 hard wall, default 20)")
124
+ f13.add_argument("--skip", type=int, default=0, help="Rows to skip (upstream param: skip; default 0)")
125
+ f13.add_argument("--sort", default=None, help="Sort (upstream param: sort; no default — this face has no date field)")
126
+
127
+ f14 = v.add_parser("fda_device_pma", parents=[g()],
128
+ help="FDA device PMA approvals (openFDA device/pma; upstream-verbatim params)",
129
+ description="Premarket approvals + supplements (12,896 rows): pma_number × applicant with decision "
130
+ "code/date. ao_statement (long text) and street-level address are dropped. "
131
+ "Zero hits = valid answer")
132
+ f14.add_argument("--search", default=None, help="Raw query expression (upstream param: search; e.g. 'applicant:\"Medtronic\"' — required)")
133
+ f14.add_argument("--limit", type=int, default=20, help="Rows to return (upstream param: limit; 1-1000 hard wall, default 20)")
134
+ f14.add_argument("--skip", type=int, default=0, help="Rows to skip (upstream param: skip; default 0)")
135
+ f14.add_argument("--sort", default=None, help="Sort (upstream param: sort; our default decision_date:desc)")
136
+
137
+ f15 = v.add_parser("fda_device_recalls", parents=[g()],
138
+ help="FDA device recalls (openFDA device/recall RES system; upstream-verbatim params)",
139
+ description="Device recalls from the RES system (incl. Ongoing full lifecycle; a different system from the bulk-loaded "
140
+ "device/enforcement press releases). Rows carry firm_fei_number + k_numbers hub keys. "
141
+ "Firm matching uses the phrase form (probed 2026-10-28)")
142
+ f15.add_argument("--search", default=None, help="Raw query expression (upstream param: search; e.g. 'recalling_firm:\"Firm Name\"' phrase — .exact unprobed; required)")
143
+ f15.add_argument("--limit", type=int, default=20, help="Rows to return (upstream param: limit; 1-1000 hard wall, default 20)")
144
+ f15.add_argument("--skip", type=int, default=0, help="Rows to skip (upstream param: skip; default 0)")
145
+ f15.add_argument("--sort", default=None, help="Sort (upstream param: sort; our default event_date_posted:desc)")
146
+
147
+ f16 = v.add_parser("fda_device_registrations", parents=[g()],
148
+ help="FDA device establishment registrations (openFDA device/registrationlisting; upstream-verbatim params)",
149
+ description="Establishment registration & listing (336,946 rows, quarterly): registration_number/fei_number "
150
+ "× firm_name. All searchable fields live in the registration.* namespace (top-level "
151
+ "firm_name/fei_number don't match — probed 2026-10-29). us_agent and "
152
+ "official_correspondent (person contacts, PII) are dropped. Zero hits = valid answer")
153
+ f16.add_argument("--search", default=None, help="Raw query expression (upstream param: search; e.g. 'registration.name:\"Firm Name\"' — required)")
154
+ f16.add_argument("--limit", type=int, default=20, help="Rows to return (upstream param: limit; 1-1000 hard wall, default 20)")
155
+ f16.add_argument("--skip", type=int, default=0, help="Rows to skip (upstream param: skip; default 0)")
156
+ f16.add_argument("--sort", default=None, help="Sort (upstream param: sort; our default registration.reg_expiry_date_year:desc)")
157
+
158
+ f17 = v.add_parser("fda_drug_approvals", parents=[g()],
159
+ help="FDA drug application/approval records (openFDA drug/drugsfda; upstream-verbatim params)",
160
+ description="Drugs@FDA application dossiers (29,420 applications): application_number × sponsor_name "
161
+ "with products[] and submissions[] timelines. Company search uses sponsor_name "
162
+ "(applicant_name is a docs illusion — probed 2026-10-29). Zero hits = valid answer")
163
+ f17.add_argument("--search", default=None, help="Raw query expression (upstream param: search; e.g. 'sponsor_name:\"TEVA\"' — keyword field is case-sensitive, match stored form; required)")
164
+ f17.add_argument("--limit", type=int, default=20, help="Rows to return (upstream param: limit; 1-1000 hard wall, default 20)")
165
+ f17.add_argument("--skip", type=int, default=0, help="Rows to skip (upstream param: skip; default 0)")
166
+ f17.add_argument("--sort", default=None, help="Sort (upstream param: sort; our default submissions.submission_status_date:desc)")
167
+
168
+ f18 = v.add_parser("fda_drug_catalog", parents=[g()],
169
+ help="FDA NDC directory (openFDA drug/ndc; upstream-verbatim params)",
170
+ description="NDC product catalog (daily refresh): product_ndc × labeler_name with marketing dates and "
171
+ "category — NSDE content lives here (the separate /drug/nsde.json endpoint is dead, "
172
+ "probed 2026-10-29). Zero hits = valid answer")
173
+ f18.add_argument("--search", default=None, help="Raw query expression (upstream param: search; e.g. 'labeler_name:\"pfizer\"' — required)")
174
+ f18.add_argument("--limit", type=int, default=20, help="Rows to return (upstream param: limit; 1-1000 hard wall, default 20)")
175
+ f18.add_argument("--skip", type=int, default=0, help="Rows to skip (upstream param: skip; default 0)")
176
+ f18.add_argument("--sort", default=None, help="Sort (upstream param: sort; our default marketing_start_date:desc)")
177
+
178
+ f19 = v.add_parser("fda_drug_recalls", parents=[g()],
179
+ help="FDA drug recalls (openFDA drug/enforcement; upstream-verbatim params)",
180
+ description="Drug recall/enforcement records 2012+ (press-release source — drug has no separate recall endpoint): "
181
+ "recall_number × recalling_firm with class/status/reason. Zero hits = valid answer (404 NOT_FOUND)")
182
+ f19.add_argument("--search", default=None, help="Raw query expression (upstream param: search; e.g. 'recalling_firm.exact:\"Firm Name\"' — required)")
183
+ f19.add_argument("--limit", type=int, default=20, help="Rows to return (upstream param: limit; 1-1000 hard wall, default 20)")
184
+ f19.add_argument("--skip", type=int, default=0, help="Rows to skip (upstream param: skip; default 0)")
185
+ f19.add_argument("--sort", default=None, help="Sort (upstream param: sort; our default report_date:desc)")
186
+
187
+ f20 = v.add_parser("fda_drug_shortages", parents=[g()],
188
+ help="FDA drug shortages (openFDA; --search upstream syntax)",
189
+ description="Drug-shortage records: supply-risk event stream (status/availability/reason); limit 1-1000 is an upstream hard wall, deeper via --skip")
190
+ f20.add_argument("--search", default=None, help="Raw query expression (upstream param: search; e.g. 'generic_name:\"bupivacaine\"' or 'openfda.manufacturer_name.exact:\"Fresenius Kabi USA, LLC\"')")
191
+ f20.add_argument("--limit", type=int, default=20, help="Rows to return (upstream param: limit; 1-1000 hard wall, default 20)")
192
+ f20.add_argument("--skip", type=int, default=0, help="Rows to skip (upstream param: skip; default 0)")
193
+ f20.add_argument("--sort", default="update_date:desc", help="Sort (upstream param: sort; our default update_date:desc)")
194
+
195
+ f21 = v.add_parser("fda_food_recalls", parents=[g()],
196
+ help="FDA food recalls (openFDA food/enforcement; upstream-verbatim params)",
197
+ description="Food recall/enforcement records 2012+: recall_number × recalling_firm with class/status/reason. "
198
+ "Zero hits = valid answer (404 NOT_FOUND)")
199
+ f21.add_argument("--search", default=None, help="Raw query expression (upstream param: search; e.g. 'recalling_firm.exact:\"Firm Name\"' — required)")
200
+ f21.add_argument("--limit", type=int, default=20, help="Rows to return (upstream param: limit; 1-1000 hard wall, default 20)")
201
+ f21.add_argument("--skip", type=int, default=0, help="Rows to skip (upstream param: skip; default 0)")
202
+ f21.add_argument("--sort", default=None, help="Sort (upstream param: sort; our default report_date:desc)")
203
+
204
+ f22 = v.add_parser("fda_orangebook", parents=[g()],
205
+ help="FDA Orange Book approved drugs (openFDA drug/orangebook; upstream-verbatim params)",
206
+ description="Orange Book approved-drug products (48,865 rows): approval_date × application holder with "
207
+ "RLD/reference-standard flags. Company keys sit inside products[] (application_name / "
208
+ "application_full_name — top-level applicant fields don't exist, probed 2026-10-29). "
209
+ "Zero hits = valid answer")
210
+ f22.add_argument("--search", default=None, help="Raw query expression (upstream param: search; e.g. 'products.application_full_name:\"Teva\"' — required)")
211
+ f22.add_argument("--limit", type=int, default=20, help="Rows to return (upstream param: limit; 1-1000 hard wall, default 20)")
212
+ f22.add_argument("--skip", type=int, default=0, help="Rows to skip (upstream param: skip; default 0)")
213
+ f22.add_argument("--sort", default=None, help="Sort (upstream param: sort; our default approval_date:desc)")
214
+
215
+ # —— 0.3.16 UK MHRA safety-alert twin faces (gov.uk search/content API, same contract family as the gb_cma case chain; stood up from live probing 2026-10-30) ——
216
+
217
+ f23 = v.add_parser("gb_mhra_alert", parents=[g()],
218
+ help="UK MHRA single-alert dossier (gov.uk content API)",
219
+ description="UK MHRA single safety-alert dossier (keyless): content_id is the stable UUID key; "
220
+ "alert_type/issued_date are the MHRA metadata fields (medicines-recall-notification, "
221
+ "device-field-safety-notice …); attachments = the notice PDF family. The content API "
222
+ "has no .json variant — a trailing .json serves the 404 page (a pasted slug with it "
223
+ "gets it stripped); a pasted full base_path (/drug-device-alerts/<slug> or absolute "
224
+ "URL) is accepted — the prefix is stripped")
225
+ f23.add_argument("--slug", required=True, help="Alert slug (from gb_mhra_alerts rows; bare tail segment, e.g. class-2-medicines-recall-xeal-pharma-clopidogrel-oral-liquids-all-strengths-el-26-a-slash-43)")
226
+
227
+ f24 = v.add_parser("gb_mhra_alerts", parents=[g()],
228
+ help="UK MHRA medicines & device safety alerts (drug alerts, device alerts, FSN weeklies)",
229
+ description="UK MHRA safety-alert search (gov.uk search API, keyless): q verbatim over alert "
230
+ "titles/bodies — a company-name penetration channel (Abbott → 160 alerts across "
231
+ "2019-2026, probed 2026-10-30); count politeness cap 100; start = offset "
232
+ "pagination; omit --q to enumerate the full live register (~1,462 alerts). "
233
+ "Single-alert detail: gb_mhra_alert with the slug from each row (under a q= query "
234
+ "the rows drop link — the row mapper then reads the _id base_path)")
235
+ f24.add_argument("--q", default=None, help="Query term (upstream param: q; company/product names penetrate — e.g. Abbott, recall class)")
236
+ f24.add_argument("--count", type=int, default=20, help="Rows (upstream param: count; 1-100 politeness cap)")
237
+ f24.add_argument("--start", type=int, default=0, help="Offset (upstream param: start)")
238
+
239
+ f25 = v.add_parser("gudid_devices", parents=[g()],
240
+ help="US device master record (AccessGUDID v3, one of di/udi/record_key)",
241
+ description="US device record: brand/company/product codes (exactly one of --di / --udi / --record-key)")
242
+ f25.add_argument("--di", default=None, help="Device Identifier (upstream param: di)")
243
+ f25.add_argument("--udi", default=None, help="Full UDI (upstream param: udi)")
244
+ f25.add_argument("--record-key", dest="record_key", default=None, help="Record key (upstream param: record_key)")
245
+
246
+ f26 = v.add_parser("mesh_descriptor", parents=[g()],
247
+ help="Term→MeSH descriptor + synonym pool (NLM lookup API)",
248
+ description="Drug/disease term → MeSH descriptor candidates + top hit's synonym pool (controlled-vocabulary anchor)")
249
+ f26.add_argument("--label", required=True, help="Term to look up (upstream param: label — passing term/descriptor gets 400)")
250
+ f26.add_argument("--match", default="contains", choices=["contains", "exact", "startswith"],
251
+ help="Match mode (upstream param: match; default contains)")
252
+ f26.add_argument("--limit", type=int, default=10, help="Candidates cap (upstream param: limit; default 10, cap 50)")
253
+
254
+ f27 = v.add_parser("rasff_notifications", parents=[g()],
255
+ help="EU RASFF food-safety alert search (window backend)",
256
+ description="The consolidated English RASFF window: subject/reference/date-window filters (the proven ones — enum-shaped country filters stay unprobed upstream) + paging; 33k-notification corpus, notifying country × product × risk rows")
257
+ f27.add_argument("--subject", default=None, help="Free text on the alert subject (upstream param: subject; e.g. aflatoxin)")
258
+ f27.add_argument("--notification-reference", dest="notification_reference", default=None, help="Reference number, exact (upstream param: notificationReference; e.g. 2026.9701)")
259
+ f27.add_argument("--ec-valid-date-from", dest="ec_valid_date_from", default=None, help="Window start (upstream param: ecValidDateFrom; format dd-MM-yyyy, e.g. 01-09-2026 — ISO input is rejected upstream)")
260
+ f27.add_argument("--ec-valid-date-to", dest="ec_valid_date_to", default=None, help="Window end (upstream param: ecValidDateTo; format dd-MM-yyyy)")
261
+ f27.add_argument("--page-number", dest="page_number", type=int, default=1, help="Page number, 1-based (upstream param: pageNumber)")
262
+ f27.add_argument("--items-per-page", dest="items_per_page", type=int, default=25, help="Rows per page (upstream param: itemsPerPage; browser default 25, politeness cap 200)")
263
+
264
+ f28 = v.add_parser("rxnav_rxcui_atc", parents=[g()],
265
+ help="RxCUI→ATC class (RxNav property.json?propName=ATC)",
266
+ description="RxCUI → ATC classification (numeric RxCUI only; resolve names first via rxnav_rxcui_by_name)")
267
+ f28.add_argument("--rxcui", required=True, help="Numeric RxCUI (upstream param: rxcui)")
268
+
269
+ f29 = v.add_parser("rxnav_rxcui_by_name", parents=[g()],
270
+ help="Drug name→RxCUI card (RxNav rxcuiByName)",
271
+ description="Identity normalization: drug/brand name → RxCUI (single-record face, no pagination)")
272
+ f29.add_argument("--name", required=True, help="Drug or brand name (upstream param: name)")
273
+
274
+ f30 = v.add_parser("safety_gate_alerts", parents=[g()],
275
+ help="EU Safety Gate weekly-report index (one XML, 2005→now)",
276
+ description="The ex-RAPEX rapid-alert system's weekly-report index: 1,122 reports with report_id join keys; year is a local post-filter (the upstream always serves the whole index)")
277
+ f30.add_argument("--year", default=None, help="Report year, a local post-filter (not an upstream param; e.g. 2026)")
278
+ f30.add_argument("--limit", type=int, default=200, help="Rows to return (local post-slice over the single cached index)")
279
+ f30.add_argument("--skip", type=int, default=0, help="Rows to skip (local post-slice)")
280
+
281
+ f31 = v.add_parser("safety_gate_report", parents=[g()],
282
+ help="EU Safety Gate one weekly report's alerts (detail XML)",
283
+ description="A single weekly report's alert rows: case number / product / risk type / level / notifying country / country of origin / online trader — the company-relevant edges of the non-food product-safety regime")
284
+ f31.add_argument("--report-id", dest="report_id", required=True, help="Numeric report id (the report_id field echoed by safety_gate_alerts; e.g. 10000329 — a pasted detail URL also works)")
285
+ f31.add_argument("--limit", type=int, default=200, help="Rows to return (local post-slice)")
286
+ f31.add_argument("--skip", type=int, default=0, help="Rows to skip (local post-slice)")
287
+
288
+ # —— Wave 10 openFDA family, four faces (2026-10-28; born flat: upstream param original names only, zero sugar, zero deprecation debt) ——
289
+
290
+ FLAT = {
291
+ "au_tga_artg": lambda a: {"query": a.query, "limit": a.limit, "skip": a.skip},
292
+ "au_tga_manufacturers": lambda a: {"state": a.state, "limit": a.limit},
293
+ # 0.3.19 CA + EU product-safety faces (born flat: upstream params verbatim + closed-set validation; local post-slices marked)
294
+ "ca_dpd_companies": lambda a: {"limit": a.limit, "skip": a.skip},
295
+ "ca_dpd_drug_products": lambda a: {"status": a.status, "limit": a.limit, "skip": a.skip},
296
+ "ca_mdall_companies": lambda a: {"limit": a.limit, "skip": a.skip},
297
+ "ca_mdall_licence": lambda a: {"licence_id": a.licence_id, "licence_type": a.licence_type,
298
+ "lang": a.lang},
299
+ "ctgov_studies": lambda a: {"query_term": a.query_term, "query_titles": a.query_titles,
300
+ "query_intr": a.query_intr, "query_cond": a.query_cond,
301
+ "filter_advanced": a.filter_advanced, "agg_filters": a.agg_filters,
302
+ "fields": a.fields,
303
+ "page_size": a.page_size, "page_token": a.page_token,
304
+ "sort": a.sort, "count_total": a.count_total},
305
+ "dailymed_spls": lambda a: {"search": a.search, "manufacturer": a.manufacturer,
306
+ "setid": a.setid, "page": a.page, "pagesize": a.pagesize, "sort": a.sort},
307
+ "epmc_search": lambda a: {"search": a.search, "result_type": a.result_type,
308
+ "page_size": a.page_size, "cursor_mark": a.cursor_mark, "sort": a.sort},
309
+ "eudamed_devices_udi_di_data": lambda a: {"primary_di": a.primary_di, "page": a.page,
310
+ "page_size": a.page_size, "iso2_code": a.iso2_code},
311
+ "fda_crl_letters": lambda a: {"search": a.search, "limit": a.limit, "skip": a.skip, "sort": a.sort},
312
+ # Wave 10b openFDA family, seven faces (born flat: upstream param names passed through, no sugar keys)
313
+ "fda_device_510k": lambda a: {"search": a.search, "limit": a.limit, "skip": a.skip, "sort": a.sort},
314
+ "fda_device_classification": lambda a: {"search": a.search, "limit": a.limit, "skip": a.skip, "sort": a.sort},
315
+ "fda_device_pma": lambda a: {"search": a.search, "limit": a.limit, "skip": a.skip, "sort": a.sort},
316
+ "fda_device_recalls": lambda a: {"search": a.search, "limit": a.limit, "skip": a.skip, "sort": a.sort},
317
+ "fda_device_registrations": lambda a: {"search": a.search, "limit": a.limit, "skip": a.skip, "sort": a.sort},
318
+ "fda_drug_approvals": lambda a: {"search": a.search, "limit": a.limit, "skip": a.skip, "sort": a.sort},
319
+ "fda_drug_catalog": lambda a: {"search": a.search, "limit": a.limit, "skip": a.skip, "sort": a.sort},
320
+ "fda_drug_recalls": lambda a: {"search": a.search, "limit": a.limit, "skip": a.skip, "sort": a.sort},
321
+ "fda_drug_shortages": lambda a: {"search": a.search,
322
+ "limit": a.limit, "skip": a.skip, "sort": a.sort},
323
+ "fda_food_recalls": lambda a: {"search": a.search, "limit": a.limit, "skip": a.skip, "sort": a.sort},
324
+ "fda_orangebook": lambda a: {"search": a.search, "limit": a.limit, "skip": a.skip, "sort": a.sort},
325
+ # 0.3.16 UK MHRA safety-alert twin faces (gov.uk search/content API; upstream param names verbatim + our polite tier)
326
+ "gb_mhra_alert": lambda a: {"slug": a.slug},
327
+ "gb_mhra_alerts": lambda a: {"q": a.q, "count": a.count, "start": a.start},
328
+ "gudid_devices": lambda a: {"di": a.di, "udi": a.udi, "record_key": a.record_key},
329
+ "mesh_descriptor": lambda a: {"label": a.label, "match": a.match, "limit": a.limit},
330
+ "rasff_notifications": lambda a: {"subject": a.subject, "notification_reference": a.notification_reference,
331
+ "ec_valid_date_from": a.ec_valid_date_from, "ec_valid_date_to": a.ec_valid_date_to,
332
+ "page_number": a.page_number, "items_per_page": a.items_per_page},
333
+ "rxnav_rxcui_atc": lambda a: {"rxcui": a.rxcui},
334
+ "rxnav_rxcui_by_name": lambda a: {"name": a.name},
335
+ "safety_gate_alerts": lambda a: {"year": a.year, "limit": a.limit, "skip": a.skip},
336
+ "safety_gate_report": lambda a: {"report_id": a.report_id, "limit": a.limit, "skip": a.skip},
337
+ # Wave 10 openFDA family, four faces (born flat: upstream param names passed through, no sugar keys)
338
+ }
339
+
340
+
341
+ # 0.3.10 jurisdiction chapter: face → jurisdiction (default = the issuing authority's jurisdiction; bodies
342
+ # covering global phenomena = GLOBAL; vocabulary = ISO-3166 alpha-2 + EU + GLOBAL) — merged into _JURISDICTION by the root cli.py; success envelopes carry the jurisdiction chapter
343
+ COUNTRY = {
344
+ "au_tga_artg": "AU",
345
+ "au_tga_manufacturers": "AU",
346
+ "ca_dpd_companies": "CA",
347
+ "ca_dpd_drug_products": "CA",
348
+ "ca_mdall_companies": "CA",
349
+ "ca_mdall_licence": "CA",
350
+ "ctgov_studies": "US",
351
+ "dailymed_spls": "US",
352
+ "epmc_search": "GLOBAL",
353
+ "eudamed_devices_udi_di_data": "EU",
354
+ "fda_crl_letters": "US",
355
+ "fda_device_510k": "US",
356
+ "fda_device_classification": "US",
357
+ "fda_device_pma": "US",
358
+ "fda_device_recalls": "US",
359
+ "fda_device_registrations": "US",
360
+ "fda_drug_approvals": "US",
361
+ "fda_drug_catalog": "US",
362
+ "fda_drug_recalls": "US",
363
+ "fda_drug_shortages": "US",
364
+ "fda_food_recalls": "US",
365
+ "fda_orangebook": "US",
366
+ "gb_mhra_alert": "GB",
367
+ "gb_mhra_alerts": "GB",
368
+ "gudid_devices": "US",
369
+ "mesh_descriptor": "GLOBAL",
370
+ "rasff_notifications": "EU",
371
+ "rxnav_rxcui_atc": "US",
372
+ "rxnav_rxcui_by_name": "US",
373
+ "safety_gate_alerts": "EU",
374
+ "safety_gate_report": "EU",
375
+ }