Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **`account_category` and `source_anomalies` on budget accounts** (Tango API 5.7.0). Both are in the default `BUDGET_ACCOUNTS_MINIMAL` shape and accepted in custom shapes. `account_category` is `budgetary` or `credit_financing` today. `source_anomalies` lists the problems found in the source data behind an account, and is `[]` when there are none. Each element is typed as the new `BudgetAccountSourceAnomaly` `TypedDict` (with `BudgetAccountAnomalySource` and `BudgetAccountAnomalySourceRow` for its nested `source`), and every key is optional. Treat `account_category` and an anomaly's `code` as open strings, since new values may appear.
- **`list_budget_accounts(account_category=..., account_category_in=...)`** filters by account category, exactly or against a comma-separated list.

### Changed

- **`list_budget_accounts()` documents the API's default ordering**: the latest fiscal year first, then largest `enacted_ba` first, with accounts that have no `enacted_ba` last.
- Re-vendored `contracts/filter_shape_contract.json` (Tango API 5.7.0) and regenerated `tango/shapes/generated_overlay.py`.

### Fixed

- **`list_idvs()`, `list_idv_awards()`, `list_idv_child_idvs()` and `list_idv_transactions()` now return the API's pagination `cursor`.** They dropped it, so `response.cursor` was always `None` and a caller following the cursor stopped after the first page.
Expand Down
17 changes: 16 additions & 1 deletion contracts/filter_shape_contract.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"meta": {
"api_version": "5.5.0",
"api_version": "5.7.0",
"description": "Canonical API filter/shape contract. Downstream consumers (SDK, MCP) should validate their conformance against this manifest.",
"generated_from": "scripts/filter_shape_conformance.py",
"schema_version": 2
Expand Down Expand Up @@ -1628,6 +1628,8 @@
"resource_key": "budget/accounts",
"runtime": {
"filter_params": [
"account_category",
"account_category__in",
"account_title__icontains",
"actual_vs_requested_contract",
"actual_vs_requested_contract__gte",
Expand Down Expand Up @@ -1727,6 +1729,15 @@
"unobligated_pct__lte"
],
"filter_params_detail": {
"account_category": {
"filter_class": "CharFilter",
"type": "string"
},
"account_category__in": {
"filter_class": "CharInFilter",
"lookup": "in",
"type": "string"
},
"account_title__icontains": {
"filter_class": "CharFilter",
"lookup": "icontains",
Expand Down Expand Up @@ -2260,6 +2271,7 @@
}
},
"fields": [
"account_category",
"account_narrative_excerpt",
"account_title",
"actual_vs_requested_contract",
Expand Down Expand Up @@ -2324,6 +2336,7 @@
"requested_ba",
"requested_contractual_services",
"requested_personnel_share",
"source_anomalies",
"subfunction_code",
"top_contract_recipients",
"top_grant_recipients",
Expand All @@ -2333,6 +2346,7 @@
]
},
"shape_flat_paths": [
"account_category",
"account_narrative_excerpt",
"account_title",
"actual_vs_requested_contract",
Expand Down Expand Up @@ -2441,6 +2455,7 @@
"requested_ba",
"requested_contractual_services",
"requested_personnel_share",
"source_anomalies",
"subfunction_code",
"top_contract_recipients",
"top_grant_recipients",
Expand Down
12 changes: 12 additions & 0 deletions contracts/observed_shape_types.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@
},
"budget/accounts": {
"paths": {
"account_category": {
"is_list": false,
"is_optional": true,
"kind": "scalar",
"type": "str"
},
"account_narrative_excerpt": {
"is_list": false,
"is_optional": true,
Expand Down Expand Up @@ -438,6 +444,12 @@
"is_optional": true,
"kind": "scalar",
"type": "int"
},
"source_anomalies": {
"is_list": true,
"is_optional": true,
"kind": "scalar",
"type": "dict"
}
},
"records_seen": 80
Expand Down
16 changes: 14 additions & 2 deletions docs/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,8 @@ accounts = client.list_budget_accounts(
bea_category=None,
on_off_budget=None,
subfunction_code=None,
account_category=None,
account_category_in=None,
search=None,
ordering=None,
)
Expand All @@ -1785,8 +1787,18 @@ accounts = client.list_budget_accounts(
- `bea_category` - BEA category (exact)
- `on_off_budget` - On/off budget flag (exact)
- `subfunction_code` - Subfunction code (exact)
- `account_category` - Account category (exact): `budgetary` or `credit_financing` today; treat it as an open string
- `account_category_in` - Comma-separated account categories to match any of (e.g. `"budgetary,credit_financing"`)
- `search` - Full-text search over `account_title`, `agency_name`, `bureau_name`
- `ordering` - Sort field; prefix with `-` for descending
- `ordering` - Sort field; prefix with `-` for descending. The default is the latest fiscal year first, then largest `enacted_ba` first, with accounts that have no `enacted_ba` last.

**Source anomalies:** the default shape includes `account_category` and `source_anomalies`. `source_anomalies` is a list of problems found in the source data behind the account, and `[]` when there are none. Each element is a `BudgetAccountSourceAnomaly` (a `TypedDict`; every key is optional and may be `None`, so read keys with `.get()`): `code`, `field`, `bound_field`, `action` (`capped` or `flagged`), `reported_value`, `served_value`, `likely_cause`, `affected_fields`, `message`, and `source` (`dataset`, `fiscal_year`, and `rows` of `fiscal_period`, `piid`, `parent_piid`, `tas`, `reporting_agency_id`, `transaction_obligated_amount`, `file_c_source`). Today `code` is one of `contract_exceeds_obligations`, `assistance_exceeds_obligations` or `contract_without_obligations`; treat it as an open string. There is no filter on anomalies.

```python
for acct in client.list_budget_accounts(fiscal_year=2025).results:
for anomaly in acct["source_anomalies"] or []:
print(acct["federal_account_symbol"], anomaly.get("code"), anomaly.get("action"))
```

**Returns:** [PaginatedResponse](#paginatedresponse) of `BudgetAccount` records (see [ShapeConfig](#shapeconfig-predefined-shapes) for the default shape).

Expand Down Expand Up @@ -2547,7 +2559,7 @@ entity = client.get_entity("UEI_KEY", shape=ShapeConfig.ENTITIES_COMPREHENSIVE)
| `FEDERAL_REGISTER_COMPREHENSIVE` | `get_federal_register_document` | The list fields plus dates, signing_date, start_page, end_page, volume, docket_ids, dockets, regulation_id_numbers, topics, correction_of, corrections, executive_order_number, presidential_document_number, proclamation_number, comment_url, regulations_dot_gov_url, raw_text_url, body_html_url (omits `full_text`) |
| `EBUY_REQUESTS_MINIMAL` | `list_ebuy_requests` | rfq_id, request_type, title, schedule, sin, status, buyer_name, buyer_agency, buyer_agency_code, reference_number, issue_date, close_date, attachment_count, link_count, last_seen |
| `EBUY_REQUESTS_COMPREHENSIVE` | `get_ebuy_request` | Every field, plus the `organization` and `attachments` expands |
| `BUDGET_ACCOUNTS_MINIMAL` | `list_budget_accounts`, `get_budget_account` | id, federal_account_symbol, fiscal_year, agency_code/name, bureau_name, account_title, bea_category, on_off_budget, subfunction_code, lifecycle (requested/enacted/apportioned/obligated/outlayed/unobligated), contract & assistance rollups, key ratios, next-year growth |
| `BUDGET_ACCOUNTS_MINIMAL` | `list_budget_accounts`, `get_budget_account` | id, federal_account_symbol, fiscal_year, agency_code/name, bureau_name, account_title, bea_category, on_off_budget, subfunction_code, account_category, lifecycle (requested/enacted/apportioned/obligated/outlayed/unobligated), contract & assistance rollups, key ratios, next-year growth, source_anomalies |
| `VEHICLE_ORDERS_MINIMAL` | `list_vehicle_orders` | key, piid, award_date, recipient(display_name,uei), total_contract_value, obligated |
| `ITDASHBOARD_INVESTMENTS_MINIMAL` | `list_itdashboard_investments` | Minimal IT Dashboard investment fields |
| `ITDASHBOARD_INVESTMENTS_COMPREHENSIVE` | `get_itdashboard_investment` | Full investment fields: uii, agency_code, agency_name, bureau_code, bureau_name, investment_title, type_of_investment, part_of_it_portfolio, updated_time, url |
Expand Down
6 changes: 6 additions & 0 deletions tango/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
)
from .models import (
BudgetAccount,
BudgetAccountAnomalySource,
BudgetAccountAnomalySourceRow,
BudgetAccountSourceAnomaly,
ContractAppeal,
DibbsAward,
DibbsRfp,
Expand Down Expand Up @@ -72,6 +75,9 @@
"ResolveCandidate",
"ResolveResult",
"BudgetAccount",
"BudgetAccountAnomalySource",
"BudgetAccountAnomalySourceRow",
"BudgetAccountSourceAnomaly",
"ContractAppeal",
"FederalRegisterDocument",
"EbuyAccess",
Expand Down
13 changes: 12 additions & 1 deletion tango/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4448,6 +4448,8 @@ def list_budget_accounts(
bea_category: str | None = None,
on_off_budget: str | None = None,
subfunction_code: str | None = None,
account_category: str | None = None,
account_category_in: str | None = None,
# Range-numeric filters: each field also accepts ``__gte`` / ``__lte``.
requested_ba: float | None = None,
requested_ba_gte: float | None = None,
Expand Down Expand Up @@ -4554,6 +4556,11 @@ def list_budget_accounts(
bea_category: BEA category (exact).
on_off_budget: On/off budget flag (exact).
subfunction_code: Subfunction code (exact).
account_category: Account category (exact), e.g. ``budgetary`` or
``credit_financing``. Treat the value as an open string; new
categories may be added.
account_category_in: Comma-separated account categories to match
any of, e.g. ``"budgetary,credit_financing"``.
requested_ba: President's-budget requested BA (exact). Also
``requested_ba_gte`` / ``requested_ba_lte`` for range queries.
enacted_ba: Enacted budget authority (exact / gte / lte).
Expand Down Expand Up @@ -4596,7 +4603,9 @@ def list_budget_accounts(
ordering: Sort field (prefix with '-' for descending). Any of the
numeric fields above is a valid ordering target — e.g.
``ordering="-unobligated_balance"`` to rank by largest
headroom first.
headroom first. The default ordering is the latest fiscal year
first, then largest ``enacted_ba`` first, with accounts that
have no ``enacted_ba`` sorted last.
"""
params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
if shape is None:
Expand All @@ -4619,6 +4628,8 @@ def list_budget_accounts(
("bea_category", bea_category),
("on_off_budget", on_off_budget),
("subfunction_code", subfunction_code),
("account_category", account_category),
("account_category__in", account_category_in),
("search", search),
("ordering", ordering),
)
Expand Down
57 changes: 55 additions & 2 deletions tango/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,55 @@ class ValidateResult:
errors: list[str] | None = None


class BudgetAccountAnomalySourceRow(TypedDict, total=False):
"""One source record behind a budget-account source anomaly.

Every key is optional and any value may be ``None``.
"""

fiscal_period: int | None
piid: str | None
parent_piid: str | None
tas: str | None
reporting_agency_id: str | None
transaction_obligated_amount: float | str | None
file_c_source: str | None


class BudgetAccountAnomalySource(TypedDict, total=False):
"""Where a budget-account source anomaly was found.

Every key is optional and any value may be ``None``.
"""

dataset: str | None
fiscal_year: int | None
rows: list[BudgetAccountAnomalySourceRow] | None


class BudgetAccountSourceAnomaly(TypedDict, total=False):
"""One problem found in the source data behind a budget account.

An element of ``BudgetAccount.source_anomalies``. Every key is optional and
any value may be ``None``, so read keys with ``.get()``. ``code`` and
``action`` are open strings: today ``code`` is one of
``contract_exceeds_obligations``, ``assistance_exceeds_obligations`` or
``contract_without_obligations``, and ``action`` is ``capped`` or
``flagged``, but new values may appear.
"""

code: str | None
field: str | None
bound_field: str | None
action: str | None
reported_value: float | str | None
served_value: float | str | None
likely_cause: str | None
affected_fields: list[str] | None
message: str | None
source: BudgetAccountAnomalySource | None


@dataclass
class BudgetAccount:
"""Schema definition for BudgetAccount (not used for instances).
Expand All @@ -805,6 +854,8 @@ class BudgetAccount:
bea_category: str | None = None
on_off_budget: str | None = None
subfunction_code: str | None = None
# ``budgetary`` or ``credit_financing`` today; treat as an open string.
account_category: str | None = None
# Lifecycle
requested_ba: Decimal | None = None
enacted_ba: Decimal | None = None
Expand All @@ -828,6 +879,8 @@ class BudgetAccount:
assistance_share_of_obligated: Decimal | None = None
assistance_share_of_obligated_capped: Decimal | None = None
assistance_share_capped_flag: bool | None = None
# Problems found in the source data; ``[]`` when there are none.
source_anomalies: list[BudgetAccountSourceAnomaly] | None = None
# Forward-look
next_year_requested_ba: Decimal | None = None
ba_growth_next_year: Decimal | None = None
Expand Down Expand Up @@ -1728,12 +1781,12 @@ class ShapeConfig:
# Mirrors the API's BUDGET_ACCOUNT_DEFAULT_SHAPE.
BUDGET_ACCOUNTS_MINIMAL: Final = (
"id,federal_account_symbol,fiscal_year,agency_code,agency_name,bureau_name,"
"account_title,bea_category,on_off_budget,subfunction_code,"
"account_title,bea_category,on_off_budget,subfunction_code,account_category,"
"requested_ba,enacted_ba,apportioned,obligated_total,outlayed_total,"
"unobligated_balance,contract_obligated,contract_share_of_obligated_capped,"
"assistance_obligated,obligated_to_apportioned_pct_capped,"
"obligated_to_enacted_pct_capped,outlayed_to_obligated_pct_capped,"
"ba_growth_next_year_pct"
"ba_growth_next_year_pct,source_anomalies"
)

# Default for list_organizations()
Expand Down
6 changes: 6 additions & 0 deletions tango/shapes/generated_overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -1225,6 +1225,9 @@
),
},
"BudgetAccount": {
"account_category": FieldSchema(
name="account_category", type=str, is_optional=True, is_list=False
),
"account_narrative_excerpt": FieldSchema(
name="account_narrative_excerpt", type=str, is_optional=True, is_list=False
),
Expand Down Expand Up @@ -1410,6 +1413,9 @@
"requested_personnel_share": FieldSchema(
name="requested_personnel_share", type=str, is_optional=True, is_list=False
),
"source_anomalies": FieldSchema(
name="source_anomalies", type=dict, is_optional=True, is_list=True
),
"subfunction_code": FieldSchema(
name="subfunction_code", type=str, is_optional=True, is_list=False
),
Expand Down
Loading
Loading