openbb-tensorust 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. openbb_tensorust-0.1.0/.gitignore +9 -0
  2. openbb_tensorust-0.1.0/CHANGELOG.md +14 -0
  3. openbb_tensorust-0.1.0/LICENSE +21 -0
  4. openbb_tensorust-0.1.0/PKG-INFO +65 -0
  5. openbb_tensorust-0.1.0/docs/index.md +91 -0
  6. openbb_tensorust-0.1.0/examples/contractor_and_compliance.py +28 -0
  7. openbb_tensorust-0.1.0/examples/private_credit_screen.py +21 -0
  8. openbb_tensorust-0.1.0/openbb_tensorust/__init__.py +52 -0
  9. openbb_tensorust-0.1.0/openbb_tensorust/client.py +110 -0
  10. openbb_tensorust-0.1.0/openbb_tensorust/models/__init__.py +1 -0
  11. openbb_tensorust-0.1.0/openbb_tensorust/models/contractor.py +213 -0
  12. openbb_tensorust-0.1.0/openbb_tensorust/models/credit.py +261 -0
  13. openbb_tensorust-0.1.0/openbb_tensorust/models/exposure.py +107 -0
  14. openbb_tensorust-0.1.0/openbb_tensorust/models/idr.py +128 -0
  15. openbb_tensorust-0.1.0/openbb_tensorust/router.py +115 -0
  16. openbb_tensorust-0.1.0/pyproject.toml +96 -0
  17. openbb_tensorust-0.1.0/tests/integration_tests/__init__.py +0 -0
  18. openbb_tensorust-0.1.0/tests/integration_tests/test_live.py +60 -0
  19. openbb_tensorust-0.1.0/tests/unit_tests/__init__.py +0 -0
  20. openbb_tensorust-0.1.0/tests/unit_tests/conftest.py +59 -0
  21. openbb_tensorust-0.1.0/tests/unit_tests/fixtures/contractor_credit_preview.json +104 -0
  22. openbb_tensorust-0.1.0/tests/unit_tests/fixtures/contractor_credit_watch_preview.json +113 -0
  23. openbb_tensorust-0.1.0/tests/unit_tests/fixtures/credit_disagreements_full.json +37 -0
  24. openbb_tensorust-0.1.0/tests/unit_tests/fixtures/credit_disagreements_preview.json +39 -0
  25. openbb_tensorust-0.1.0/tests/unit_tests/fixtures/credit_q3_cuts_full.json +26 -0
  26. openbb_tensorust-0.1.0/tests/unit_tests/fixtures/credit_q3_cuts_preview.json +36 -0
  27. openbb_tensorust-0.1.0/tests/unit_tests/fixtures/exposure_institution_preview.json +136 -0
  28. openbb_tensorust-0.1.0/tests/unit_tests/fixtures/idr_offer_preview.json +32 -0
  29. openbb_tensorust-0.1.0/tests/unit_tests/fixtures/lender_marks_full.json +50 -0
  30. openbb_tensorust-0.1.0/tests/unit_tests/fixtures/lender_marks_missing.json +6 -0
  31. openbb_tensorust-0.1.0/tests/unit_tests/fixtures/lender_marks_preview.json +30 -0
  32. openbb_tensorust-0.1.0/tests/unit_tests/test_extension.py +57 -0
  33. openbb_tensorust-0.1.0/tests/unit_tests/test_fetchers.py +198 -0
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .venv/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+ .mypy_cache/
9
+ .ruff_cache/
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2026-11-15)
4
+
5
+ First release.
6
+
7
+ - OpenBB provider `tensorust` and router `obb.tensorust` with seven commands:
8
+ `bdc_loan_marks`, `credit_disagreements`, `credit_q3_cuts`, `contractor_credit`,
9
+ `contractor_watch`, `exposure_institution` and `idr_offer`.
10
+ - Free previews without a key. `tensorust_api_key` (or `TENSORUST_API_KEY`) returns full records.
11
+ - Summary fields (gap, totals, rule status counts, arbitrator table) are in
12
+ `OBBject.extra["results_metadata"]`.
13
+ - Works with the Python interface, the OpenBB REST API (`openbb-api`) and OpenBB's MCP server.
14
+ - Tested against openbb-core 1.6.9 and 2.0.1.
@@ -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.
@@ -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,91 @@
1
+ # openbb-tensorust
2
+
3
+ OpenBB extension for [Tensorust](https://tensorust.com) data. Seven commands under
4
+ `obb.tensorust`, served by the `tensorust` provider. Every command answers a free preview
5
+ without a key; a Tensorust key returns full records.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install openbb openbb-tensorust
11
+ openbb-build # regenerates the obb package so the new commands appear
12
+ ```
13
+
14
+ Optional key (full records):
15
+
16
+ ```python
17
+ from openbb import obb
18
+
19
+ obb.user.credentials.tensorust_api_key = "tdk_..."
20
+ ```
21
+
22
+ or `export TENSORUST_API_KEY=tdk_...`. Keys: https://shop.tensorust.com/offers/enterprise-api
23
+
24
+ ## Commands
25
+
26
+ | Command | Parameters | Rows | Free preview | With a key |
27
+ |---|---|---|---|---|
28
+ | `bdc_loan_marks` | `company` | one per BDC fund holding the loan | up to 3 funds, mark only | every fund: par, prior-quarter mark, Sep 30 mark, filing link |
29
+ | `credit_disagreements` | none | one per company marked 10+ points apart | top 10 | all |
30
+ | `credit_q3_cuts` | none | one per fund cut of 5+ points, Jun 30 to Sep 30 2026 | top 10 | all, with each holder not filed yet (`pending_holders`) |
31
+ | `contractor_credit` | `contractor` (name, UEI or EIN) | one | profile | adds every lender line, recipients, mark history |
32
+ | `contractor_watch` | `sort`, `private_only`, `agency`, `state`, `signal` | one per stressed contractor | top 10 | all |
33
+ | `exposure_institution` | `institution` (name or UEI) | one per award | 5 largest awards | every award with its evidence sentence and paper link |
34
+ | `idr_offer` | `code`, `payer`, `state`, `entity`, `qpa` | one cell | win rate and offer band | quartiles, every arbitrator's loss rate and 2026 fee, expected value at your QPA (cells under 50 determinations are free in full) |
35
+
36
+ Marks are fractions of par (`0.95` = 95% of par). Summary fields for each answer (the
37
+ lowest and highest mark, totals by list, the arbitrator table, how to get the full record)
38
+ are in `result.extra["results_metadata"]`.
39
+
40
+ Not found is an `EmptyDataError` naming the closest matches (HTTP 204 from `openbb-api`).
41
+
42
+ ## Sources
43
+
44
+ - **Private credit marks:** SEC Business Development Company data sets (XBRL schedules of
45
+ investments), first-lien term loans, fully funded, cost 85-110% of par; Sep 30 2026
46
+ marks from 10-Qs as filed.
47
+ - **Contractor Credit:** the marks above joined to USAspending.gov recipients, SAM parent
48
+ UEI, DOL Form 5500 headcount, FCC and FAA records.
49
+ - **Exposure Ledger:** Federal Register (DoD 1260H), FAR 52.204-25, FCC Covered List, DHS
50
+ UFLPA Entity List, BIOSECURE; evidence from Europe PMC, OpenAlex, Crossref, open-access
51
+ PDFs, ENA and dbGaP; dollars from USAspending and NIH RePORTER.
52
+ - **IDR:** CMS Federal IDR Public Use File 2025 Q4, computed by Tensorust LLC (altered and
53
+ aggregated; not CMS data as published).
54
+
55
+ ## REST API and MCP
56
+
57
+ `openbb-api` serves the commands at `/api/v1/tensorust/<command>?provider=tensorust`,
58
+ and OpenBB's MCP server exposes them as tools like any other OpenBB command.
59
+
60
+ ## OpenBB Workspace
61
+
62
+ The same data is available in OpenBB Workspace without installing anything: add a custom
63
+ backend with the URL
64
+
65
+ ```
66
+ https://shop.tensorust.com/api/openbb
67
+ ```
68
+
69
+ It serves `widgets.json` (9 widgets: loan marks table and summary, disagreements, Q3 cuts,
70
+ contractor profile and watchlist, exposure table and summary, IDR offer band) and
71
+ `apps.json` (two apps: *Tensorust Private Credit* and *Tensorust Compliance and IDR*).
72
+ Clicking a company in the disagreements or Q3 cuts table loads its marks; clicking a
73
+ contractor in the watchlist loads its profile. For full records add the header
74
+ `Authorization: Bearer tdk_...` to the backend connection.
75
+
76
+ ## How it works
77
+
78
+ Each command makes one JSON-RPC `tools/call` to the public Tensorust MCP server
79
+ (`https://shop.tensorust.com/api/mcp`, override with `TENSORUST_MCP_URL`). With a key it
80
+ sends `full=true` and the bearer token. No other network calls, no telemetry.
81
+
82
+ ## Development
83
+
84
+ ```bash
85
+ pip install -e . pytest pytest-asyncio httpx ruff mypy
86
+ pytest tests/unit_tests # no network
87
+ pytest tests/integration_tests # keyless calls to the public endpoint
88
+ ruff format --check . && ruff check . && mypy openbb_tensorust
89
+ ```
90
+
91
+ MIT licensed. Maintainer: Matthew Busel (mattbusel@tensorust.com).
@@ -0,0 +1,28 @@
1
+ """Federal contractors on marked-down loans, an institution's banned-vendor exposure, and an IDR offer band.
2
+
3
+ pip install openbb openbb-tensorust && openbb-build
4
+ python examples/contractor_and_compliance.py
5
+ """
6
+
7
+ from openbb import obb
8
+
9
+ watch = obb.tensorust.contractor_watch(sort="q3", private_only=True).to_df()
10
+ print(
11
+ watch[["operating_company", "state", "federal_exposure", "latest_mark", "q3_change_points"]]
12
+ .head()
13
+ .to_string(index=False)
14
+ )
15
+
16
+ one = obb.tensorust.contractor_credit(contractor=watch["uei"].iloc[0])
17
+ print("\n" + (one.results[0].read or ""))
18
+
19
+ awards = obb.tensorust.exposure_institution(institution="Johns Hopkins")
20
+ meta = awards.extra["results_metadata"]
21
+ print(
22
+ f"\n{meta['institution']}: {meta['awards']} awards, ${meta['fy25']:,.0f} FY25, {meta['in_force_awards']} bind now"
23
+ )
24
+ print(awards.to_df()[["award", "agency", "fy25_obligations", "lists", "attribution"]].to_string(index=False))
25
+
26
+ idr = obb.tensorust.idr_offer(code="99284", payer="uhc.com", state="TX").results[0]
27
+ band = f"{idr.offer_band_low_multiple}x to {idr.offer_band_high_multiple}x QPA"
28
+ print(f"\nCPT 99284 / UHC / TX: providers win {idr.provider_win_rate_pct}%, offer {band}")
@@ -0,0 +1,21 @@
1
+ """Screen BDC loans where lenders disagree, then pull each fund's mark for the widest gaps.
2
+
3
+ pip install openbb openbb-tensorust && openbb-build
4
+ python examples/private_credit_screen.py
5
+ """
6
+
7
+ from openbb import obb
8
+
9
+ gaps = obb.tensorust.credit_disagreements()
10
+ print(gaps.extra["results_metadata"].get("access", "full records"))
11
+ df = gaps.to_df()
12
+ print(df[["company", "gap_points", "lowest_manager", "lowest_mark", "highest_manager", "highest_mark"]].head())
13
+
14
+ for company in df["company"].head(3):
15
+ marks = obb.tensorust.bdc_loan_marks(company=company)
16
+ print(f"\n{company}: {marks.extra['results_metadata'].get('read', '')}")
17
+ print(marks.to_df()[["fund", "manager", "mark"]].to_string(index=False))
18
+
19
+ cuts = obb.tensorust.credit_q3_cuts().to_df()
20
+ print("\nBiggest Q3 2026 markdowns so far:")
21
+ print(cuts[["company", "fund", "jun30_mark", "sep30_mark", "change_points"]].head().to_string(index=False))
@@ -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)