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.
- agentdata/__init__.py +33 -0
- agentdata/__main__.py +4 -0
- agentdata/cache.py +115 -0
- agentdata/cli.py +173 -0
- agentdata/client.py +158 -0
- agentdata/company/__init__.py +3 -0
- agentdata/company/cf.py +145 -0
- agentdata/company/ch_zefix.py +100 -0
- agentdata/company/cli.py +243 -0
- agentdata/company/co_rues.py +72 -0
- agentdata/company/edgar_cik.py +84 -0
- agentdata/company/edgar_fts.py +101 -0
- agentdata/company/faces.py +746 -0
- agentdata/company/fdic_bankfind.py +107 -0
- agentdata/company/fi_ytj.py +89 -0
- agentdata/company/fr_recherche.py +124 -0
- agentdata/company/fts.py +121 -0
- agentdata/company/gleif.py +178 -0
- agentdata/company/kr_hometax.py +112 -0
- agentdata/company/no_brreg.py +124 -0
- agentdata/company/ror.py +105 -0
- agentdata/company/ted.py +117 -0
- agentdata/company/usaspending.py +124 -0
- agentdata/company/vies.py +66 -0
- agentdata/daterange.py +35 -0
- agentdata/economy/__init__.py +10 -0
- agentdata/economy/bis.py +132 -0
- agentdata/economy/cli.py +199 -0
- agentdata/economy/ecb.py +117 -0
- agentdata/economy/eurostat.py +163 -0
- agentdata/economy/faces.py +573 -0
- agentdata/economy/fred.py +92 -0
- agentdata/economy/ilo_stat.py +99 -0
- agentdata/economy/imf_sdmx.py +133 -0
- agentdata/economy/un_comtrade.py +199 -0
- agentdata/economy/unsd_sdg.py +154 -0
- agentdata/economy/wb.py +208 -0
- agentdata/economy/who_xmart.py +177 -0
- agentdata/entries.py +30 -0
- agentdata/errors.py +38 -0
- agentdata/esg/__init__.py +5 -0
- agentdata/esg/cli.py +180 -0
- agentdata/esg/climatetrace.py +260 -0
- agentdata/esg/eiti.py +187 -0
- agentdata/esg/epa_frs.py +138 -0
- agentdata/esg/euasl.py +75 -0
- agentdata/esg/eutl.py +66 -0
- agentdata/esg/faces.py +498 -0
- agentdata/esg/paris_mou.py +240 -0
- agentdata/esg/thetis_mrv.py +67 -0
- agentdata/healthcare/__init__.py +7 -0
- agentdata/healthcare/au_tga.py +142 -0
- agentdata/healthcare/ca_dpd.py +123 -0
- agentdata/healthcare/ca_mdall.py +155 -0
- agentdata/healthcare/cli.py +375 -0
- agentdata/healthcare/ctgov.py +101 -0
- agentdata/healthcare/dailymed.py +102 -0
- agentdata/healthcare/epmc.py +108 -0
- agentdata/healthcare/eudamed.py +124 -0
- agentdata/healthcare/faces.py +811 -0
- agentdata/healthcare/fda_crl.py +86 -0
- agentdata/healthcare/fda_devices.py +199 -0
- agentdata/healthcare/fda_drugs.py +156 -0
- agentdata/healthcare/fda_recalls.py +137 -0
- agentdata/healthcare/gb_mhra.py +137 -0
- agentdata/healthcare/gudid.py +84 -0
- agentdata/healthcare/mesh.py +89 -0
- agentdata/healthcare/rasff.py +164 -0
- agentdata/healthcare/rxnav.py +67 -0
- agentdata/healthcare/safety_gate.py +175 -0
- agentdata/healthcare/shortages.py +107 -0
- agentdata/legal/__init__.py +4 -0
- agentdata/legal/afdb.py +93 -0
- agentdata/legal/ca_sema.py +143 -0
- agentdata/legal/cli.py +312 -0
- agentdata/legal/co_sic.py +70 -0
- agentdata/legal/courtlistener.py +77 -0
- agentdata/legal/csl.py +139 -0
- agentdata/legal/dgcomp.py +125 -0
- agentdata/legal/ecfr.py +129 -0
- agentdata/legal/faces.py +700 -0
- agentdata/legal/federalregister.py +188 -0
- agentdata/legal/gb_cma.py +117 -0
- agentdata/legal/gb_tna_caselaw.py +245 -0
- agentdata/legal/idb.py +85 -0
- agentdata/legal/lda.py +147 -0
- agentdata/legal/sipri.py +178 -0
- agentdata/legal/ukec.py +148 -0
- agentdata/legal/wb.py +92 -0
- agentdata/procurement/__init__.py +5 -0
- agentdata/procurement/br_pncp.py +126 -0
- agentdata/procurement/cli.py +155 -0
- agentdata/procurement/co_secop.py +88 -0
- agentdata/procurement/faces.py +239 -0
- agentdata/procurement/ny_open_data.py +82 -0
- agentdata/procurement/ungm.py +137 -0
- agentdata/procurement/wb_proc.py +84 -0
- agentdata/research/__init__.py +4 -0
- agentdata/research/biorxiv.py +66 -0
- agentdata/research/cli.py +129 -0
- agentdata/research/crossref.py +121 -0
- agentdata/research/faces.py +349 -0
- agentdata/research/gb_gtr.py +58 -0
- agentdata/research/icite.py +52 -0
- agentdata/research/nih_reporter.py +143 -0
- agentdata/research/nsf.py +68 -0
- agentdata/research/openalex.py +145 -0
- agentdata/research/opencitations.py +50 -0
- agentdata/security/__init__.py +3 -0
- agentdata/security/cisa_kev.py +42 -0
- agentdata/security/cli.py +37 -0
- agentdata/security/faces.py +91 -0
- agentdata/security/osv.py +72 -0
- agentdata/transport.py +541 -0
- agentdata-0.3.26.dist-info/METADATA +266 -0
- agentdata-0.3.26.dist-info/RECORD +120 -0
- agentdata-0.3.26.dist-info/WHEEL +5 -0
- agentdata-0.3.26.dist-info/entry_points.txt +10 -0
- agentdata-0.3.26.dist-info/licenses/LICENSE +201 -0
- agentdata-0.3.26.dist-info/top_level.txt +1 -0
agentdata/esg/faces.py
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"""esg faces — flat face-method mixin (0.2.6 per-domain split, moved out of client.py _Domain).
|
|
2
|
+
|
|
3
|
+
Mixed into the root _Domain (MRO assembly, see client.py); discipline: no __init__, depends only on root
|
|
4
|
+
internals (_flat_env/_c/_request…), method names globally unique across domains (a collision
|
|
5
|
+
would shadow another domain's face).
|
|
6
|
+
"""
|
|
7
|
+
from ..errors import SdkError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from .. import esg # call via the domain-module path (historical shape, kept to minimize churn)
|
|
11
|
+
|
|
12
|
+
class EsgFaces:
|
|
13
|
+
def _esg_only(self, tool: str):
|
|
14
|
+
if self.name != "esg":
|
|
15
|
+
raise SdkError("usage_error", detail=f"{tool} is an esg-domain capability (unavailable on the {self.name} domain)")
|
|
16
|
+
|
|
17
|
+
def eiti_companies(self, *, company_name__like=None, name=None, size=10,
|
|
18
|
+
execution=None, no_cache=None):
|
|
19
|
+
"""esg eiti_companies: extractives filer company candidates (EITI Datasette view_companies;
|
|
20
|
+
company_name__like verbatim (%/_ yours to manage) XOR name sugar (%contains%) — same slot)."""
|
|
21
|
+
self._esg_only("eiti_companies")
|
|
22
|
+
deps, assembled_from = [], None
|
|
23
|
+
raw_like = str(company_name__like or "").strip() or None
|
|
24
|
+
nm = str(name or "").strip() or None
|
|
25
|
+
if raw_like and nm:
|
|
26
|
+
raise SdkError("usage_error", provider="eiti",
|
|
27
|
+
detail="company_name__like (raw channel: %/_ yours to manage) and name (sugar: %contains%) share one slot and are mutually exclusive — give only one")
|
|
28
|
+
if not raw_like and not nm:
|
|
29
|
+
raise SdkError("usage_error", provider="eiti",
|
|
30
|
+
detail="eiti_companies requires at least one of company_name__like or name")
|
|
31
|
+
if nm:
|
|
32
|
+
assembled_from = {"name": nm}
|
|
33
|
+
deps.append("name sugar assembles company_name__like=%…% (contains) — retained sugar (not deprecated); "
|
|
34
|
+
"the raw channel is company_name__like (%/_ metacharacters yours to manage)")
|
|
35
|
+
raw_like = f"%{nm}%"
|
|
36
|
+
r = esg.eiti.companies_raw(raw_like, size=size, cache=self._c._cache,
|
|
37
|
+
no_cache=self._c.no_cache if no_cache is None else bool(no_cache))
|
|
38
|
+
result = {k: x for k, x in r.items() if k != "_meta"}
|
|
39
|
+
top = (result["candidates"] or [None])[0]
|
|
40
|
+
matched = esg.eiti.total_display(result.get("total_matches"), result.get("total_matches_capped"))
|
|
41
|
+
quartet = {"source": "assembled" if assembled_from else "raw", "upstream": r["params"]}
|
|
42
|
+
if assembled_from:
|
|
43
|
+
quartet["assembled_from"] = assembled_from
|
|
44
|
+
return self._flat_env(
|
|
45
|
+
tool="eiti_companies",
|
|
46
|
+
input={"company_name__like": company_name__like, "name": nm,
|
|
47
|
+
"size": result["limit_applied"]},
|
|
48
|
+
reason=(f"EITI name search: {len(result['candidates'])} candidates on this page / {matched} total upstream matches, "
|
|
49
|
+
f"top {top['company_name'] or '—'}"
|
|
50
|
+
f" ({top['hq_country'] or '—'}, reported {top['first_year_reported'] or '?'}–{top['last_year_reported'] or '?'}) — candidates are not authoritative normalization"
|
|
51
|
+
if top else f"EITI name search zero hits: not in the extractives filer universe (EITI has 10,200 companies; an empty result is a legal answer)"),
|
|
52
|
+
result=result,
|
|
53
|
+
evidence=[{"quote": f"{c['company_name'] or '—'} ({c['hq_country'] or '—'}, {c['sectors'] or '—'})",
|
|
54
|
+
"url": "https://eiti-database.eiti.org/eiti_database/view_companies.json"}
|
|
55
|
+
for c in result["candidates"][:5]],
|
|
56
|
+
provider_id="eiti", metas=[r["_meta"]],
|
|
57
|
+
query=quartet, deprecations=deps or None,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def eiti_payments(self, *, eiti_id_company__exact=None, eiti_id=None,
|
|
61
|
+
company_name__like=None, name=None, size=50,
|
|
62
|
+
execution=None, no_cache=None):
|
|
63
|
+
"""esg eiti_payments: company->government payment rows (EITI view_payments_detailed;
|
|
64
|
+
id channel eiti_id_company__exact verbatim XOR eiti_id sugar (adds the eiti_id_company: prefix),
|
|
65
|
+
name channel company_name__like verbatim XOR name sugar (%contains%) — the two channels may combine, XOR within each slot)."""
|
|
66
|
+
self._esg_only("eiti_payments")
|
|
67
|
+
deps, assembled_from = [], {}
|
|
68
|
+
raw_eid = str(eiti_id_company__exact or "").strip() or None
|
|
69
|
+
raw_like = str(company_name__like or "").strip() or None
|
|
70
|
+
eid = str(eiti_id or "").strip() or None
|
|
71
|
+
nm = str(name or "").strip() or None
|
|
72
|
+
if raw_eid and eid:
|
|
73
|
+
raise SdkError("usage_error", provider="eiti",
|
|
74
|
+
detail="eiti_id_company__exact (raw channel) and eiti_id (sugar: auto-adds the eiti_id_company: prefix) share one slot and are mutually exclusive — give only one")
|
|
75
|
+
if raw_like and nm:
|
|
76
|
+
raise SdkError("usage_error", provider="eiti",
|
|
77
|
+
detail="company_name__like (raw channel) and name (sugar: %contains%) share one slot and are mutually exclusive — give only one")
|
|
78
|
+
if not (raw_eid or raw_like or eid or nm):
|
|
79
|
+
raise SdkError("usage_error", provider="eiti",
|
|
80
|
+
detail="eiti_payments requires at least one of the id channel (eiti_id_company__exact / eiti_id) "
|
|
81
|
+
"or the name channel (company_name__like / name)"
|
|
82
|
+
"(local safety wall: a bare scan of all 104,484 rows is served by the batch pipeline)")
|
|
83
|
+
if eid:
|
|
84
|
+
assembled_from["eiti_id"] = eid
|
|
85
|
+
deps.append("eiti_id sugar assembles eiti_id_company__exact=eiti_id_company:<UUID> (official values carry the prefix) — "
|
|
86
|
+
"retained sugar (not deprecated); the raw channel is eiti_id_company__exact")
|
|
87
|
+
raw_eid = esg.eiti._norm_id(eid)
|
|
88
|
+
if nm:
|
|
89
|
+
assembled_from["name"] = nm
|
|
90
|
+
deps.append("name sugar assembles company_name__like=%…% (contains) — retained sugar (not deprecated); "
|
|
91
|
+
"the raw channel is company_name__like")
|
|
92
|
+
raw_like = f"%{nm}%"
|
|
93
|
+
r = esg.eiti.payments_raw(eiti_id_company__exact=raw_eid, company_name__like=raw_like,
|
|
94
|
+
size=size, cache=self._c._cache,
|
|
95
|
+
no_cache=self._c.no_cache if no_cache is None else bool(no_cache))
|
|
96
|
+
result = {k: x for k, x in r.items() if k != "_meta"}
|
|
97
|
+
countries = sorted({p["country_iso3"] for p in result["rows"] if p["country_iso3"]})
|
|
98
|
+
usd = sum(p["payment_value_usd"] for p in result["rows"]
|
|
99
|
+
if isinstance(p["payment_value_usd"], (int, float)))
|
|
100
|
+
total = esg.eiti.total_display(result.get("total_rows"), result.get("total_rows_capped"))
|
|
101
|
+
quartet = {"source": "assembled" if assembled_from else "raw", "upstream": r["params"]}
|
|
102
|
+
if assembled_from:
|
|
103
|
+
quartet["assembled_from"] = assembled_from
|
|
104
|
+
return self._flat_env(
|
|
105
|
+
tool="eiti_payments",
|
|
106
|
+
input={"eiti_id_company__exact": eiti_id_company__exact, "eiti_id": eid,
|
|
107
|
+
"company_name__like": company_name__like, "name": nm,
|
|
108
|
+
"size": result["limit_applied"]},
|
|
109
|
+
reason=(f"EITI payments face: {len(result['rows'])} rows on this page / {total} total upstream rows"
|
|
110
|
+
f" ({'/'.join(countries) or '—'}; USD ~${round(usd):,} is this page's subtotal, not the full total; GFS-classified figures are comparable across batches)"
|
|
111
|
+
if result["rows"] else "EITI payments face zero hits (an empty result is a legal answer — not a filer, or the name did not match)"),
|
|
112
|
+
result=result,
|
|
113
|
+
evidence=[{"quote": f"{p['company_name'] or '—'} {p['year'] or '—'} -> {p['gov_entity'] or '—'}: "
|
|
114
|
+
f"{p['revenue_stream'] or '—'}" + (f" (${p['payment_value_usd']})" if p["payment_value_usd"] else ""),
|
|
115
|
+
"url": "https://eiti-database.eiti.org/eiti_database/view_payments_detailed"}
|
|
116
|
+
for p in result["rows"][:5]],
|
|
117
|
+
provider_id="eiti", metas=[r["_meta"]],
|
|
118
|
+
query=quartet, deprecations=deps or None,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def climatetrace_owners(self, *, name, limit=10, offset=None, execution=None, no_cache=None):
|
|
122
|
+
"""esg climatetrace_owners: emissions-owner candidates (Climate TRACE v7 owners;
|
|
123
|
+
name/limit/offset straight through under upstream names — zero hits = empty candidates, the upstream 404 JSON is already mapped to a legal empty)."""
|
|
124
|
+
self._esg_only("climatetrace_owners")
|
|
125
|
+
v = str(name or "").strip()
|
|
126
|
+
if not v:
|
|
127
|
+
raise SdkError("usage_error", provider="climatetrace",
|
|
128
|
+
detail="climatetrace_owners requires name (company name, upstream param name)")
|
|
129
|
+
r = esg.climatetrace.owners_raw(v, limit=limit, offset=offset,
|
|
130
|
+
cache=self._c._cache,
|
|
131
|
+
no_cache=self._c.no_cache if no_cache is None else bool(no_cache))
|
|
132
|
+
result = {k: x for k, x in r.items() if k != "_meta"}
|
|
133
|
+
top = (result["candidates"] or [None])[0]
|
|
134
|
+
return self._flat_env(
|
|
135
|
+
tool="climatetrace_owners", input={"name": v, "limit": result["limit_applied"],
|
|
136
|
+
"offset": offset},
|
|
137
|
+
reason=(f"Climate TRACE owner search: {len(result['candidates'])} candidates, top {top['owner_name'] or '—'}"
|
|
138
|
+
f" ({top['trace_owner_id'] or '—'}) — candidates are not authoritative normalization, the rows face trusts owner_ids; "
|
|
139
|
+
f"ordering is unstable across limits (measured 2026-10-23), fetch enough in one go"
|
|
140
|
+
if top else f"Climate TRACE zero hits: '{v}' is not in the emissions-owner universe"
|
|
141
|
+
f" (point-source sectors such as oil & gas, mining, heavy industry, power; an empty result is a legal answer)"),
|
|
142
|
+
result=result,
|
|
143
|
+
evidence=[{"quote": f"{c['trace_owner_id'] or '—'} = {c['owner_name'] or '—'}",
|
|
144
|
+
"url": f"https://api.climatetrace.org/v7/owners?name={v}"}
|
|
145
|
+
for c in result["candidates"][:5]],
|
|
146
|
+
provider_id="climatetrace", metas=[r["_meta"]],
|
|
147
|
+
query={"source": "raw", "upstream": r["params"]},
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def climatetrace_sources(self, *, owner_ids=None, owner_name=None, year=None, gas=None,
|
|
151
|
+
limit=50, offset=None, sectors=None, subsectors=None,
|
|
152
|
+
gadm_id=None, city_id=None, country_group=None, continent=None,
|
|
153
|
+
execution=None, no_cache=None):
|
|
154
|
+
"""esg climatetrace_sources: emissions-owner -> facility/aggregate emission rows (Climate TRACE v7 sources;
|
|
155
|
+
owner_ids -> ownerIds verbatim (comma-joined = union with dedup) XOR owner_name sugar (resolves the first
|
|
156
|
+
candidate) — two channels, one of the two required; year has no local wall for out-of-window values (upstream
|
|
157
|
+
returns null, not an error); gas straight through under its own name — a non-co2e basis switches the row key
|
|
158
|
+
to emissions_quantity.
|
|
159
|
+
0.2.9 audit parameter expansion (2026-10-28 openapi + live probing): offset paging, sectors closed set
|
|
160
|
+
validated locally (invalid values make the upstream silently return the whole corpus = a false negative),
|
|
161
|
+
subsectors pass-through (upstream 400 self-validates), four location params gadm_id/city_id/
|
|
162
|
+
country_group/continent pass-through (mutual exclusion upstream-proven by a 400); at least one of
|
|
163
|
+
owner_ids / a filter param (soft wall against bare corpus scans)."""
|
|
164
|
+
self._esg_only("climatetrace_sources")
|
|
165
|
+
deps, assembled_from = [], None
|
|
166
|
+
oids = str(owner_ids or "").strip() or None
|
|
167
|
+
nm = str(owner_name or "").strip() or None
|
|
168
|
+
if oids and nm:
|
|
169
|
+
raise SdkError("usage_error", provider="climatetrace",
|
|
170
|
+
detail="owner_ids (raw channel: ownerIds comma union) and owner_name (sugar: resolves the first candidate) are mutually exclusive — give only one")
|
|
171
|
+
has_filter = any(x not in (None, "") for x in
|
|
172
|
+
(sectors, subsectors, gadm_id, city_id, country_group, continent))
|
|
173
|
+
if not oids and not nm and not has_filter:
|
|
174
|
+
raise SdkError("usage_error", provider="climatetrace",
|
|
175
|
+
detail="climatetrace_sources requires at least one of owner_ids (E key), owner_name (company name), "
|
|
176
|
+
"or a filter param (sectors/subsectors/gadm_id/city_id/country_group/continent)"
|
|
177
|
+
" — a bare whole-corpus scan goes through the batch pipeline service "
|
|
178
|
+
"(upstream allows it, but this face's politeness tier intercepts bulk corpus scans)")
|
|
179
|
+
cache, nc = self._c._cache, self._c.no_cache if no_cache is None else bool(no_cache)
|
|
180
|
+
metas = []
|
|
181
|
+
if nm and not oids:
|
|
182
|
+
s = esg.climatetrace.owners_raw(nm, limit=5, cache=cache, no_cache=nc)
|
|
183
|
+
metas.append(s["_meta"])
|
|
184
|
+
if not s["candidates"]:
|
|
185
|
+
return self._flat_env(
|
|
186
|
+
tool="climatetrace_sources", input={"owner_ids": oids, "owner_name": nm,
|
|
187
|
+
"year": year, "gas": gas, "limit": limit,
|
|
188
|
+
"offset": offset, "sectors": sectors,
|
|
189
|
+
"subsectors": subsectors, "gadm_id": gadm_id,
|
|
190
|
+
"city_id": city_id, "country_group": country_group,
|
|
191
|
+
"continent": continent},
|
|
192
|
+
reason=(f"Climate TRACE emissions face zero hits: owner name '{nm}' has no candidates"
|
|
193
|
+
f" (an empty result is a legal answer — not a point-source-sector owner; use the owner_ids raw channel on the rows face)"),
|
|
194
|
+
result={"rows": [], "count": 0, "resolved_owner": None,
|
|
195
|
+
"year": year, "gas": gas or "co2e_100yr", "matched_on": None},
|
|
196
|
+
evidence=[], provider_id="climatetrace", metas=metas,
|
|
197
|
+
query={"source": "assembled", "assembled_from": {"owner_name": nm},
|
|
198
|
+
"upstream": s["params"]},
|
|
199
|
+
deprecations=["owner_name sugar resolves the first owners candidate (not authoritative normalization)",
|
|
200
|
+
"— the raw channel is owner_ids"],
|
|
201
|
+
)
|
|
202
|
+
oids = s["candidates"][0]["trace_owner_id"]
|
|
203
|
+
assembled_from = {"owner_name": nm, "resolved_first_candidate": oids}
|
|
204
|
+
deps.append("owner_name sugar resolves the first owners candidate (not authoritative normalization) — the raw channel is owner_ids")
|
|
205
|
+
r = esg.climatetrace.sources_raw(oids, year=year, gas=gas, limit=limit, offset=offset,
|
|
206
|
+
sectors=sectors, subsectors=subsectors,
|
|
207
|
+
gadm_id=gadm_id, city_id=city_id,
|
|
208
|
+
country_group=country_group, continent=continent,
|
|
209
|
+
cache=cache, no_cache=nc)
|
|
210
|
+
metas.append(r["_meta"])
|
|
211
|
+
result = {k: x for k, x in r.items() if k != "_meta"}
|
|
212
|
+
countries = sorted({x["country"] for x in result["rows"] if x["country"]})
|
|
213
|
+
is_tco2e = result.get("gas") == "co2e_100yr"
|
|
214
|
+
rkey = "emissions_tco2e" if is_tco2e else "emissions_quantity"
|
|
215
|
+
skey = f"{rkey}_sum"
|
|
216
|
+
unit = "tCO2e (co2e_100yr basis)" if is_tco2e \
|
|
217
|
+
else f"{result.get('gas')} native unit (not tCO2e — row key emissions_quantity)"
|
|
218
|
+
yr_note = "" if (result["year"] or 0) >= 2021 else \
|
|
219
|
+
f"; year={result['year']} is outside the data window (2021 onward) — upstream returns empty, not an error (measured 2026-10-23)"
|
|
220
|
+
quartet = {"source": "assembled" if assembled_from else "raw", "upstream": r["params"]}
|
|
221
|
+
if assembled_from:
|
|
222
|
+
quartet["assembled_from"] = assembled_from
|
|
223
|
+
return self._flat_env(
|
|
224
|
+
tool="climatetrace_sources",
|
|
225
|
+
input={"owner_ids": owner_ids, "owner_name": owner_name,
|
|
226
|
+
"year": result["year"], "gas": result.get("gas"), "limit": r["params"]["limit"],
|
|
227
|
+
"offset": offset, "sectors": sectors, "subsectors": subsectors,
|
|
228
|
+
"gadm_id": gadm_id, "city_id": city_id,
|
|
229
|
+
"country_group": country_group, "continent": continent},
|
|
230
|
+
reason=(f"Climate TRACE emissions face: {len(result['rows'])} sources for {result['year']}"
|
|
231
|
+
f" totalling {result[skey]:,.2f} {unit}"
|
|
232
|
+
f" ({'/'.join(countries[:3]) or '—'}{'…' if len(countries) > 3 else ''}){yr_note}"
|
|
233
|
+
if result["rows"] else
|
|
234
|
+
f"Climate TRACE emissions face zero hits (an empty result is a legal answer — not a point-source-sector "
|
|
235
|
+
f"owner, or no data for that year; aggregate surfaces have source_type != point-source){yr_note}"),
|
|
236
|
+
result=result,
|
|
237
|
+
evidence=[{"quote": f"{x['name'] or '—'} ({x['country'] or '—'}, {x['sector'] or '—'}): "
|
|
238
|
+
+ (f"{round(x[rkey]):,}" if x.get(rkey) is not None else "—")
|
|
239
|
+
+ f" ({x['gas'] or ''}) {x['year'] or ''}",
|
|
240
|
+
"url": "https://api.climatetrace.org/v7/sources"}
|
|
241
|
+
for x in result["rows"][:5]],
|
|
242
|
+
provider_id="climatetrace", metas=metas,
|
|
243
|
+
query=quartet, deprecations=deps or None,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
def eutl_datasets(self, *, execution=None, no_cache=None):
|
|
247
|
+
"""esg eutl_datasets: EU carbon registry official bulk-download catalog (EUTL data-download;
|
|
248
|
+
zero upstream params, the whole catalog in one response — two families: Azure daily extracts x climate.ec.europa.eu yearly files; the file bodies go through the batch pipeline)."""
|
|
249
|
+
self._esg_only("eutl_datasets")
|
|
250
|
+
r = esg.eutl.datasets_raw(cache=self._c._cache,
|
|
251
|
+
no_cache=self._c.no_cache if no_cache is None else bool(no_cache))
|
|
252
|
+
result = {k: x for k, x in r.items() if k != "_meta"}
|
|
253
|
+
azure = [x for x in result["rows"] if x["cadence"] == "daily"]
|
|
254
|
+
yearly = [x for x in result["rows"] if x["cadence"] == "yearly"]
|
|
255
|
+
static = [x for x in result["rows"] if x["cadence"] == "static"]
|
|
256
|
+
top = (result["rows"] or [None])[0]
|
|
257
|
+
return self._flat_env(
|
|
258
|
+
tool="eutl_datasets", input={},
|
|
259
|
+
reason=(f"EUTL official bulk-download catalog: {result['count']} files — {len(azure)} Azure daily extracts"
|
|
260
|
+
f" (accounts/operators/holdings csv.gz) + {len(yearly)} European Commission yearly files"
|
|
261
|
+
f" (compliance/verified emissions 2021-2025) + {len(static)} transactions file — a coordinates face, the file bodies go through the batch pipeline"
|
|
262
|
+
if result["rows"] else "EUTL catalog empty (the portal has no files — an abnormal state, not the legal-empty semantics)"),
|
|
263
|
+
result=result,
|
|
264
|
+
evidence=[{"quote": f"{x['name'] or '—'} ({x['cadence']}): {x['url'] or '—'}",
|
|
265
|
+
"url": "https://union-registry-data.ec.europa.eu/api/data-download"}
|
|
266
|
+
for x in result["rows"][:5]],
|
|
267
|
+
provider_id="eutl", metas=[r["_meta"]],
|
|
268
|
+
query={"source": "raw", "upstream": r["params"]},
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def epa_frs_facilities(self, *, registry_id=None, primary_name=None, state_code=None,
|
|
272
|
+
city_name=None, country_name=None, site_type_name=None, epa_region_code=None,
|
|
273
|
+
start=0, size=50, execution=None, no_cache=None):
|
|
274
|
+
"""esg epa_frs_facilities: FRS facility master point query (Envirofacts efservice frs_facility_site;
|
|
275
|
+
seven filter columns under upstream names (all `=` exact, combinable AND) + start/size closed-interval
|
|
276
|
+
paging — born flat, zero sugar; 0.3.20 audit added country_name/site_type_name/epa_region_code,
|
|
277
|
+
country_name unlocking the non-US facility slice GERMANY=110)."""
|
|
278
|
+
self._esg_only("epa_frs_facilities")
|
|
279
|
+
r = esg.epa_frs.facilities_raw(registry_id=registry_id, primary_name=primary_name,
|
|
280
|
+
state_code=state_code, city_name=city_name,
|
|
281
|
+
country_name=country_name, site_type_name=site_type_name,
|
|
282
|
+
epa_region_code=epa_region_code,
|
|
283
|
+
start=start, size=size, cache=self._c._cache,
|
|
284
|
+
no_cache=self._c.no_cache if no_cache is None else bool(no_cache))
|
|
285
|
+
result = {k: x for k, x in r.items() if k not in ("_meta", "_count_meta")}
|
|
286
|
+
top = (result["rows"] or [None])[0]
|
|
287
|
+
total = result.get("total_matches")
|
|
288
|
+
total_s = f"{total:,}" if isinstance(total, int) else "?"
|
|
289
|
+
exact_note = ("; primary_name is an exact match (the upstream contains/~ operators silently return 0 — fuzzy facility names go through the ECHO face)"
|
|
290
|
+
if primary_name else "")
|
|
291
|
+
return self._flat_env(
|
|
292
|
+
tool="epa_frs_facilities",
|
|
293
|
+
input={"registry_id": registry_id, "primary_name": primary_name,
|
|
294
|
+
"state_code": (state_code or "").strip().upper() if state_code else None,
|
|
295
|
+
"city_name": city_name,
|
|
296
|
+
"country_name": (country_name or "").strip().upper() if country_name else None,
|
|
297
|
+
"site_type_name": (site_type_name or "").strip().upper() if site_type_name else None,
|
|
298
|
+
"epa_region_code": (epa_region_code or "").strip().upper() if epa_region_code else None,
|
|
299
|
+
"start": result["offset"],
|
|
300
|
+
"size": result["limit_applied"]},
|
|
301
|
+
reason=(f"FRS facility master registry: {result['count']} rows on this page / {total_s} total matches"
|
|
302
|
+
f", top {top['primary_name'] or '—'} ({top['registry_id'] or '—'}, "
|
|
303
|
+
f"{top['std_full_address'] or top['location_address'] or '—'}) — "
|
|
304
|
+
f"registry_id is the authoritative ECHO/TRI/GHGRP convergence key{exact_note}"
|
|
305
|
+
if top else f"FRS facility master registry zero hits: 0 rows on this page / {total_s} total matches"
|
|
306
|
+
f" (an empty result is a legal answer — the name did not match, or the state has no facilities){exact_note}"),
|
|
307
|
+
result=result,
|
|
308
|
+
evidence=[{"quote": f"{x['registry_id'] or '—'} = {x['primary_name'] or '—'}"
|
|
309
|
+
f" ({x['std_full_address'] or x['location_address'] or '—'})",
|
|
310
|
+
"url": "https://data.epa.gov/efservice/frs_facility_site"}
|
|
311
|
+
for x in result["rows"][:5]],
|
|
312
|
+
provider_id="epa_frs", metas=[r["_meta"], r["_count_meta"]],
|
|
313
|
+
query={"source": "raw", "upstream": r["params"]},
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
# ---------- 0.3.5 port-state emissions/supervision axis (thetis_mrv catalog face + paris_mou four snapshot/search faces) ----------
|
|
317
|
+
|
|
318
|
+
def thetis_mrv_datasets(self, *, execution=None, no_cache=None):
|
|
319
|
+
"""esg thetis_mrv_datasets: EU MRV ship emissions-reporting versioned bulk-file catalog (THETIS-MRV
|
|
320
|
+
reporting-periods x downloadable-files combined — the reporting-period x version ledger; XLSX bodies go through the batch pipeline)."""
|
|
321
|
+
self._esg_only("thetis_mrv_datasets")
|
|
322
|
+
r = esg.thetis_mrv.datasets_raw(cache=self._c._cache,
|
|
323
|
+
no_cache=self._c.no_cache if no_cache is None else bool(no_cache))
|
|
324
|
+
result = {k: x for k, x in r.items() if k != "_meta"}
|
|
325
|
+
top = (result["rows"] or [None])[0]
|
|
326
|
+
periods = result.get("reporting_periods") or []
|
|
327
|
+
return self._flat_env(
|
|
328
|
+
tool="thetis_mrv_datasets", input={},
|
|
329
|
+
reason=(f"THETIS-MRV emissions-reporting file catalog: {len(periods)} reporting periods ({periods[-1] if periods else '—'}-"
|
|
330
|
+
f"{periods[0] if periods else '—'}) x {result['count']} versioned publication files — newest "
|
|
331
|
+
f"{top['reporting_period']} v{top['version']} (regenerated {top['generation_date']}); "
|
|
332
|
+
f"the version ledger rolls in jumps, reconcile harvests against the listing and never guess file names; XLSX bodies go through the batch pipeline"
|
|
333
|
+
if top else "THETIS-MRV version ledger empty (the portal has no files — an abnormal state, not the legal-empty semantics)"),
|
|
334
|
+
result=result,
|
|
335
|
+
evidence=[{"quote": f"{x['reporting_period']} v{x['version']} ({x['generation_date']}): {x['file_name']}",
|
|
336
|
+
"url": "https://mrv.emsa.europa.eu/api/public-emission-report/downloadable-files"}
|
|
337
|
+
for x in result["rows"][:5]],
|
|
338
|
+
provider_id="thetis_mrv", metas=[r["_meta"]],
|
|
339
|
+
query={"source": "raw", "upstream": r["params"]},
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
def paris_mou_detentions(self, *, execution=None, no_cache=None):
|
|
343
|
+
"""esg paris_mou_detentions: Paris MoU currently-detained ships snapshot (THETIS getCurrentDetentions;
|
|
344
|
+
a daily-rolling live state — ship IMO / detention port / reporting authority; historical detentions go through the inspections window search)."""
|
|
345
|
+
self._esg_only("paris_mou_detentions")
|
|
346
|
+
r = esg.paris_mou.detentions_raw(cache=self._c._cache,
|
|
347
|
+
no_cache=self._c.no_cache if no_cache is None else bool(no_cache))
|
|
348
|
+
result = {k: x for k, x in r.items() if k != "_meta"}
|
|
349
|
+
top = (result["rows"] or [None])[0]
|
|
350
|
+
return self._flat_env(
|
|
351
|
+
tool="paris_mou_detentions", input={},
|
|
352
|
+
reason=(f"Paris MoU current detentions: {result['count']} ships detained (e.g. {top['ship_name']} / IMO "
|
|
353
|
+
f"{top['imo_number']}, detained {top['detention_date']} at {top['detention_country_code']} "
|
|
354
|
+
f"{top['detention_port']}) — a live-state snapshot rolling daily; the ship IMO number is the asset key"
|
|
355
|
+
if top else "Paris MoU currently zero detentions (a legal empty for the live state — a clean-list day)"),
|
|
356
|
+
result=result,
|
|
357
|
+
evidence=[{"quote": f"{x['ship_name']} (IMO {x['imo_number']}, {x['flag_code']}, "
|
|
358
|
+
f"{x['ship_type'] or '—'}): detained {x['detention_date']} at "
|
|
359
|
+
f"{x['detention_country_code'] or '—'} {x['detention_port'] or '—'}",
|
|
360
|
+
"url": "https://portal.emsa.europa.eu/o/portlet-public/rest/detention/getCurrentDetentions.json"}
|
|
361
|
+
for x in result["rows"][:5]],
|
|
362
|
+
provider_id="paris_mou", metas=[r["_meta"]],
|
|
363
|
+
query={"source": "raw", "upstream": r["params"]},
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
def paris_mou_bans(self, *, execution=None, no_cache=None):
|
|
367
|
+
"""esg paris_mou_bans: Paris MoU banned ships register (THETIS getBanShips; the embedded ismCompany is
|
|
368
|
+
flattened into the official ship x ISM management-company edge — the company IMO number is a zero-padded 7-digit string)."""
|
|
369
|
+
self._esg_only("paris_mou_bans")
|
|
370
|
+
r = esg.paris_mou.bans_raw(cache=self._c._cache,
|
|
371
|
+
no_cache=self._c.no_cache if no_cache is None else bool(no_cache))
|
|
372
|
+
result = {k: x for k, x in r.items() if k != "_meta"}
|
|
373
|
+
top = (result["rows"] or [None])[0]
|
|
374
|
+
with_co = [x for x in result["rows"] if x.get("company_imo_number")]
|
|
375
|
+
return self._flat_env(
|
|
376
|
+
tool="paris_mou_bans", input={},
|
|
377
|
+
reason=(f"Paris MoU bans register: {result['count']} rows, {len(with_co)} of which carry an ISM management-company "
|
|
378
|
+
f"IMO number (e.g. {top['ship_name']} / IMO {top['imo_number']} -> {top['company_name']} / "
|
|
379
|
+
f"company IMO {top['company_imo_number']}, banned {top['ban_date']}) — "
|
|
380
|
+
f"the official ship x management-company edge emitted directly; the company key can be matched against shipping-company records"
|
|
381
|
+
if top else "Paris MoU bans register empty (a legal empty for the live state)"),
|
|
382
|
+
result=result,
|
|
383
|
+
evidence=[{"quote": f"{x['ship_name']} (IMO {x['imo_number']}, {x['flag_code']}): "
|
|
384
|
+
f"{x['ban_date']} {x['ban_occurrence'] or ''} ban"
|
|
385
|
+
f" ({x['banning_authority'] or '—'}) -> {x['company_name'] or '—'}"
|
|
386
|
+
f" (company IMO {x['company_imo_number'] or 'none'})",
|
|
387
|
+
"url": "https://portal.emsa.europa.eu/o/portlet-public/rest/ban/getBanShips.json"}
|
|
388
|
+
for x in result["rows"][:5]],
|
|
389
|
+
provider_id="paris_mou", metas=[r["_meta"]],
|
|
390
|
+
query={"source": "raw", "upstream": r["params"]},
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
def paris_mou_low_performance_companies(self, *, execution=None, no_cache=None):
|
|
394
|
+
"""esg paris_mou_low_performance_companies: Paris MoU low-performance ISM company list
|
|
395
|
+
(THETIS getLowPerformance6MonthsISMCompanies, rolling 6-month window — 100% carry an IMO company number)."""
|
|
396
|
+
self._esg_only("paris_mou_low_performance_companies")
|
|
397
|
+
r = esg.paris_mou.low_performance_raw(cache=self._c._cache,
|
|
398
|
+
no_cache=self._c.no_cache if no_cache is None else bool(no_cache))
|
|
399
|
+
result = {k: x for k, x in r.items() if k != "_meta"}
|
|
400
|
+
top = (result["rows"] or [None])[0]
|
|
401
|
+
countries = len({x["country_code"] for x in result["rows"] if x.get("country_code")})
|
|
402
|
+
return self._flat_env(
|
|
403
|
+
tool="paris_mou_low_performance_companies", input={},
|
|
404
|
+
reason=(f"Paris MoU low-performance ISM companies: {result['count']} companies across {countries} countries/regions, 100% carrying an IMO "
|
|
405
|
+
f"company number (e.g. {top['name']} / IMO {top['imo_number']}, {top['country_name']}, "
|
|
406
|
+
f"{top['performance']}, listed {top['effect_date']}) — a rolling 6-month-window company-risk attribute list"
|
|
407
|
+
if top else "Paris MoU low-performance list empty (a legal empty for the rolling window)"),
|
|
408
|
+
result=result,
|
|
409
|
+
evidence=[{"quote": f"{x['name']} (IMO {x['imo_number']}, {x['country_name']}, "
|
|
410
|
+
f"{x['performance']}, listed {x['effect_date']})",
|
|
411
|
+
"url": "https://portal.emsa.europa.eu/o/portlet-public/rest/companyperformance/getLowPerformance6MonthsISMCompanies.json"}
|
|
412
|
+
for x in result["rows"][:5]],
|
|
413
|
+
provider_id="paris_mou", metas=[r["_meta"]],
|
|
414
|
+
query={"source": "raw", "upstream": r["params"]},
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
def paris_mou_inspections(self, *, period_min, period_max, psc_inspection_regime=1,
|
|
418
|
+
page=1, start=0, limit=50,
|
|
419
|
+
ism_company_name=None, ism_company_imo_number=None,
|
|
420
|
+
execution=None, no_cache=None):
|
|
421
|
+
"""esg paris_mou_inspections: Paris MoU historical PSC inspection stream window search (THETIS getPublicInspections
|
|
422
|
+
form-encoded POST; period_min/period_max dd/mm/yyyy window <= 3 years + psc_inspection_regime (1 = the only live
|
|
423
|
+
value, 0.3.20 audit: 0 -> poisoned empty, 2 -> HTTP 500, walled with evidence) + page/start/limit paging +
|
|
424
|
+
ism_company_name/ism_company_imo_number direct company query (IMO padded/unpadded both accepted) — the resume
|
|
425
|
+
window stats ride along)."""
|
|
426
|
+
self._esg_only("paris_mou_inspections")
|
|
427
|
+
r = esg.paris_mou.inspections_raw(period_min=period_min, period_max=period_max,
|
|
428
|
+
psc_inspection_regime=psc_inspection_regime,
|
|
429
|
+
page=page, start=start, limit=limit,
|
|
430
|
+
ism_company_name=ism_company_name,
|
|
431
|
+
ism_company_imo_number=ism_company_imo_number,
|
|
432
|
+
cache=self._c._cache,
|
|
433
|
+
no_cache=self._c.no_cache if no_cache is None else bool(no_cache))
|
|
434
|
+
result = {k: x for k, x in r.items() if k != "_meta"}
|
|
435
|
+
top = (result["rows"] or [None])[0]
|
|
436
|
+
total = result.get("total")
|
|
437
|
+
total_s = f"{total:,}" if isinstance(total, int) else "?"
|
|
438
|
+
resume = result.get("resume") or {}
|
|
439
|
+
resume_s = (f"window stats: {resume.get('inspections')} inspections/{resume.get('ships')} ships/"
|
|
440
|
+
f"{resume.get('detentions')} detentions/{resume.get('ism_deficiencies')} ISM deficiencies"
|
|
441
|
+
if resume else "no window stats")
|
|
442
|
+
co_filter = (f"; company filter {ism_company_imo_number or ism_company_name}"
|
|
443
|
+
if (ism_company_imo_number or ism_company_name) else "")
|
|
444
|
+
return self._flat_env(
|
|
445
|
+
tool="paris_mou_inspections",
|
|
446
|
+
input={"period_min": period_min, "period_max": period_max,
|
|
447
|
+
"psc_inspection_regime": r["params"]["pscInspectionRegime"],
|
|
448
|
+
"page": result["paging"]["page"], "start": result["paging"]["start"],
|
|
449
|
+
"limit": result["paging"]["limit"],
|
|
450
|
+
"ism_company_name": ism_company_name,
|
|
451
|
+
"ism_company_imo_number": ism_company_imo_number},
|
|
452
|
+
reason=(f"Paris MoU historical inspection stream: window {period_min}–{period_max}, {result['count']} rows on this page / whole window "
|
|
453
|
+
f"{total_s}{co_filter} — {resume_s}; first row {top['ship_name']} (IMO {top['imo_number']}) "
|
|
454
|
+
f"{top['inspection_date']} @{top['member_state']} {top['port_name']} "
|
|
455
|
+
f"{top['ndeficiencies']} deficiencies — the id key allows a per-inspection deep dive (getInspectionDetail)"
|
|
456
|
+
if top else f"Paris MoU historical inspection stream zero hits: window {period_min}–{period_max}, whole window "
|
|
457
|
+
f"{total_s} rows{co_filter} (an empty window or a too-narrow filter = a legal empty) — {resume_s}"),
|
|
458
|
+
result=result,
|
|
459
|
+
evidence=[{"quote": f"{x['ship_name']} (IMO {x['imo_number']}, {x['flag_code']}): "
|
|
460
|
+
f"{x['inspection_date']} @{x['member_state']} {x['port_name']} — "
|
|
461
|
+
f"{x['ndeficiencies']} deficiencies ({x['inspection_type'] or '—'})",
|
|
462
|
+
"url": "https://portal.emsa.europa.eu/o/portlet-public/rest/inspection/getPublicInspections.json"}
|
|
463
|
+
for x in result["rows"][:5]],
|
|
464
|
+
provider_id="paris_mou", metas=[r["_meta"]],
|
|
465
|
+
query={"source": "raw", "upstream": r["params"]},
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# ---------- 0.3.22 transport-compliance axis (EU air safety list catalog face) ----------
|
|
469
|
+
|
|
470
|
+
def euasl_datasets(self, *, execution=None, no_cache=None):
|
|
471
|
+
"""esg euasl_datasets: EU Air Safety List download catalog (banned/restricted third-country air
|
|
472
|
+
carriers — one XLSX machine-readable master + 24 official-language PDF renditions; the document UUIDs
|
|
473
|
+
rotate on every republication, reconcile against this listing and never guess file names; the binary
|
|
474
|
+
bodies go through the batch pipeline — same posture as eutl/thetis_mrv)."""
|
|
475
|
+
self._esg_only("euasl_datasets")
|
|
476
|
+
r = esg.euasl.datasets_raw(cache=self._c._cache,
|
|
477
|
+
no_cache=self._c.no_cache if no_cache is None else bool(no_cache))
|
|
478
|
+
result = {k: x for k, x in r.items() if k != "_meta"}
|
|
479
|
+
top = (result["rows"] or [None])[0]
|
|
480
|
+
master = result.get("master")
|
|
481
|
+
langs = len({x["language"] for x in result["rows"] if x.get("language")})
|
|
482
|
+
return self._flat_env(
|
|
483
|
+
tool="euasl_datasets", input={},
|
|
484
|
+
reason=(f"EU Air Safety List download catalog: {result['count']} files across {langs} languages — the machine-readable "
|
|
485
|
+
f"XLSX master ({master['file_name']}) sorts first, the rest are official-language PDF renditions; "
|
|
486
|
+
f"Annex A = carriers under an operating ban, Annex B = restrictions/conditions; "
|
|
487
|
+
f"the document UUIDs rotate on every republication — reconcile against this listing, the binary bodies go through the batch pipeline"
|
|
488
|
+
if top and master else
|
|
489
|
+
"EU Air Safety List page rendered without download links (an abnormal state — the Commission reorganization moved the page, not a legal empty"),
|
|
490
|
+
result=result,
|
|
491
|
+
evidence=[{"quote": f"{x['file_name']} ({x['format']}, {x['language']}): {x['download_path']}",
|
|
492
|
+
"url": "https://transport.ec.europa.eu/transport-themes/eu-air-safety-list_en"}
|
|
493
|
+
for x in result["rows"][:5]],
|
|
494
|
+
provider_id="euasl", metas=[r["_meta"]],
|
|
495
|
+
query={"source": "raw", "upstream": r["params"]},
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
# ---------- 0.2.0 flat face commands (legal: source-qualified names + upstream param names verbatim + our defaults) ----------
|