openbb-tensorust 0.1.0__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.
- openbb_tensorust/__init__.py +52 -0
- openbb_tensorust/client.py +110 -0
- openbb_tensorust/models/__init__.py +1 -0
- openbb_tensorust/models/contractor.py +213 -0
- openbb_tensorust/models/credit.py +261 -0
- openbb_tensorust/models/exposure.py +107 -0
- openbb_tensorust/models/idr.py +128 -0
- openbb_tensorust/router.py +115 -0
- openbb_tensorust-0.1.0.dist-info/METADATA +65 -0
- openbb_tensorust-0.1.0.dist-info/RECORD +13 -0
- openbb_tensorust-0.1.0.dist-info/WHEEL +4 -0
- openbb_tensorust-0.1.0.dist-info/entry_points.txt +5 -0
- openbb_tensorust-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""OpenBB provider and router extension for Tensorust data.
|
|
2
|
+
|
|
3
|
+
Private credit loan marks from SEC BDC filings, federal contractor credit,
|
|
4
|
+
banned-vendor exposure in federal research awards, and No Surprises Act IDR
|
|
5
|
+
offer bands. Free previews without a key; set ``tensorust_api_key`` (or the
|
|
6
|
+
``TENSORUST_API_KEY`` environment variable) for full records.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from openbb_core.provider.abstract.provider import Provider
|
|
10
|
+
|
|
11
|
+
from openbb_tensorust.models.contractor import (
|
|
12
|
+
TensorustContractorCreditFetcher,
|
|
13
|
+
TensorustContractorWatchFetcher,
|
|
14
|
+
)
|
|
15
|
+
from openbb_tensorust.models.credit import (
|
|
16
|
+
TensorustBdcLoanMarksFetcher,
|
|
17
|
+
TensorustCreditDisagreementsFetcher,
|
|
18
|
+
TensorustCreditQ3CutsFetcher,
|
|
19
|
+
)
|
|
20
|
+
from openbb_tensorust.models.exposure import TensorustExposureInstitutionFetcher
|
|
21
|
+
from openbb_tensorust.models.idr import TensorustIdrOfferFetcher
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.0"
|
|
24
|
+
|
|
25
|
+
tensorust_provider = Provider(
|
|
26
|
+
name="tensorust",
|
|
27
|
+
website="https://tensorust.com",
|
|
28
|
+
description=(
|
|
29
|
+
"Tensorust builds entity-level datasets from US public records: what BDC (private credit) "
|
|
30
|
+
"lenders mark each first-lien loan at, which federal contractors sit on marked-down loans, "
|
|
31
|
+
"which federal research awards pay for work by vendors on banned-vendor lists, and the "
|
|
32
|
+
"No Surprises Act IDR offer band by code, payer and state. Every command answers a free "
|
|
33
|
+
"preview without a key."
|
|
34
|
+
),
|
|
35
|
+
credentials=["api_key"],
|
|
36
|
+
fetcher_dict={
|
|
37
|
+
"TensorustBdcLoanMarks": TensorustBdcLoanMarksFetcher,
|
|
38
|
+
"TensorustCreditDisagreements": TensorustCreditDisagreementsFetcher,
|
|
39
|
+
"TensorustCreditQ3Cuts": TensorustCreditQ3CutsFetcher,
|
|
40
|
+
"TensorustContractorCredit": TensorustContractorCreditFetcher,
|
|
41
|
+
"TensorustContractorWatch": TensorustContractorWatchFetcher,
|
|
42
|
+
"TensorustExposureInstitution": TensorustExposureInstitutionFetcher,
|
|
43
|
+
"TensorustIdrOffer": TensorustIdrOfferFetcher,
|
|
44
|
+
},
|
|
45
|
+
repr_name="Tensorust",
|
|
46
|
+
instructions=(
|
|
47
|
+
"No key is needed for previews. For full records set the credential tensorust_api_key "
|
|
48
|
+
"to a Tensorust enterprise key (tdk_...): https://shop.tensorust.com/offers/enterprise-api"
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
__all__ = ["__version__", "tensorust_provider"]
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Tensorust MCP client: one JSON-RPC ``tools/call`` per OpenBB command.
|
|
2
|
+
|
|
3
|
+
Every fetcher in this package calls the public Tensorust MCP server
|
|
4
|
+
(https://shop.tensorust.com/api/mcp). Without a key it answers free previews.
|
|
5
|
+
With a Tensorust key (``tensorust_api_key`` in OpenBB credentials, or the
|
|
6
|
+
``TENSORUST_API_KEY`` environment variable) the call asks for ``full=true``
|
|
7
|
+
and the server returns full records.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
import requests
|
|
17
|
+
from openbb_core.app.model.abstract.error import OpenBBError
|
|
18
|
+
from openbb_core.provider.utils.errors import EmptyDataError
|
|
19
|
+
|
|
20
|
+
MCP_URL = "https://shop.tensorust.com/api/mcp"
|
|
21
|
+
KEY_URL = "https://shop.tensorust.com/offers/enterprise-api"
|
|
22
|
+
USER_AGENT = "openbb-tensorust"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def mcp_url() -> str:
|
|
26
|
+
"""Endpoint, overridable with TENSORUST_MCP_URL (tests, staging)."""
|
|
27
|
+
return os.environ.get("TENSORUST_MCP_URL", MCP_URL)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def api_key(credentials: dict[str, str] | None) -> str | None:
|
|
31
|
+
"""The Tensorust key from OpenBB credentials, else from the environment."""
|
|
32
|
+
key = (credentials or {}).get("tensorust_api_key") or os.environ.get("TENSORUST_API_KEY")
|
|
33
|
+
return key.strip() if key and key.strip() else None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def call_tool(
|
|
37
|
+
name: str,
|
|
38
|
+
arguments: dict[str, Any],
|
|
39
|
+
credentials: dict[str, str] | None = None,
|
|
40
|
+
timeout: float = 60.0,
|
|
41
|
+
) -> dict[str, Any]:
|
|
42
|
+
"""Call one Tensorust MCP tool and return its JSON answer as a dict.
|
|
43
|
+
|
|
44
|
+
Adds ``full=true`` and the bearer key when a key is configured.
|
|
45
|
+
Raises OpenBBError on transport or tool errors.
|
|
46
|
+
"""
|
|
47
|
+
key = api_key(credentials)
|
|
48
|
+
args = {k: v for k, v in arguments.items() if v is not None and v != ""}
|
|
49
|
+
headers = {
|
|
50
|
+
"content-type": "application/json",
|
|
51
|
+
"accept": "application/json",
|
|
52
|
+
"user-agent": USER_AGENT,
|
|
53
|
+
}
|
|
54
|
+
if key:
|
|
55
|
+
headers["authorization"] = f"Bearer {key}"
|
|
56
|
+
args["full"] = True
|
|
57
|
+
payload = {
|
|
58
|
+
"jsonrpc": "2.0",
|
|
59
|
+
"id": 1,
|
|
60
|
+
"method": "tools/call",
|
|
61
|
+
"params": {"name": name, "arguments": args},
|
|
62
|
+
}
|
|
63
|
+
try:
|
|
64
|
+
resp = requests.post(mcp_url(), json=payload, headers=headers, timeout=timeout)
|
|
65
|
+
except requests.RequestException as exc: # pragma: no cover - network
|
|
66
|
+
raise OpenBBError(f"Tensorust request failed: {exc}") from exc
|
|
67
|
+
if resp.status_code != 200:
|
|
68
|
+
raise OpenBBError(f"Tensorust answered HTTP {resp.status_code}: {resp.text[:300]}")
|
|
69
|
+
body = resp.json()
|
|
70
|
+
if "error" in body:
|
|
71
|
+
raise OpenBBError(f"Tensorust error: {body['error'].get('message', body['error'])}")
|
|
72
|
+
result = body.get("result") or {}
|
|
73
|
+
text = "".join(c.get("text", "") for c in result.get("content", []) if c.get("type") == "text")
|
|
74
|
+
if result.get("isError"):
|
|
75
|
+
raise EmptyDataError(text or f"Tensorust tool {name} returned an error.")
|
|
76
|
+
try:
|
|
77
|
+
data = json.loads(text)
|
|
78
|
+
except json.JSONDecodeError as exc:
|
|
79
|
+
raise OpenBBError(f"Tensorust tool {name} returned non-JSON text: {text[:200]}") from exc
|
|
80
|
+
if not isinstance(data, dict):
|
|
81
|
+
raise OpenBBError(f"Tensorust tool {name} returned {type(data).__name__}, not an object.")
|
|
82
|
+
return data
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def not_found(data: dict[str, Any], what: str) -> EmptyDataError:
|
|
86
|
+
"""EmptyDataError naming the closest matches the server suggested."""
|
|
87
|
+
similar = data.get("similar") or []
|
|
88
|
+
names = [s.get("name") if isinstance(s, dict) else str(s) for s in similar if s]
|
|
89
|
+
hint = f" Closest names: {', '.join(n for n in names if n)}." if names else ""
|
|
90
|
+
note = f" {data['note']}" if data.get("note") else ""
|
|
91
|
+
return EmptyDataError(f"No {what} matches '{data.get('query', '')}'.{note}{hint}")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def access_note(data: dict[str, Any]) -> str | None:
|
|
95
|
+
"""How to get the full answer, when this one is a preview (None when it is already full)."""
|
|
96
|
+
if data.get("access") in ("full", "key", "free"):
|
|
97
|
+
return None
|
|
98
|
+
scope = ""
|
|
99
|
+
for k in ("access", "full_data", "full"):
|
|
100
|
+
v = data.get(k)
|
|
101
|
+
if isinstance(v, str) and v.lower().startswith(("top ", "first ")):
|
|
102
|
+
scope = f" ({v.split(';')[0].strip()})"
|
|
103
|
+
break
|
|
104
|
+
is_preview = data.get("access") is not None or data.get("preview") is True or "full_data" in data
|
|
105
|
+
if not is_preview:
|
|
106
|
+
return None
|
|
107
|
+
return (
|
|
108
|
+
f"Free preview{scope}. For full records set obb.user.credentials.tensorust_api_key "
|
|
109
|
+
f"(or TENSORUST_API_KEY) to a Tensorust key: {KEY_URL}"
|
|
110
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tensorust provider models."""
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Contractor Credit: federal contractors financed by BDC lenders, and how those lenders mark them.
|
|
2
|
+
|
|
3
|
+
Sources: SEC BDC schedules of investments (Q3 2026 from 10-Qs as filed),
|
|
4
|
+
USAspending.gov, SAM parent UEI, DOL Form 5500, FCC, FAA and county records.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Literal
|
|
10
|
+
|
|
11
|
+
from openbb_core.provider.abstract.annotated_result import AnnotatedResult
|
|
12
|
+
from openbb_core.provider.abstract.data import Data
|
|
13
|
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
|
14
|
+
from openbb_core.provider.abstract.query_params import QueryParams
|
|
15
|
+
from pydantic import Field
|
|
16
|
+
|
|
17
|
+
from openbb_tensorust.client import access_note, call_tool, not_found
|
|
18
|
+
|
|
19
|
+
MARK = "Fraction of par (0.95 = 95% of par)."
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ---- contractor_credit ----------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TensorustContractorCreditQueryParams(QueryParams):
|
|
26
|
+
"""Contractor credit query."""
|
|
27
|
+
|
|
28
|
+
contractor: str = Field(description="Federal contractor name, 12-character UEI or 9-digit EIN.")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TensorustContractorCreditData(Data):
|
|
32
|
+
"""One federal contractor's credit picture."""
|
|
33
|
+
|
|
34
|
+
operating_company: str = Field(description="Federal award recipient.")
|
|
35
|
+
uei: str | None = Field(default=None, description="SAM UEI of the recipient.")
|
|
36
|
+
state: str | None = Field(default=None, description="Recipient state.")
|
|
37
|
+
borrower: str | None = Field(default=None, description="Borrower name in BDC filings.")
|
|
38
|
+
sam_parent: str | None = Field(default=None, description="Parent reported to SAM.")
|
|
39
|
+
federal_obligations_fy2025_to_date: float | None = Field(default=None, description="USD.")
|
|
40
|
+
dod_fy2024_2026: float | None = Field(default=None, description="DoD obligations FY2024-26, USD.")
|
|
41
|
+
top_agency: str | None = Field(default=None, description="Agency with the most obligations.")
|
|
42
|
+
marks_as_of: str | None = Field(default=None, description="Quarter end of the BDC marks.")
|
|
43
|
+
lowest_mark: float | None = Field(default=None, description="Lowest Jun 30 BDC mark. " + MARK)
|
|
44
|
+
highest_mark: float | None = Field(default=None, description="Highest Jun 30 BDC mark. " + MARK)
|
|
45
|
+
sep30_lowest_mark: float | None = Field(default=None, description="Lowest Sep 30 2026 mark filed so far. " + MARK)
|
|
46
|
+
all_debt_mark: float | None = Field(
|
|
47
|
+
default=None, description="Par-weighted mark of every BDC debt line, latest quarter."
|
|
48
|
+
)
|
|
49
|
+
bdc_par: float | None = Field(default=None, description="BDC first-lien par, USD.")
|
|
50
|
+
headcount_change_pct: float | None = Field(default=None, description="Form 5500 participants, 2024 to 2025, %.")
|
|
51
|
+
signals: list[str] = Field(default_factory=list, description="Stress signals.")
|
|
52
|
+
market_priced: str | None = Field(default=None, description="Set when the credit is already market priced.")
|
|
53
|
+
read: str | None = Field(default=None, description="One-paragraph summary.")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class TensorustContractorCreditFetcher(
|
|
57
|
+
Fetcher[TensorustContractorCreditQueryParams, list[TensorustContractorCreditData]]
|
|
58
|
+
):
|
|
59
|
+
"""contractor_credit: obligations, lender marks, headcount and signals for one contractor."""
|
|
60
|
+
|
|
61
|
+
require_credentials = False
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def transform_query(params: dict[str, Any]) -> TensorustContractorCreditQueryParams:
|
|
65
|
+
return TensorustContractorCreditQueryParams(**params)
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def extract_data(
|
|
69
|
+
query: TensorustContractorCreditQueryParams,
|
|
70
|
+
credentials: dict[str, str] | None,
|
|
71
|
+
**kwargs: Any,
|
|
72
|
+
) -> dict[str, Any]:
|
|
73
|
+
return call_tool("contractor_credit", {"name": query.contractor}, credentials)
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def transform_data(
|
|
77
|
+
query: TensorustContractorCreditQueryParams, data: dict[str, Any], **kwargs: Any
|
|
78
|
+
) -> AnnotatedResult[list[TensorustContractorCreditData]]:
|
|
79
|
+
if not data.get("found"):
|
|
80
|
+
raise not_found(data, "BDC-financed federal contractor")
|
|
81
|
+
op = data.get("operating_company") or {}
|
|
82
|
+
fed = data.get("federal") or {}
|
|
83
|
+
m = data.get("marks") or {}
|
|
84
|
+
hc = data.get("headcount") or {}
|
|
85
|
+
mp = data.get("market_priced") or {}
|
|
86
|
+
all_debt = m.get("all_debt_lines_latest") or {}
|
|
87
|
+
row = TensorustContractorCreditData(
|
|
88
|
+
operating_company=op.get("name") or query.contractor,
|
|
89
|
+
uei=op.get("uei"),
|
|
90
|
+
state=op.get("state"),
|
|
91
|
+
borrower=data.get("borrower"),
|
|
92
|
+
sam_parent=op.get("sam_parent_reported"),
|
|
93
|
+
federal_obligations_fy2025_to_date=fed.get("obligations_fy2025_to_date"),
|
|
94
|
+
dod_fy2024_2026=fed.get("dod_fy2024_2026"),
|
|
95
|
+
top_agency=fed.get("top_agency"),
|
|
96
|
+
marks_as_of=m.get("as_of"),
|
|
97
|
+
lowest_mark=m.get("lowest"),
|
|
98
|
+
highest_mark=m.get("highest"),
|
|
99
|
+
sep30_lowest_mark=m.get("q3_lowest"),
|
|
100
|
+
all_debt_mark=all_debt.get("mark"),
|
|
101
|
+
bdc_par=m.get("bdc_par"),
|
|
102
|
+
headcount_change_pct=hc.get("change_pct"),
|
|
103
|
+
signals=data.get("signals") or [],
|
|
104
|
+
market_priced=mp.get("kind"),
|
|
105
|
+
read=data.get("read"),
|
|
106
|
+
)
|
|
107
|
+
meta: dict[str, Any] = {
|
|
108
|
+
k: data[k]
|
|
109
|
+
for k in (
|
|
110
|
+
"federal",
|
|
111
|
+
"marks",
|
|
112
|
+
"headcount",
|
|
113
|
+
"footprint",
|
|
114
|
+
"subcontracts_fy2025_to_date",
|
|
115
|
+
"market_priced",
|
|
116
|
+
"agencies",
|
|
117
|
+
"sample_agencies",
|
|
118
|
+
"lenders",
|
|
119
|
+
"q3_pending_holders",
|
|
120
|
+
"recipients",
|
|
121
|
+
"mark_history",
|
|
122
|
+
"source",
|
|
123
|
+
)
|
|
124
|
+
if data.get(k) is not None
|
|
125
|
+
}
|
|
126
|
+
note = access_note(data)
|
|
127
|
+
if note:
|
|
128
|
+
meta["access"] = note
|
|
129
|
+
return AnnotatedResult(result=[row], metadata=meta)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# ---- contractor_watch -----------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class TensorustContractorWatchQueryParams(QueryParams):
|
|
136
|
+
"""Stressed federal contractors query."""
|
|
137
|
+
|
|
138
|
+
sort: Literal["federal", "mark", "q3"] = Field(
|
|
139
|
+
default="federal",
|
|
140
|
+
description="federal: largest federal exposure first; mark: lowest mark first; q3: biggest Sep 30 cut first.",
|
|
141
|
+
)
|
|
142
|
+
private_only: bool = Field(default=False, description="Drop names with listed equity or a rated syndicated loan.")
|
|
143
|
+
agency: str | None = Field(default=None, description="Only contractors paid by this agency (substring).")
|
|
144
|
+
state: str | None = Field(default=None, description="Two-letter recipient state.")
|
|
145
|
+
signal: str | None = Field(
|
|
146
|
+
default=None,
|
|
147
|
+
description="One signal instead of the stress default, e.g. marked_below_80, lenders_10pt_apart, cut_5pt_q3.",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class TensorustContractorWatchData(Data):
|
|
152
|
+
"""A federal contractor whose BDC lenders are marking its loan down."""
|
|
153
|
+
|
|
154
|
+
operating_company: str = Field(description="Federal award recipient.")
|
|
155
|
+
borrower: str | None = Field(default=None, description="Borrower name in BDC filings.")
|
|
156
|
+
uei: str | None = Field(default=None, description="SAM UEI.")
|
|
157
|
+
state: str | None = Field(default=None, description="Recipient state.")
|
|
158
|
+
federal_exposure: float | None = Field(
|
|
159
|
+
default=None, description="Larger of FY2025-to-date and DoD FY2024-26 obligations, USD."
|
|
160
|
+
)
|
|
161
|
+
top_agency: str | None = Field(default=None, description="Agency with the most obligations.")
|
|
162
|
+
latest_mark: float | None = Field(default=None, description="Latest BDC mark. " + MARK)
|
|
163
|
+
q3_change_points: float | None = Field(default=None, description="Worst Jun 30 to Sep 30 move, points.")
|
|
164
|
+
managers: int | None = Field(default=None, description="BDC managers on the loan.")
|
|
165
|
+
bdc_par: float | None = Field(default=None, description="BDC first-lien par, USD.")
|
|
166
|
+
headcount_change_pct: float | None = Field(default=None, description="Form 5500 participants, 2024 to 2025, %.")
|
|
167
|
+
signals: list[str] = Field(default_factory=list, description="Stress signals.")
|
|
168
|
+
market_priced: str | None = Field(default=None, description="public or bsl when already market priced.")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class TensorustContractorWatchFetcher(Fetcher[TensorustContractorWatchQueryParams, list[TensorustContractorWatchData]]):
|
|
172
|
+
"""contractor_credit_watch. Free: top 10; with a key: every name."""
|
|
173
|
+
|
|
174
|
+
require_credentials = False
|
|
175
|
+
|
|
176
|
+
@staticmethod
|
|
177
|
+
def transform_query(params: dict[str, Any]) -> TensorustContractorWatchQueryParams:
|
|
178
|
+
return TensorustContractorWatchQueryParams(**params)
|
|
179
|
+
|
|
180
|
+
@staticmethod
|
|
181
|
+
def extract_data(
|
|
182
|
+
query: TensorustContractorWatchQueryParams,
|
|
183
|
+
credentials: dict[str, str] | None,
|
|
184
|
+
**kwargs: Any,
|
|
185
|
+
) -> dict[str, Any]:
|
|
186
|
+
args: dict[str, Any] = {
|
|
187
|
+
"sort": query.sort,
|
|
188
|
+
"agency": query.agency,
|
|
189
|
+
"state": query.state,
|
|
190
|
+
"signal": query.signal,
|
|
191
|
+
}
|
|
192
|
+
if query.private_only:
|
|
193
|
+
args["private_only"] = True
|
|
194
|
+
return call_tool("contractor_credit_watch", args, credentials)
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def transform_data(
|
|
198
|
+
query: TensorustContractorWatchQueryParams, data: dict[str, Any], **kwargs: Any
|
|
199
|
+
) -> AnnotatedResult[list[TensorustContractorWatchData]]:
|
|
200
|
+
fields = set(TensorustContractorWatchData.model_fields)
|
|
201
|
+
rows = []
|
|
202
|
+
for x in data.get("items") or []:
|
|
203
|
+
known = {k: v for k, v in x.items() if k in fields}
|
|
204
|
+
known["signals"] = x.get("signals") or []
|
|
205
|
+
extra = {k: v for k, v in x.items() if k not in fields and k != "key"}
|
|
206
|
+
rows.append(TensorustContractorWatchData(**known, **extra))
|
|
207
|
+
meta: dict[str, Any] = {
|
|
208
|
+
k: data[k] for k in ("marks_as_of", "q3_filers", "q3_last_filed", "count", "federal_exposure") if k in data
|
|
209
|
+
}
|
|
210
|
+
note = access_note(data)
|
|
211
|
+
if note:
|
|
212
|
+
meta["access"] = note
|
|
213
|
+
return AnnotatedResult(result=rows, metadata=meta)
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Private credit: what BDC lenders mark a company's first-lien loan at.
|
|
2
|
+
|
|
3
|
+
Source: SEC Business Development Company data sets (XBRL schedules of
|
|
4
|
+
investments), first-lien term loans, fully funded, cost 85-110% of par.
|
|
5
|
+
Marks are fractions of par (0.95 = 95% of par).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from openbb_core.provider.abstract.annotated_result import AnnotatedResult
|
|
13
|
+
from openbb_core.provider.abstract.data import Data
|
|
14
|
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
|
15
|
+
from openbb_core.provider.abstract.query_params import QueryParams
|
|
16
|
+
from pydantic import Field
|
|
17
|
+
|
|
18
|
+
from openbb_tensorust.client import access_note, call_tool, not_found
|
|
19
|
+
|
|
20
|
+
MARK = "Fair value as a fraction of par (0.95 = 95% of par)."
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _meta(data: dict[str, Any], *keys: str) -> dict[str, Any]:
|
|
24
|
+
out = {k: data[k] for k in keys if data.get(k) is not None}
|
|
25
|
+
note = access_note(data)
|
|
26
|
+
if note:
|
|
27
|
+
out["access"] = note
|
|
28
|
+
return out
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---- bdc_loan_marks -------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TensorustBdcLoanMarksQueryParams(QueryParams):
|
|
35
|
+
"""BDC loan marks query."""
|
|
36
|
+
|
|
37
|
+
company: str = Field(
|
|
38
|
+
description="Borrower name as BDC schedules of investments spell it. A prefix works ('Pluralsight')."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TensorustBdcLoanMarksData(Data):
|
|
43
|
+
"""One BDC fund's mark on a company's first-lien term loan."""
|
|
44
|
+
|
|
45
|
+
company: str = Field(description="Borrower name.")
|
|
46
|
+
as_of: str | None = Field(default=None, description="Quarter end of the marks.")
|
|
47
|
+
fund: str = Field(description="BDC fund holding the loan.")
|
|
48
|
+
manager: str | None = Field(default=None, description="Fund manager.")
|
|
49
|
+
mark: float | None = Field(default=None, description=MARK)
|
|
50
|
+
prior_mark: float | None = Field(default=None, description="Prior-quarter mark (key only). " + MARK)
|
|
51
|
+
sep30_mark: float | None = Field(default=None, description="Sep 30 2026 mark if this fund has filed it (key only).")
|
|
52
|
+
sep30_filed: str | None = Field(default=None, description="Date the Sep 30 mark was filed (key only).")
|
|
53
|
+
par: float | None = Field(default=None, description="Par held by this fund, USD (key only).")
|
|
54
|
+
filing: str | None = Field(default=None, description="SEC filing link (key only).")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TensorustBdcLoanMarksFetcher(Fetcher[TensorustBdcLoanMarksQueryParams, list[TensorustBdcLoanMarksData]]):
|
|
58
|
+
"""lender_marks: every BDC fund's mark on one borrower's first-lien loan."""
|
|
59
|
+
|
|
60
|
+
require_credentials = False
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def transform_query(params: dict[str, Any]) -> TensorustBdcLoanMarksQueryParams:
|
|
64
|
+
return TensorustBdcLoanMarksQueryParams(**params)
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def extract_data(
|
|
68
|
+
query: TensorustBdcLoanMarksQueryParams,
|
|
69
|
+
credentials: dict[str, str] | None,
|
|
70
|
+
**kwargs: Any,
|
|
71
|
+
) -> dict[str, Any]:
|
|
72
|
+
return call_tool("lender_marks", {"name": query.company}, credentials)
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def transform_data(
|
|
76
|
+
query: TensorustBdcLoanMarksQueryParams, data: dict[str, Any], **kwargs: Any
|
|
77
|
+
) -> AnnotatedResult[list[TensorustBdcLoanMarksData]]:
|
|
78
|
+
if not data.get("found"):
|
|
79
|
+
raise not_found(data, "BDC first-lien term loan")
|
|
80
|
+
company = data.get("company") or data.get("name") or query.company
|
|
81
|
+
as_of = data.get("as_of")
|
|
82
|
+
lenders = data.get("lenders")
|
|
83
|
+
if isinstance(lenders, list): # full record
|
|
84
|
+
rows = [
|
|
85
|
+
TensorustBdcLoanMarksData(
|
|
86
|
+
company=company,
|
|
87
|
+
as_of=as_of,
|
|
88
|
+
fund=x.get("fund"),
|
|
89
|
+
manager=x.get("manager"),
|
|
90
|
+
mark=x.get("mark"),
|
|
91
|
+
prior_mark=x.get("prev_mark"),
|
|
92
|
+
sep30_mark=x.get("q3_mark"),
|
|
93
|
+
sep30_filed=x.get("q3_filed"),
|
|
94
|
+
par=x.get("par"),
|
|
95
|
+
filing=x.get("filing"),
|
|
96
|
+
)
|
|
97
|
+
for x in lenders
|
|
98
|
+
]
|
|
99
|
+
else:
|
|
100
|
+
rows = [
|
|
101
|
+
TensorustBdcLoanMarksData(
|
|
102
|
+
company=company, as_of=as_of, fund=x.get("fund"), manager=x.get("manager"), mark=x.get("mark")
|
|
103
|
+
)
|
|
104
|
+
for x in data.get("sample") or []
|
|
105
|
+
]
|
|
106
|
+
meta = _meta(
|
|
107
|
+
data,
|
|
108
|
+
"funds",
|
|
109
|
+
"managers",
|
|
110
|
+
"loan_par_usd",
|
|
111
|
+
"maturity",
|
|
112
|
+
"lowest_mark",
|
|
113
|
+
"lowest_manager",
|
|
114
|
+
"highest_mark",
|
|
115
|
+
"highest_manager",
|
|
116
|
+
"gap_points",
|
|
117
|
+
"read",
|
|
118
|
+
"q3_so_far",
|
|
119
|
+
"signal",
|
|
120
|
+
"source",
|
|
121
|
+
)
|
|
122
|
+
if "gap_points" not in meta and isinstance(data.get("gap"), (int, float)):
|
|
123
|
+
meta["gap_points"] = round(data["gap"] * 100, 1)
|
|
124
|
+
return AnnotatedResult(result=rows, metadata=meta)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ---- credit_disagreements -------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TensorustCreditDisagreementsQueryParams(QueryParams):
|
|
131
|
+
"""Loans BDC lenders disagree on. No parameters."""
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class TensorustCreditDisagreementsData(Data):
|
|
135
|
+
"""A company whose first-lien loan two or more BDC managers mark 10+ points apart."""
|
|
136
|
+
|
|
137
|
+
company: str = Field(description="Borrower name.")
|
|
138
|
+
gap_points: float | None = Field(default=None, description="Highest minus lowest manager mark, in points of par.")
|
|
139
|
+
lowest_mark: float | None = Field(default=None, description="Lowest manager mark. " + MARK)
|
|
140
|
+
lowest_manager: str | None = Field(default=None, description="Manager with the lowest mark.")
|
|
141
|
+
highest_mark: float | None = Field(default=None, description="Highest manager mark. " + MARK)
|
|
142
|
+
highest_manager: str | None = Field(default=None, description="Manager with the highest mark.")
|
|
143
|
+
loan_par: float | None = Field(default=None, description="BDC par on the loan, USD.")
|
|
144
|
+
funds: int | None = Field(default=None, description="BDC funds holding the loan.")
|
|
145
|
+
managers: int | None = Field(default=None, description="Distinct managers (key only).")
|
|
146
|
+
maturity: str | None = Field(default=None, description="Loan maturity (key only).")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
class TensorustCreditDisagreementsFetcher(
|
|
150
|
+
Fetcher[TensorustCreditDisagreementsQueryParams, list[TensorustCreditDisagreementsData]]
|
|
151
|
+
):
|
|
152
|
+
"""credit_disagreements: widest gaps first. Free: top 10; with a key: all of them."""
|
|
153
|
+
|
|
154
|
+
require_credentials = False
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def transform_query(params: dict[str, Any]) -> TensorustCreditDisagreementsQueryParams:
|
|
158
|
+
return TensorustCreditDisagreementsQueryParams(**params)
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def extract_data(
|
|
162
|
+
query: TensorustCreditDisagreementsQueryParams,
|
|
163
|
+
credentials: dict[str, str] | None,
|
|
164
|
+
**kwargs: Any,
|
|
165
|
+
) -> dict[str, Any]:
|
|
166
|
+
return call_tool("credit_disagreements", {}, credentials)
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def transform_data(
|
|
170
|
+
query: TensorustCreditDisagreementsQueryParams, data: dict[str, Any], **kwargs: Any
|
|
171
|
+
) -> AnnotatedResult[list[TensorustCreditDisagreementsData]]:
|
|
172
|
+
rows = []
|
|
173
|
+
for x in data.get("items") or []:
|
|
174
|
+
gap = x.get("gap_points")
|
|
175
|
+
if gap is None and isinstance(x.get("gap"), (int, float)):
|
|
176
|
+
gap = round(x["gap"] * 100, 1)
|
|
177
|
+
rows.append(
|
|
178
|
+
TensorustCreditDisagreementsData(
|
|
179
|
+
company=x.get("company") or x.get("name"),
|
|
180
|
+
gap_points=gap,
|
|
181
|
+
lowest_mark=x.get("lowest_mark", x.get("low")),
|
|
182
|
+
lowest_manager=x.get("lowest_manager", x.get("low_manager")),
|
|
183
|
+
highest_mark=x.get("highest_mark", x.get("high")),
|
|
184
|
+
highest_manager=x.get("highest_manager", x.get("high_manager")),
|
|
185
|
+
loan_par=x.get("loan_par_usd", x.get("par")),
|
|
186
|
+
funds=x.get("funds"),
|
|
187
|
+
managers=x.get("managers"),
|
|
188
|
+
maturity=x.get("maturity"),
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
return AnnotatedResult(
|
|
192
|
+
result=rows,
|
|
193
|
+
metadata=_meta(data, "as_of", "companies_with_2plus_managers", "disagreements_10pt", "source"),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ---- credit_q3_cuts -------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class TensorustCreditQ3CutsQueryParams(QueryParams):
|
|
201
|
+
"""Q3 2026 BDC markdowns. No parameters."""
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class TensorustCreditQ3CutsData(Data):
|
|
205
|
+
"""A fund that cut its mark on a loan by 5+ points from Jun 30 to Sep 30 2026."""
|
|
206
|
+
|
|
207
|
+
company: str = Field(description="Borrower name.")
|
|
208
|
+
fund: str = Field(description="BDC fund that cut its mark.")
|
|
209
|
+
manager: str | None = Field(default=None, description="Fund manager (key only).")
|
|
210
|
+
jun30_mark: float | None = Field(default=None, description="Jun 30 2026 mark. " + MARK)
|
|
211
|
+
sep30_mark: float | None = Field(default=None, description="Sep 30 2026 mark. " + MARK)
|
|
212
|
+
change_points: float | None = Field(default=None, description="Sep 30 minus Jun 30, points of par.")
|
|
213
|
+
par: float | None = Field(default=None, description="Par held by this fund, USD.")
|
|
214
|
+
filed: str | None = Field(default=None, description="Date the Sep 30 mark was filed (key only).")
|
|
215
|
+
holders_not_filed_yet_above: int | None = Field(
|
|
216
|
+
default=None,
|
|
217
|
+
description="Other holders that have not filed Sep 30 yet and still carry the loan 5+ points higher.",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class TensorustCreditQ3CutsFetcher(Fetcher[TensorustCreditQ3CutsQueryParams, list[TensorustCreditQ3CutsData]]):
|
|
222
|
+
"""credit_q3_cuts: biggest cuts first. Free: top 10; with a key: all, with each pending holder."""
|
|
223
|
+
|
|
224
|
+
require_credentials = False
|
|
225
|
+
|
|
226
|
+
@staticmethod
|
|
227
|
+
def transform_query(params: dict[str, Any]) -> TensorustCreditQ3CutsQueryParams:
|
|
228
|
+
return TensorustCreditQ3CutsQueryParams(**params)
|
|
229
|
+
|
|
230
|
+
@staticmethod
|
|
231
|
+
def extract_data(
|
|
232
|
+
query: TensorustCreditQ3CutsQueryParams,
|
|
233
|
+
credentials: dict[str, str] | None,
|
|
234
|
+
**kwargs: Any,
|
|
235
|
+
) -> dict[str, Any]:
|
|
236
|
+
return call_tool("credit_q3_cuts", {}, credentials)
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def transform_data(
|
|
240
|
+
query: TensorustCreditQ3CutsQueryParams, data: dict[str, Any], **kwargs: Any
|
|
241
|
+
) -> AnnotatedResult[list[TensorustCreditQ3CutsData]]:
|
|
242
|
+
rows = []
|
|
243
|
+
for x in data.get("items") or []:
|
|
244
|
+
pending = x.get("not_filed_yet_above")
|
|
245
|
+
count = len(pending) if isinstance(pending, list) else x.get("holders_not_filed_yet_above")
|
|
246
|
+
extra: dict[str, Any] = {"pending_holders": pending} if isinstance(pending, list) else {}
|
|
247
|
+
rows.append(
|
|
248
|
+
TensorustCreditQ3CutsData(
|
|
249
|
+
company=x.get("company"),
|
|
250
|
+
fund=x.get("fund"),
|
|
251
|
+
manager=x.get("manager"),
|
|
252
|
+
jun30_mark=x.get("jun30"),
|
|
253
|
+
sep30_mark=x.get("sep30"),
|
|
254
|
+
change_points=x.get("change_points"),
|
|
255
|
+
par=x.get("par"),
|
|
256
|
+
filed=x.get("filed"),
|
|
257
|
+
holders_not_filed_yet_above=count,
|
|
258
|
+
**extra,
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
return AnnotatedResult(result=rows, metadata=_meta(data, "as_of", "funds_filed", "last_filing", "cuts_5pt"))
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Research Exposure Ledger: federal awards whose papers name vendors on banned-vendor lists.
|
|
2
|
+
|
|
3
|
+
Lists: DoD 1260H, Section 889 (FAR 52.204-25), FCC Covered List, DHS UFLPA
|
|
4
|
+
Entity List, BIOSECURE. Evidence: Europe PMC, OpenAlex, Crossref, open-access
|
|
5
|
+
PDFs, ENA and dbGaP; dollars from USAspending and NIH RePORTER.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from openbb_core.provider.abstract.annotated_result import AnnotatedResult
|
|
13
|
+
from openbb_core.provider.abstract.data import Data
|
|
14
|
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
|
15
|
+
from openbb_core.provider.abstract.query_params import QueryParams
|
|
16
|
+
from pydantic import Field
|
|
17
|
+
|
|
18
|
+
from openbb_tensorust.client import access_note, call_tool, not_found
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TensorustExposureInstitutionQueryParams(QueryParams):
|
|
22
|
+
"""Exposure query for one institution."""
|
|
23
|
+
|
|
24
|
+
institution: str = Field(description="Federal award recipient name or UEI.")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TensorustExposureInstitutionData(Data):
|
|
28
|
+
"""One federal award whose 2025-26 papers name a listed vendor."""
|
|
29
|
+
|
|
30
|
+
award: str = Field(description="Award id (FAIN).")
|
|
31
|
+
recipient: str | None = Field(default=None, description="Recipient name.")
|
|
32
|
+
agency: str | None = Field(default=None, description="Awarding agency.")
|
|
33
|
+
title: str | None = Field(default=None, description="Award title.")
|
|
34
|
+
fy25_obligations: float | None = Field(default=None, description="Whole-award FY2025 obligations, USD.")
|
|
35
|
+
fy26_amount: float | None = Field(default=None, description="FY2026 amount, USD.")
|
|
36
|
+
project_end: str | None = Field(default=None, description="Project end date.")
|
|
37
|
+
vendors: list[str] = Field(default_factory=list, description="Listed vendors named in the evidence.")
|
|
38
|
+
lists: list[str] = Field(default_factory=list, description="Lists the vendors are on.")
|
|
39
|
+
rules: dict[str, str] = Field(default_factory=dict, description="Rule id to status for this award.")
|
|
40
|
+
attribution: str | None = Field(default=None, description="broad, narrow or likely_paid.")
|
|
41
|
+
in_force_or_no_grace: bool | None = Field(
|
|
42
|
+
default=None, description="A consequence binds now or has no grace period."
|
|
43
|
+
)
|
|
44
|
+
papers: int | None = Field(default=None, description="Citing 2025-26 papers.")
|
|
45
|
+
latest_paper: str | None = Field(default=None, description="Date of the latest citing paper.")
|
|
46
|
+
evidence: str | None = Field(default=None, description="Sentence naming the vendor (key only).")
|
|
47
|
+
evidence_paper: str | None = Field(default=None, description="Link to the paper (key only).")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TensorustExposureInstitutionFetcher(
|
|
51
|
+
Fetcher[TensorustExposureInstitutionQueryParams, list[TensorustExposureInstitutionData]]
|
|
52
|
+
):
|
|
53
|
+
"""exposure_institution. Free: totals and the 5 largest awards; with a key: every award with evidence."""
|
|
54
|
+
|
|
55
|
+
require_credentials = False
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def transform_query(params: dict[str, Any]) -> TensorustExposureInstitutionQueryParams:
|
|
59
|
+
return TensorustExposureInstitutionQueryParams(**params)
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def extract_data(
|
|
63
|
+
query: TensorustExposureInstitutionQueryParams,
|
|
64
|
+
credentials: dict[str, str] | None,
|
|
65
|
+
**kwargs: Any,
|
|
66
|
+
) -> dict[str, Any]:
|
|
67
|
+
return call_tool("exposure_institution", {"name": query.institution}, credentials)
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def transform_data(
|
|
71
|
+
query: TensorustExposureInstitutionQueryParams, data: dict[str, Any], **kwargs: Any
|
|
72
|
+
) -> AnnotatedResult[list[TensorustExposureInstitutionData]]:
|
|
73
|
+
if not data.get("found"):
|
|
74
|
+
raise not_found(data, "award recipient with a listed-vendor paper")
|
|
75
|
+
rows = []
|
|
76
|
+
for a in data.get("items") or []:
|
|
77
|
+
row = {k: v for k, v in a.items() if v is not None}
|
|
78
|
+
row["vendors"] = a.get("vendors") or []
|
|
79
|
+
row["lists"] = a.get("lists") or []
|
|
80
|
+
row["rules"] = a.get("rules") or {}
|
|
81
|
+
rows.append(TensorustExposureInstitutionData(**row))
|
|
82
|
+
meta: dict[str, Any] = {
|
|
83
|
+
k: data[k]
|
|
84
|
+
for k in (
|
|
85
|
+
"institution",
|
|
86
|
+
"uei",
|
|
87
|
+
"state",
|
|
88
|
+
"as_of",
|
|
89
|
+
"awards",
|
|
90
|
+
"fy25",
|
|
91
|
+
"fy26",
|
|
92
|
+
"in_force_awards",
|
|
93
|
+
"in_force_fy25",
|
|
94
|
+
"narrow_awards",
|
|
95
|
+
"narrow_fy25",
|
|
96
|
+
"likely_paid_awards",
|
|
97
|
+
"likely_paid_fy25",
|
|
98
|
+
"by_list",
|
|
99
|
+
"rule_status_counts",
|
|
100
|
+
"ontology_id",
|
|
101
|
+
)
|
|
102
|
+
if k in data
|
|
103
|
+
}
|
|
104
|
+
note = access_note(data)
|
|
105
|
+
if note:
|
|
106
|
+
meta["access"] = note
|
|
107
|
+
return AnnotatedResult(result=rows, metadata=meta)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""No Surprises Act federal IDR: the offer band that wins for a code, payer and state.
|
|
2
|
+
|
|
3
|
+
Source: CMS Federal IDR Public Use File, 2025 Q4, computed by Tensorust LLC
|
|
4
|
+
(altered and aggregated; not CMS data as published).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from openbb_core.provider.abstract.annotated_result import AnnotatedResult
|
|
12
|
+
from openbb_core.provider.abstract.data import Data
|
|
13
|
+
from openbb_core.provider.abstract.fetcher import Fetcher
|
|
14
|
+
from openbb_core.provider.abstract.query_params import QueryParams
|
|
15
|
+
from pydantic import Field
|
|
16
|
+
|
|
17
|
+
from openbb_tensorust.client import access_note, call_tool
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TensorustIdrOfferQueryParams(QueryParams):
|
|
21
|
+
"""IDR offer query."""
|
|
22
|
+
|
|
23
|
+
code: str = Field(description="CPT or HCPCS code, e.g. 99284.")
|
|
24
|
+
payer: str | None = Field(
|
|
25
|
+
default=None, description="Payer email domain or common name (uhc.com, aetna.com, cigna.com)."
|
|
26
|
+
)
|
|
27
|
+
state: str | None = Field(default=None, description="Two-letter state.")
|
|
28
|
+
entity: str | None = Field(default=None, description="Certified IDR entity (arbitrator), optional.")
|
|
29
|
+
qpa: float | None = Field(
|
|
30
|
+
default=None, description="Your qualifying payment amount in USD, for expected value (key or small cells)."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TensorustIdrOfferData(Data):
|
|
35
|
+
"""The IDR cell that decides the dispute."""
|
|
36
|
+
|
|
37
|
+
code: str = Field(description="Service code.")
|
|
38
|
+
payer: str | None = Field(default=None, description="Payer domain matched.")
|
|
39
|
+
state: str | None = Field(default=None, description="State matched.")
|
|
40
|
+
level: str | None = Field(default=None, description="Which cell matched (code+payer+state, or a wider one).")
|
|
41
|
+
determinations: int | None = Field(default=None, description="Determinations in the cell.")
|
|
42
|
+
provider_win_rate_pct: float | None = Field(default=None, description="Share of line items providers won, %.")
|
|
43
|
+
offer_band_low_multiple: float | None = Field(
|
|
44
|
+
default=None, description="Median winning provider offer, multiple of QPA."
|
|
45
|
+
)
|
|
46
|
+
offer_band_high_multiple: float | None = Field(
|
|
47
|
+
default=None, description="75th percentile winning provider offer, multiple of QPA."
|
|
48
|
+
)
|
|
49
|
+
winning_p25_multiple: float | None = Field(
|
|
50
|
+
default=None, description="25th percentile winning offer, multiple of QPA (full)."
|
|
51
|
+
)
|
|
52
|
+
plan_median_offer_multiple: float | None = Field(
|
|
53
|
+
default=None, description="Median plan offer, multiple of QPA (full)."
|
|
54
|
+
)
|
|
55
|
+
losing_provider_median_offer_multiple: float | None = Field(
|
|
56
|
+
default=None, description="Median losing provider offer (full)."
|
|
57
|
+
)
|
|
58
|
+
median_days_to_determination: float | None = Field(
|
|
59
|
+
default=None, description="Median days to a determination (full)."
|
|
60
|
+
)
|
|
61
|
+
access: str | None = Field(default=None, description="preview, free (small cell) or key.")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TensorustIdrOfferFetcher(Fetcher[TensorustIdrOfferQueryParams, list[TensorustIdrOfferData]]):
|
|
65
|
+
"""idr_offer. Free: win rate and offer band; with a key (or a cell under 50): quartiles, arbitrators, EV."""
|
|
66
|
+
|
|
67
|
+
require_credentials = False
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def transform_query(params: dict[str, Any]) -> TensorustIdrOfferQueryParams:
|
|
71
|
+
return TensorustIdrOfferQueryParams(**params)
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def extract_data(
|
|
75
|
+
query: TensorustIdrOfferQueryParams,
|
|
76
|
+
credentials: dict[str, str] | None,
|
|
77
|
+
**kwargs: Any,
|
|
78
|
+
) -> dict[str, Any]:
|
|
79
|
+
return call_tool(
|
|
80
|
+
"idr_offer",
|
|
81
|
+
{"code": query.code, "payer": query.payer, "state": query.state, "entity": query.entity, "qpa": query.qpa},
|
|
82
|
+
credentials,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def transform_data(
|
|
87
|
+
query: TensorustIdrOfferQueryParams, data: dict[str, Any], **kwargs: Any
|
|
88
|
+
) -> AnnotatedResult[list[TensorustIdrOfferData]]:
|
|
89
|
+
q = data.get("query") or {}
|
|
90
|
+
matched = data.get("matched") or {}
|
|
91
|
+
cell = data.get("cell") or {}
|
|
92
|
+
band = data.get("recommended_offer_band") or {}
|
|
93
|
+
win = (cell.get("winning_multiple_of_qpa") or {}) if cell else {}
|
|
94
|
+
preview = data.get("preview") is True
|
|
95
|
+
row = TensorustIdrOfferData(
|
|
96
|
+
code=q.get("code") or query.code.upper(),
|
|
97
|
+
payer=q.get("payer"),
|
|
98
|
+
state=q.get("state"),
|
|
99
|
+
level=matched.get("level"),
|
|
100
|
+
determinations=matched.get("determinations"),
|
|
101
|
+
provider_win_rate_pct=cell.get("provider_win_rate_pct", data.get("provider_win_rate_pct")),
|
|
102
|
+
offer_band_low_multiple=band.get("low_multiple"),
|
|
103
|
+
offer_band_high_multiple=band.get("high_multiple"),
|
|
104
|
+
winning_p25_multiple=win.get("p25"),
|
|
105
|
+
plan_median_offer_multiple=cell.get("plan_median_offer_multiple"),
|
|
106
|
+
losing_provider_median_offer_multiple=cell.get("losing_provider_median_offer_multiple"),
|
|
107
|
+
median_days_to_determination=cell.get("median_days_to_determination"),
|
|
108
|
+
access="preview" if preview else data.get("access"),
|
|
109
|
+
)
|
|
110
|
+
meta = {
|
|
111
|
+
k: data[k]
|
|
112
|
+
for k in (
|
|
113
|
+
"cell",
|
|
114
|
+
"at_your_qpa",
|
|
115
|
+
"code_dollars_q4_2025",
|
|
116
|
+
"arbitrators_in_cell",
|
|
117
|
+
"chosen_arbitrator",
|
|
118
|
+
"entity_pick",
|
|
119
|
+
"flags",
|
|
120
|
+
"pricing",
|
|
121
|
+
"source",
|
|
122
|
+
"vintage",
|
|
123
|
+
)
|
|
124
|
+
if data.get(k) is not None
|
|
125
|
+
}
|
|
126
|
+
if preview:
|
|
127
|
+
meta["access"] = access_note(data)
|
|
128
|
+
return AnnotatedResult(result=[row], metadata=meta)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""obb.tensorust: commands served by the Tensorust provider."""
|
|
2
|
+
|
|
3
|
+
from openbb_core.app.model.command_context import CommandContext
|
|
4
|
+
from openbb_core.app.model.example import APIEx
|
|
5
|
+
from openbb_core.app.model.obbject import OBBject
|
|
6
|
+
from openbb_core.app.provider_interface import (
|
|
7
|
+
ExtraParams,
|
|
8
|
+
ProviderChoices,
|
|
9
|
+
StandardParams,
|
|
10
|
+
)
|
|
11
|
+
from openbb_core.app.query import Query
|
|
12
|
+
from openbb_core.app.router import Router
|
|
13
|
+
|
|
14
|
+
router = Router(prefix="", description="Tensorust: private credit marks, contractor credit, exposure and IDR data.")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@router.command(
|
|
18
|
+
model="TensorustBdcLoanMarks",
|
|
19
|
+
examples=[APIEx(parameters={"company": "Pluralsight", "provider": "tensorust"})],
|
|
20
|
+
)
|
|
21
|
+
async def bdc_loan_marks(
|
|
22
|
+
cc: CommandContext,
|
|
23
|
+
provider_choices: ProviderChoices,
|
|
24
|
+
standard_params: StandardParams,
|
|
25
|
+
extra_params: ExtraParams,
|
|
26
|
+
) -> OBBject:
|
|
27
|
+
"""What each BDC (private credit) fund marks one company's first-lien term loan at, as a fraction of par.
|
|
28
|
+
|
|
29
|
+
The results metadata carries the lowest and highest manager mark, the gap and Q3 2026 marks filed so far.
|
|
30
|
+
"""
|
|
31
|
+
return await OBBject.from_query(Query(**locals()))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@router.command(
|
|
35
|
+
model="TensorustCreditDisagreements",
|
|
36
|
+
examples=[APIEx(parameters={"provider": "tensorust"})],
|
|
37
|
+
)
|
|
38
|
+
async def credit_disagreements(
|
|
39
|
+
cc: CommandContext,
|
|
40
|
+
provider_choices: ProviderChoices,
|
|
41
|
+
standard_params: StandardParams,
|
|
42
|
+
extra_params: ExtraParams,
|
|
43
|
+
) -> OBBject:
|
|
44
|
+
"""Companies whose first-lien loan two or more BDC managers mark 10+ points apart, widest gap first."""
|
|
45
|
+
return await OBBject.from_query(Query(**locals()))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@router.command(
|
|
49
|
+
model="TensorustCreditQ3Cuts",
|
|
50
|
+
examples=[APIEx(parameters={"provider": "tensorust"})],
|
|
51
|
+
)
|
|
52
|
+
async def credit_q3_cuts(
|
|
53
|
+
cc: CommandContext,
|
|
54
|
+
provider_choices: ProviderChoices,
|
|
55
|
+
standard_params: StandardParams,
|
|
56
|
+
extra_params: ExtraParams,
|
|
57
|
+
) -> OBBject:
|
|
58
|
+
"""Q3 2026 BDC markdowns of 5+ points as funds file their 10-Qs, with holders that have not filed yet."""
|
|
59
|
+
return await OBBject.from_query(Query(**locals()))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@router.command(
|
|
63
|
+
model="TensorustContractorCredit",
|
|
64
|
+
examples=[APIEx(parameters={"contractor": "Peraton", "provider": "tensorust"})],
|
|
65
|
+
)
|
|
66
|
+
async def contractor_credit(
|
|
67
|
+
cc: CommandContext,
|
|
68
|
+
provider_choices: ProviderChoices,
|
|
69
|
+
standard_params: StandardParams,
|
|
70
|
+
extra_params: ExtraParams,
|
|
71
|
+
) -> OBBject:
|
|
72
|
+
"""One federal contractor: obligations, what its BDC lenders mark its loan at, headcount trend and signals."""
|
|
73
|
+
return await OBBject.from_query(Query(**locals()))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@router.command(
|
|
77
|
+
model="TensorustContractorWatch",
|
|
78
|
+
examples=[APIEx(parameters={"sort": "q3", "provider": "tensorust"})],
|
|
79
|
+
)
|
|
80
|
+
async def contractor_watch(
|
|
81
|
+
cc: CommandContext,
|
|
82
|
+
provider_choices: ProviderChoices,
|
|
83
|
+
standard_params: StandardParams,
|
|
84
|
+
extra_params: ExtraParams,
|
|
85
|
+
) -> OBBject:
|
|
86
|
+
"""Federal contractors whose BDC lenders are marking their loans down."""
|
|
87
|
+
return await OBBject.from_query(Query(**locals()))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@router.command(
|
|
91
|
+
model="TensorustExposureInstitution",
|
|
92
|
+
examples=[APIEx(parameters={"institution": "Harvard", "provider": "tensorust"})],
|
|
93
|
+
)
|
|
94
|
+
async def exposure_institution(
|
|
95
|
+
cc: CommandContext,
|
|
96
|
+
provider_choices: ProviderChoices,
|
|
97
|
+
standard_params: StandardParams,
|
|
98
|
+
extra_params: ExtraParams,
|
|
99
|
+
) -> OBBject:
|
|
100
|
+
"""Federal awards of one institution whose 2025-26 papers name a vendor on a banned-vendor list."""
|
|
101
|
+
return await OBBject.from_query(Query(**locals()))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@router.command(
|
|
105
|
+
model="TensorustIdrOffer",
|
|
106
|
+
examples=[APIEx(parameters={"code": "99284", "payer": "uhc.com", "state": "TX", "provider": "tensorust"})],
|
|
107
|
+
)
|
|
108
|
+
async def idr_offer(
|
|
109
|
+
cc: CommandContext,
|
|
110
|
+
provider_choices: ProviderChoices,
|
|
111
|
+
standard_params: StandardParams,
|
|
112
|
+
extra_params: ExtraParams,
|
|
113
|
+
) -> OBBject:
|
|
114
|
+
"""No Surprises Act federal IDR: provider win rate and the winning offer band for a code, payer and state."""
|
|
115
|
+
return await OBBject.from_query(Query(**locals()))
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: openbb-tensorust
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: OpenBB extension for Tensorust data: BDC private credit loan marks, federal contractor credit, banned-vendor exposure in federal awards and No Surprises Act IDR offer bands.
|
|
5
|
+
Project-URL: Homepage, https://tensorust.com
|
|
6
|
+
Project-URL: Source, https://github.com/Mattbusel/openbb-tensorust
|
|
7
|
+
Project-URL: Documentation, https://github.com/Mattbusel/openbb-tensorust/blob/main/docs/index.md
|
|
8
|
+
Project-URL: Changelog, https://github.com/Mattbusel/openbb-tensorust/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Workspace backend, https://shop.tensorust.com/api/openbb
|
|
10
|
+
Author: Matthew Busel
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: bdc,federal-contracts,finance,loan-marks,no-surprises-act,openbb,private-credit,sanctions
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: openbb-core<3,>=1.6
|
|
25
|
+
Requires-Dist: requests>=2.31
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# openbb-tensorust
|
|
29
|
+
|
|
30
|
+
An [OpenBB Platform](https://github.com/OpenBB-finance/OpenBB) extension for
|
|
31
|
+
[Tensorust](https://tensorust.com) data, built from US public records.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install openbb openbb-tensorust
|
|
35
|
+
openbb-build # once, so obb picks up the new commands
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from openbb import obb
|
|
40
|
+
|
|
41
|
+
obb.tensorust.credit_disagreements().to_df() # loans BDC lenders mark 10+ points apart
|
|
42
|
+
obb.tensorust.bdc_loan_marks(company="Pluralsight").to_df()
|
|
43
|
+
obb.tensorust.credit_q3_cuts().to_df() # Q3 2026 markdowns as BDCs file
|
|
44
|
+
obb.tensorust.contractor_credit(contractor="Peraton")
|
|
45
|
+
obb.tensorust.contractor_watch(sort="q3").to_df()
|
|
46
|
+
obb.tensorust.exposure_institution(institution="Harvard").to_df()
|
|
47
|
+
obb.tensorust.idr_offer(code="99284", payer="uhc.com", state="TX")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Command | Answers |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `bdc_loan_marks` | What each BDC fund marks one company's first-lien loan at (SEC schedules of investments) |
|
|
53
|
+
| `credit_disagreements` | Loans two or more BDC managers mark 10+ points apart |
|
|
54
|
+
| `credit_q3_cuts` | Fund-level cuts of 5+ points from Jun 30 to Sep 30 2026, and who has not filed yet |
|
|
55
|
+
| `contractor_credit` | A federal contractor's obligations, lender marks, headcount trend and stress signals |
|
|
56
|
+
| `contractor_watch` | Federal contractors whose lenders are marking them down |
|
|
57
|
+
| `exposure_institution` | Federal awards whose papers name vendors on the 1260H, Section 889, FCC Covered, UFLPA or BIOSECURE lists |
|
|
58
|
+
| `idr_offer` | No Surprises Act federal IDR: provider win rate and the winning offer band by code, payer and state |
|
|
59
|
+
|
|
60
|
+
No key is needed: every command answers a free preview (top 10 rows, or a summary).
|
|
61
|
+
Set `obb.user.credentials.tensorust_api_key = "tdk_..."` (or `TENSORUST_API_KEY`) for full
|
|
62
|
+
records. The same data loads in OpenBB Workspace as a custom backend:
|
|
63
|
+
`https://shop.tensorust.com/api/openbb`.
|
|
64
|
+
|
|
65
|
+
Docs: https://github.com/Mattbusel/openbb-tensorust/blob/main/docs/index.md
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
openbb_tensorust/__init__.py,sha256=OlgfZYk3nSHy6plIWR1sIr0NPcHswxuWddY-pd3Duhk,2242
|
|
2
|
+
openbb_tensorust/client.py,sha256=VDEk61OgFEN62NuUqwEX3kguX585RSNxZFuo_EOIT9Q,4427
|
|
3
|
+
openbb_tensorust/router.py,sha256=UOoAZO7g_B3YunyudExfPLvIOOqeRWaVt3UluhyZSeE,3961
|
|
4
|
+
openbb_tensorust/models/__init__.py,sha256=hCiRAV6sAyhJXHnLoUVl3LJNHXyDbSZRhdLNf3bxMJM,33
|
|
5
|
+
openbb_tensorust/models/contractor.py,sha256=qpDyPrVXC_OoLcL4zAf4kd2ImkV9DamXbrq-5cSxiyU,9800
|
|
6
|
+
openbb_tensorust/models/credit.py,sha256=rT0Hu2nIV7-JhUqDN_8CfDi_FmatvKXKAWCN6Rth_pQ,11113
|
|
7
|
+
openbb_tensorust/models/exposure.py,sha256=2IXylKGibxnL3jVbVoBdSjma4IVfAvmURfEODMGG7E0,4658
|
|
8
|
+
openbb_tensorust/models/idr.py,sha256=0lYLfswK9HQ51bxquRoviyHYo7JtqNesnSWGdAr7Atc,5506
|
|
9
|
+
openbb_tensorust-0.1.0.dist-info/METADATA,sha256=8TeHWBOD6Vm6Lj4DmKJoMRtIlVZ-r-L5d7bocyUXRZQ,3321
|
|
10
|
+
openbb_tensorust-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
11
|
+
openbb_tensorust-0.1.0.dist-info/entry_points.txt,sha256=XqO7mOh_vDsRGmJheNHYiM6QmE0vwkgXCB9feUsydZ8,144
|
|
12
|
+
openbb_tensorust-0.1.0.dist-info/licenses/LICENSE,sha256=TpCqwl9Iq4EFUnYGbR2dGUSmF5tDy-hCISSPJasaP10,1070
|
|
13
|
+
openbb_tensorust-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matthew Busel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|