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,125 @@
1
+ """dgcomp — EU DG COMP competition case register provider (company name → EU competition cases; keyless).
2
+
3
+ The endpoint is the search backend webgate ES API of the competition-cases.ec.europa.eu portal.
4
+ Shape verified 2026-10-20: POST /es/search-api/rest/search/grouped, body = multipart FormData with an
5
+ embedded query field = bare ES bool DSL (same as the SPA; first user of the transport form channel, extension 4);
6
+ apiKey=CS_PROD_ODSE_PROD is a database label exposed in the public SPA configuration plaintext (not a credential).
7
+ Python urllib direct connection verified (webgate is not gated per client stack; Node undici also works — no curl needed).
8
+ pageSize hard cap 100: verified 2026-10-26, pageSize=200 → silently returns only 100 rows (verified on the instrument=SA 62k face).
9
+ TTL: search 7 days (case cards change slowly); polite rate 0.5 rps (webgate publishes no quota).
10
+ """
11
+ from ..errors import SdkError
12
+
13
+ provider = {
14
+ "id": "dgcomp",
15
+ "baseUrl": "https://webgate.ec.europa.eu/es/search-api/rest",
16
+ "rate": {"rps": 0.5, "burst": 2},
17
+ "ops": {
18
+ "search": {"path": "/search/grouped", "method": "POST", "idempotent": True, "form": "query"},
19
+ },
20
+ "ttl": {"search": 7 * 86400},
21
+ }
22
+
23
+ API_KEY = "CS_PROD_ODSE_PROD" # static config exposed by the public SPA (not a secret; see module docstring)
24
+ # caseInstrument closed set — distinct probe against upstream (webgate ES) 2026-10-21:
25
+ # {AT: 8, M: 6, SA: 2, InstrumentDMA: 4}; term caseInstrument=InstrumentDMA → 55 cases (the DMA.100018 case family).
26
+ # The raw DMA value carries the Instrument prefix — guessing "DMA" and firing a term misses (silent 0 rows).
27
+ # The closed set comes from upstream probing; do not generalize from first-seen samples
28
+ # (lesson: an earlier closed set missed DMA and with it an entire act family).
29
+ INSTRUMENTS = {"AT", "M", "SA", "InstrumentDMA"} # antitrust / merger / state aid / Digital Markets Act (upstream literals)
30
+ INSTRUMENT_ALIASES = {"DMA": "InstrumentDMA"} # human alias → upstream literal (normalized via .upper())
31
+ INSTRUMENT_KIND = {"AT": "AT", "M": "M", "SA": "SA", "InstrumentDMA": "DMA"} # upstream literal → stable short code
32
+
33
+
34
+ def build_query(name=None, instrument=None) -> dict:
35
+ """→ bare ES bool DSL (SPA-identical construction; pure function so the body is offline-testable).
36
+ Company-name search = text query with fields=[caseTitle,caseCompanies,caseOriginalTitle] (copied from the SPA main.js).
37
+ instrument takes the upstream literal (AT/M/SA/InstrumentDMA) or an alias (DMA, case-insensitive), normalized
38
+ to the literal before entering the term."""
39
+ must = []
40
+ q = str(name or "").strip()
41
+ if q:
42
+ must.append({"text": {"fields": ["caseTitle", "caseCompanies", "caseOriginalTitle"],
43
+ "query": q, "defaultOperator": "AND"}})
44
+ if instrument:
45
+ ins = str(instrument).strip()
46
+ canon = ins if ins in INSTRUMENTS else INSTRUMENT_ALIASES.get(ins.upper())
47
+ if canon not in INSTRUMENTS:
48
+ raise SdkError("usage_error", provider="dgcomp",
49
+ detail=f"instrument must be AT|M|SA|DMA, got \"{instrument}\"")
50
+ must.append({"term": {"caseInstrument": canon}})
51
+ if not must:
52
+ raise SdkError("usage_error", provider="dgcomp", detail="search_cases requires name / instrument — at least one of the two")
53
+ return {"bool": {"must": must}}
54
+
55
+
56
+ def _first(a):
57
+ return a[0] if isinstance(a, list) and a else None
58
+
59
+
60
+ def search_cases(name=None, *, instrument=None, limit=20, page=1, cache=None, no_cache=False):
61
+ """→ {total, rows[], _meta}; case rows (case_number official key / parties array / decision dates).
62
+ /search/grouped still returns per-document rows (groupById=case number) — deduped client-side by groupById
63
+ into one row per case; the grouped face's totalResults = deduped case count (on the non-grouped face it is the
64
+ document row count, which over-counts) — verified 2026-10-20."""
65
+ from ..transport import call_provider
66
+
67
+ size = min(max(1, int(limit or 20)), 100) # upstream pageSize hard cap 100 (verified 2026-10-26: 200 → silent 100 rows)
68
+ r = call_provider(provider, "search", {
69
+ "query": build_query(name, instrument),
70
+ "apiKey": API_KEY,
71
+ "text": "", # required query param (the SPA always sends it; empty string = no extra text condition; omitting → 400 businessError, verified 2026-10-20)
72
+ "pageNumber": max(1, int(page or 1)),
73
+ "pageSize": size,
74
+ }, cache=cache, no_cache=no_cache)
75
+ return {**_rows_from(r["data"], size), "_meta": r}
76
+
77
+
78
+ def _rows_from(data, size):
79
+ """Upstream response → deduped case rows (deduped client-side by groupById into one row per case; pure function, offline-testable)."""
80
+ seen, rows = set(), []
81
+ for e in (data or {}).get("results") or []:
82
+ m = e.get("metadata") or {}
83
+ num = _first(m.get("caseNumber")) or e.get("groupById") or e.get("reference")
84
+ if not num or num in seen:
85
+ continue
86
+ seen.add(num)
87
+ ins_raw = _first(m.get("caseInstrument"))
88
+ rows.append({
89
+ "case_number": num,
90
+ "title": _first(m.get("caseOriginalTitle")) or _first(m.get("caseTitle")), # caseTitle carries <b> highlights — prefer originalTitle
91
+ "instrument": ins_raw, # upstream literal passed through (AT/M/SA/InstrumentDMA), not rewritten
92
+ "instrument_kind": INSTRUMENT_KIND.get(ins_raw), # stable short code (AT/M/SA/DMA)
93
+ "member_state": (_first(m.get("caseMemberState")) or "").replace("Country", "") or None,
94
+ "companies": m.get("caseCompanies") or [], # structured parties (named per AT case; often empty on SA cases — the beneficiary name is in title)
95
+ "sectors_nace": m.get("caseSectors") or [],
96
+ "decision_date": (_first(m.get("caseLastDecisionDate")) or "")[:10] or None,
97
+ "initiation_date": (_first(m.get("caseInitiationDate")) or "")[:10] or None,
98
+ "dg": (_first(m.get("caseDg")) or "").replace("DG_", "", 1).replace("DG", "", 1) or None,
99
+ "url": f"https://competition-cases.ec.europa.eu/search?caseNumber={num}",
100
+ })
101
+ return {"total": (data or {}).get("totalResults"), "rows": rows, "limit_applied": size}
102
+
103
+
104
+ def search_raw(query=None, *, name=None, instrument=None, text="", limit=20, page=1,
105
+ cache=None, no_cache=False):
106
+ """Flat-panel low-level face: query (bare ES bool DSL dict, verbatim-name channel) XOR name/instrument (sugar assembles the DSL).
107
+
108
+ → {total, rows[], limit_applied, params, _meta} — params is the exact form body sent (query/apiKey/text/
109
+ pageNumber/pageSize; apiKey = public SPA static label, not a credential). pageSize wall 1-100:
110
+ verified 2026-10-26, pageSize=200 → silently returns only 100 rows (upstream hard cap — walls must fail
111
+ clearly, not clamp silently)."""
112
+ from ..transport import call_provider
113
+
114
+ if query is not None and (str(name or "").strip() or instrument):
115
+ raise SdkError("usage_error", provider="dgcomp",
116
+ detail="query (verbatim-name channel: bare ES bool DSL) and name/instrument (sugar assembles the DSL) are mutually exclusive — give only one")
117
+ dsl = query if query is not None else build_query(name, instrument) # all empty → at-least-one usage_error
118
+ lim = int(limit or 20)
119
+ if not 1 <= lim <= 100:
120
+ raise SdkError("usage_error", provider="dgcomp",
121
+ detail=f"limit must be 1-100 (upstream pageSize hard cap: verified 2026-10-26, 200 → silently returns 100 rows), got \"{limit}\"")
122
+ params = {"query": dsl, "apiKey": API_KEY, "text": str(text or ""),
123
+ "pageNumber": max(1, int(page or 1)), "pageSize": lim}
124
+ r = call_provider(provider, "search", params, cache=cache, no_cache=no_cache)
125
+ return {**_rows_from(r["data"], lim), "params": params, "_meta": r}
@@ -0,0 +1,129 @@
1
+ """ecfr — eCFR provider (the codified current state of US federal regulations: 50 titles, updated daily).
2
+
3
+ The counterpart face of the FR daily feed (the federalregister provider): eCFR = "where the current clause lives",
4
+ FR = "what changed today". www.ecfr.gov has two keyless faces (shape verified 2026-10-26):
5
+ GET /api/search/v1/results?query=<term>&per_page=N&page=P
6
+ → {meta:{current_page,total_pages,total_count,max_score,description},results:[{starts_on,ends_on,type,
7
+ hierarchy{title,subtitle,chapter,subchapter,part,subpart,subject_group,section,appendix}, headings{proper-noun form},
8
+ hierarchy_headings{label form}, full_text_excerpt(HTML highlights), structure_index(int), change_types[], removed,
9
+ reserved(bool), score}]}
10
+ GET /api/versioner/v1/titles.json → {titles:[{number,name,latest_amended_on,latest_issue_date,
11
+ up_to_date_as_of,reserved}]} (reserved titles have up_to_date_as_of=null — treat as empty and skip, do not compare).
12
+ Param pitfalls (verified): paging only honors per_page/page — limit/max_results both 400 with
13
+ "Found unpermitted parameter" (the error body is the documentation); page=3 re-verified with meta.current_page=3 echoed
14
+ back and total_pages self-consistent = really bound.
15
+ TTL: both faces 24h (the code updates daily); polite rate 0.5 rps (no 429 seen during same-day probing — conservative start).
16
+ 0.2.9 audit (2026-10-28): search gained a date param (point-in-time retrieval — the clause versions in force on that
17
+ date; probed asbestos: no date = 1,436 vs date=2024-01-01 = 339, rows carry starts_on/ends_on validity windows).
18
+ """
19
+
20
+ import re as _re
21
+
22
+ _DATE_RE = _re.compile(r"^\d{4}-\d{2}-\d{2}$")
23
+
24
+
25
+ def _coords_of(hierarchy):
26
+ """Seven-level code coordinates → readable string (e.g. "40 CFR Part 763 Subpart E App. D")."""
27
+ h = hierarchy or {}
28
+ parts = []
29
+ if h.get("title"):
30
+ parts.append(f"{h['title']} CFR")
31
+ if h.get("part"):
32
+ parts.append(f"Part {h['part']}")
33
+ if h.get("subpart"):
34
+ parts.append(f"Subpart {h['subpart']}")
35
+ if h.get("section"):
36
+ parts.append(f"§ {h['section']}")
37
+ if h.get("appendix"):
38
+ parts.append(h["appendix"])
39
+ return " ".join(parts)
40
+
41
+
42
+ def rows_from_upstream(data, size):
43
+ """Upstream response → hit rows (pure function, offline-testable)."""
44
+
45
+ rows = [{
46
+ "coords": _coords_of(r.get("hierarchy")), # readable coordinates (for citation / re-joining the enforcement face)
47
+ "hierarchy": r.get("hierarchy") or {}, # seven-level code form, verbatim
48
+ "headings": r.get("headings") or {}, # proper-noun form (EPA/Toxic Substances…)
49
+ "type": r.get("type"), # Section|Appendix|Part…
50
+ "starts_on": r.get("starts_on"), # that version's effective window (change-stream semantics)
51
+ "ends_on": r.get("ends_on"),
52
+ "change_types": r.get("change_types") or [], # effective/initial…
53
+ "removed": r.get("removed") is True,
54
+ "reserved": r.get("reserved") is True,
55
+ "structure_index": r.get("structure_index"), # structure index (anchor for versioner lookups)
56
+ "score": r.get("score") if isinstance(r.get("score"), (int, float)) else None,
57
+ "excerpt": r.get("full_text_excerpt") or None, # HTML-highlighted excerpt (hit terms wrapped in <strong>)
58
+ } for r in ((data or {}).get("results") or [])]
59
+ meta = (data or {}).get("meta") or {}
60
+ return {
61
+ "rows": rows, "count": len(rows),
62
+ "limit_applied": size,
63
+ "total": meta.get("total_count") if isinstance(meta.get("total_count"), int) else None,
64
+ "total_pages": meta.get("total_pages") if isinstance(meta.get("total_pages"), int) else None,
65
+ "current_page": meta.get("current_page") if isinstance(meta.get("current_page"), int) else None,
66
+ "description": meta.get("description"), # upstream self-description ("Changes to sections matching …")
67
+ }
68
+
69
+
70
+ def search_results(q=None, *, page=1, limit=20, date=None, cache=None, no_cache=False):
71
+ """→ {rows[], count, limit_applied, total, total_pages, current_page, description, _meta}.
72
+
73
+ Section-level current-state search (q required; params mirror the upstream names query/per_page/page/date —
74
+ all four verified bound). date (adopted in the 0.2.9 audit, YYYY-MM-DD): point-in-time retrieval — returns the
75
+ clause versions in force on that date (rows carry starts_on/ends_on validity windows); probed query=asbestos:
76
+ no date = 1,436 hits vs date=2024-01-01 = 339 (a subset inside the point-in-time window, probed 2026-10-28)."""
77
+ from ..transport import call_provider
78
+ from ..errors import SdkError
79
+
80
+ term = str(q or "").strip()
81
+ if not term:
82
+ raise SdkError("usage_error", provider="ecfr",
83
+ detail="ecfr_search requires q (topic term; returns section-level current-state hits)")
84
+ d = str(date or "").strip() or None
85
+ if d is not None and not _DATE_RE.fullmatch(d):
86
+ raise SdkError("usage_error", provider="ecfr", detail=f"date must be YYYY-MM-DD (point-in-time retrieval): {date}")
87
+ size = min(max(1, int(limit or 20)), 100) # local payload wall; no documented upstream per_page ceiling — polite cap 100
88
+ params = {"query": term, "per_page": size, "page": max(1, int(page or 1))}
89
+ if d:
90
+ params["date"] = d
91
+ r = call_provider(provider, "search", params, cache=cache, no_cache=no_cache)
92
+ out = rows_from_upstream(r.get("data"), size)
93
+ out["_meta"] = r
94
+ return out
95
+
96
+
97
+ def titles_catalog(cache=None, no_cache=False):
98
+ """→ {count, active, as_of, titles[], _meta} — the 50-title catalog (as_of = library-wide currency watermark)."""
99
+ from ..transport import call_provider
100
+
101
+ r = call_provider(provider, "titles", {}, cache=cache, no_cache=no_cache)
102
+ titles = [{
103
+ "number": t.get("number"),
104
+ "name": t.get("name"),
105
+ "latest_amended_on": t.get("latest_amended_on"),
106
+ "latest_issue_date": t.get("latest_issue_date"),
107
+ "up_to_date_as_of": t.get("up_to_date_as_of"), # None on reserved titles
108
+ "reserved": t.get("reserved") is True,
109
+ } for t in (((r.get("data") or {}).get("titles")) or [])]
110
+ dated = sorted(t["up_to_date_as_of"] for t in titles if t["up_to_date_as_of"])
111
+ return {
112
+ "count": len(titles),
113
+ "active": sum(1 for t in titles if not t["reserved"]),
114
+ "as_of": dated[-1] if dated else None, # library-wide daily-update watermark (observed at T-2 level)
115
+ "titles": titles,
116
+ "_meta": r,
117
+ }
118
+
119
+
120
+ provider = {
121
+ "id": "ecfr",
122
+ "baseUrl": "https://www.ecfr.gov",
123
+ "rate": {"rps": 0.5, "burst": 1},
124
+ "ops": {
125
+ "search": {"path": "/api/search/v1/results"}, # params: query, per_page, page, date (all four verified bound)
126
+ "titles": {"path": "/api/versioner/v1/titles.json"},
127
+ },
128
+ "ttl": {"search": 24 * 3600, "titles": 24 * 3600},
129
+ }