tango-python 1.10.0__py3-none-any.whl → 1.12.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.
tango/__init__.py CHANGED
@@ -11,6 +11,9 @@ from .exceptions import (
11
11
  )
12
12
  from .models import (
13
13
  BudgetAccount,
14
+ BudgetAccountAnomalySource,
15
+ BudgetAccountAnomalySourceRow,
16
+ BudgetAccountSourceAnomaly,
14
17
  ContractAppeal,
15
18
  DibbsAward,
16
19
  DibbsRfp,
@@ -59,7 +62,7 @@ from .webhooks import (
59
62
  )
60
63
  from .webhooks.receiver import Delivery, WebhookReceiver
61
64
 
62
- __version__ = "1.10.0"
65
+ __version__ = "1.12.0"
63
66
  __all__ = [
64
67
  "TangoClient",
65
68
  "TangoAPIError",
@@ -72,6 +75,9 @@ __all__ = [
72
75
  "ResolveCandidate",
73
76
  "ResolveResult",
74
77
  "BudgetAccount",
78
+ "BudgetAccountAnomalySource",
79
+ "BudgetAccountAnomalySourceRow",
80
+ "BudgetAccountSourceAnomaly",
75
81
  "ContractAppeal",
76
82
  "FederalRegisterDocument",
77
83
  "EbuyAccess",
tango/client.py CHANGED
@@ -986,6 +986,7 @@ class TangoClient:
986
986
  next=data.get("next"),
987
987
  previous=data.get("previous"),
988
988
  results=results,
989
+ cursor=data.get("cursor"),
989
990
  page_metadata=data.get("page_metadata"),
990
991
  meta=data.get("meta"),
991
992
  )
@@ -1088,6 +1089,7 @@ class TangoClient:
1088
1089
  next=data.get("next"),
1089
1090
  previous=data.get("previous"),
1090
1091
  results=results,
1092
+ cursor=data.get("cursor"),
1091
1093
  page_metadata=data.get("page_metadata"),
1092
1094
  meta=data.get("meta"),
1093
1095
  )
@@ -1133,6 +1135,7 @@ class TangoClient:
1133
1135
  next=data.get("next"),
1134
1136
  previous=data.get("previous"),
1135
1137
  results=results,
1138
+ cursor=data.get("cursor"),
1136
1139
  page_metadata=data.get("page_metadata"),
1137
1140
  meta=data.get("meta"),
1138
1141
  )
@@ -1150,6 +1153,7 @@ class TangoClient:
1150
1153
  next=data.get("next"),
1151
1154
  previous=data.get("previous"),
1152
1155
  results=data.get("results") or [],
1156
+ cursor=data.get("cursor"),
1153
1157
  page_metadata=data.get("page_metadata"),
1154
1158
  meta=data.get("meta"),
1155
1159
  )
@@ -2346,6 +2350,9 @@ class TangoClient:
2346
2350
  search: str | None = None,
2347
2351
  set_aside: str | None = None,
2348
2352
  solicitation_number: str | None = None,
2353
+ *,
2354
+ awarded: bool | None = None,
2355
+ awardee_uei: str | None = None,
2349
2356
  ) -> PaginatedResponse:
2350
2357
  """
2351
2358
  List contract opportunities/solicitations
@@ -2358,6 +2365,8 @@ class TangoClient:
2358
2365
  flat_lists: If True, flatten arrays using indexed keys
2359
2366
  active: Filter by active status
2360
2367
  agency: Agency filter
2368
+ awarded: Filter by whether the opportunity has an award, either posted on it or linked to it from a separate award notice. Searches every opportunity rather than only active ones. Requires Tango API 5.5.0
2369
+ awardee_uei: Filter by the awardee's UEI (case-insensitive). OR several with ``|``. Searches every opportunity rather than only active ones. Requires Tango API 5.5.0
2361
2370
  first_notice_date_after: First notice date after
2362
2371
  first_notice_date_before: First notice date before
2363
2372
  last_notice_date_after: Last notice date after
@@ -2387,6 +2396,8 @@ class TangoClient:
2387
2396
  for key, val in (
2388
2397
  ("active", active),
2389
2398
  ("agency", agency),
2399
+ ("awarded", awarded),
2400
+ ("awardee_uei", awardee_uei),
2390
2401
  ("first_notice_date_after", first_notice_date_after),
2391
2402
  ("first_notice_date_before", first_notice_date_before),
2392
2403
  ("last_notice_date_after", last_notice_date_after),
@@ -4437,6 +4448,8 @@ class TangoClient:
4437
4448
  bea_category: str | None = None,
4438
4449
  on_off_budget: str | None = None,
4439
4450
  subfunction_code: str | None = None,
4451
+ account_category: str | None = None,
4452
+ account_category_in: str | None = None,
4440
4453
  # Range-numeric filters: each field also accepts ``__gte`` / ``__lte``.
4441
4454
  requested_ba: float | None = None,
4442
4455
  requested_ba_gte: float | None = None,
@@ -4543,6 +4556,11 @@ class TangoClient:
4543
4556
  bea_category: BEA category (exact).
4544
4557
  on_off_budget: On/off budget flag (exact).
4545
4558
  subfunction_code: Subfunction code (exact).
4559
+ account_category: Account category (exact), e.g. ``budgetary`` or
4560
+ ``credit_financing``. Treat the value as an open string; new
4561
+ categories may be added.
4562
+ account_category_in: Comma-separated account categories to match
4563
+ any of, e.g. ``"budgetary,credit_financing"``.
4546
4564
  requested_ba: President's-budget requested BA (exact). Also
4547
4565
  ``requested_ba_gte`` / ``requested_ba_lte`` for range queries.
4548
4566
  enacted_ba: Enacted budget authority (exact / gte / lte).
@@ -4585,7 +4603,9 @@ class TangoClient:
4585
4603
  ordering: Sort field (prefix with '-' for descending). Any of the
4586
4604
  numeric fields above is a valid ordering target — e.g.
4587
4605
  ``ordering="-unobligated_balance"`` to rank by largest
4588
- headroom first.
4606
+ headroom first. The default ordering is the latest fiscal year
4607
+ first, then largest ``enacted_ba`` first, with accounts that
4608
+ have no ``enacted_ba`` sorted last.
4589
4609
  """
4590
4610
  params: dict[str, Any] = {"page": page, "limit": min(limit, 100)}
4591
4611
  if shape is None:
@@ -4608,6 +4628,8 @@ class TangoClient:
4608
4628
  ("bea_category", bea_category),
4609
4629
  ("on_off_budget", on_off_budget),
4610
4630
  ("subfunction_code", subfunction_code),
4631
+ ("account_category", account_category),
4632
+ ("account_category__in", account_category_in),
4611
4633
  ("search", search),
4612
4634
  ("ordering", ordering),
4613
4635
  )
tango/models.py CHANGED
@@ -565,6 +565,10 @@ class Opportunity:
565
565
  Both must be named, e.g. ``attachments(name,url,doc_role,doc_role_alt)``, because ``attachments(*)`` does not include them.
566
566
  An attachment Tango has not classified omits both keys rather than returning null, so read them with ``.get()``.
567
567
  A Free-plan request that names them gets the response without them, plus an entry in ``meta.upgrade_hints``.
568
+
569
+ The award fields (``award_date``, ``award_amount``, ``awardee``, ``awardee_uei``) are filled only where SAM.gov posted an award notice, and ``award_amount`` is the text SAM.gov published, not a number.
570
+ SAM.gov often posts an award as its own opportunity; when that award notice references its solicitation, ``solicitation_opportunity_id`` points back to it, and the solicitation's ``awards(...)`` expand lists up to ten of the most recent linked awards, with ``award_count`` giving the full count.
571
+ An award notice that does not reference its solicitation is not linked.
568
572
  """
569
573
 
570
574
  opportunity_id: str
@@ -575,6 +579,15 @@ class Opportunity:
575
579
  active: bool | None = None
576
580
  naics_code: str | None = None
577
581
  psc_code: str | None = None
582
+ award_number: str | None = None
583
+ awarded: bool | None = None
584
+ award_date: date | None = None
585
+ award_amount: str | None = None
586
+ awardee: str | None = None
587
+ awardee_uei: str | None = None
588
+ award_count: int | None = None
589
+ solicitation_opportunity_id: str | None = None
590
+ awards: list[dict[str, Any]] | None = None
578
591
 
579
592
 
580
593
  @dataclass
@@ -593,6 +606,11 @@ class Notice:
593
606
  description: str | None = None
594
607
  posted_date: datetime | None = None
595
608
  naics_code: str | None = None
609
+ award_number: str | None = None
610
+ award_date: date | None = None
611
+ award_amount: str | None = None
612
+ awardee: str | None = None
613
+ awardee_uei: str | None = None
596
614
 
597
615
 
598
616
  @dataclass
@@ -768,6 +786,55 @@ class ValidateResult:
768
786
  errors: list[str] | None = None
769
787
 
770
788
 
789
+ class BudgetAccountAnomalySourceRow(TypedDict, total=False):
790
+ """One source record behind a budget-account source anomaly.
791
+
792
+ Every key is optional and any value may be ``None``.
793
+ """
794
+
795
+ fiscal_period: int | None
796
+ piid: str | None
797
+ parent_piid: str | None
798
+ tas: str | None
799
+ reporting_agency_id: str | None
800
+ transaction_obligated_amount: float | str | None
801
+ file_c_source: str | None
802
+
803
+
804
+ class BudgetAccountAnomalySource(TypedDict, total=False):
805
+ """Where a budget-account source anomaly was found.
806
+
807
+ Every key is optional and any value may be ``None``.
808
+ """
809
+
810
+ dataset: str | None
811
+ fiscal_year: int | None
812
+ rows: list[BudgetAccountAnomalySourceRow] | None
813
+
814
+
815
+ class BudgetAccountSourceAnomaly(TypedDict, total=False):
816
+ """One problem found in the source data behind a budget account.
817
+
818
+ An element of ``BudgetAccount.source_anomalies``. Every key is optional and
819
+ any value may be ``None``, so read keys with ``.get()``. ``code`` and
820
+ ``action`` are open strings: today ``code`` is one of
821
+ ``contract_exceeds_obligations``, ``assistance_exceeds_obligations`` or
822
+ ``contract_without_obligations``, and ``action`` is ``capped`` or
823
+ ``flagged``, but new values may appear.
824
+ """
825
+
826
+ code: str | None
827
+ field: str | None
828
+ bound_field: str | None
829
+ action: str | None
830
+ reported_value: float | str | None
831
+ served_value: float | str | None
832
+ likely_cause: str | None
833
+ affected_fields: list[str] | None
834
+ message: str | None
835
+ source: BudgetAccountAnomalySource | None
836
+
837
+
771
838
  @dataclass
772
839
  class BudgetAccount:
773
840
  """Schema definition for BudgetAccount (not used for instances).
@@ -787,6 +854,8 @@ class BudgetAccount:
787
854
  bea_category: str | None = None
788
855
  on_off_budget: str | None = None
789
856
  subfunction_code: str | None = None
857
+ # ``budgetary`` or ``credit_financing`` today; treat as an open string.
858
+ account_category: str | None = None
790
859
  # Lifecycle
791
860
  requested_ba: Decimal | None = None
792
861
  enacted_ba: Decimal | None = None
@@ -810,6 +879,8 @@ class BudgetAccount:
810
879
  assistance_share_of_obligated: Decimal | None = None
811
880
  assistance_share_of_obligated_capped: Decimal | None = None
812
881
  assistance_share_capped_flag: bool | None = None
882
+ # Problems found in the source data; ``[]`` when there are none.
883
+ source_anomalies: list[BudgetAccountSourceAnomaly] | None = None
813
884
  # Forward-look
814
885
  next_year_requested_ba: Decimal | None = None
815
886
  ba_growth_next_year: Decimal | None = None
@@ -1710,12 +1781,12 @@ class ShapeConfig:
1710
1781
  # Mirrors the API's BUDGET_ACCOUNT_DEFAULT_SHAPE.
1711
1782
  BUDGET_ACCOUNTS_MINIMAL: Final = (
1712
1783
  "id,federal_account_symbol,fiscal_year,agency_code,agency_name,bureau_name,"
1713
- "account_title,bea_category,on_off_budget,subfunction_code,"
1784
+ "account_title,bea_category,on_off_budget,subfunction_code,account_category,"
1714
1785
  "requested_ba,enacted_ba,apportioned,obligated_total,outlayed_total,"
1715
1786
  "unobligated_balance,contract_obligated,contract_share_of_obligated_capped,"
1716
1787
  "assistance_obligated,obligated_to_apportioned_pct_capped,"
1717
1788
  "obligated_to_enacted_pct_capped,outlayed_to_obligated_pct_capped,"
1718
- "ba_growth_next_year_pct"
1789
+ "ba_growth_next_year_pct,source_anomalies"
1719
1790
  )
1720
1791
 
1721
1792
  # Default for list_organizations()
@@ -213,6 +213,16 @@ AWARDS_SCHEMA: dict[str, FieldSchema] = {
213
213
  "transactions": FieldSchema(name="transactions", type=str, is_optional=True, is_list=False),
214
214
  }
215
215
 
216
+ AWARDS2_SCHEMA: dict[str, FieldSchema] = {
217
+ "award_amount": FieldSchema(name="award_amount", type=str, is_optional=True, is_list=False),
218
+ "award_date": FieldSchema(name="award_date", type=date, is_optional=True, is_list=False),
219
+ "award_number": FieldSchema(name="award_number", type=str, is_optional=True, is_list=False),
220
+ "awardee": FieldSchema(name="awardee", type=str, is_optional=True, is_list=False),
221
+ "awardee_uei": FieldSchema(name="awardee_uei", type=str, is_optional=True, is_list=False),
222
+ "notice_id": FieldSchema(name="notice_id", type=str, is_optional=True, is_list=False),
223
+ "opportunity_id": FieldSchema(name="opportunity_id", type=str, is_optional=True, is_list=False),
224
+ }
225
+
216
226
  BUDGET_APPROPRIATION_SCHEMA: dict[str, FieldSchema] = {
217
227
  "cgac": FieldSchema(name="cgac", type=int, is_optional=True, is_list=False),
218
228
  "fiscal_year": FieldSchema(name="fiscal_year", type=int, is_optional=True, is_list=False),
@@ -1112,6 +1122,7 @@ GENERATED_NESTED: dict[str, dict[str, FieldSchema]] = {
1112
1122
  "AwardingOffice": AWARDING_OFFICE_SCHEMA,
1113
1123
  "AwardingOffice2": AWARDING_OFFICE2_SCHEMA,
1114
1124
  "Awards": AWARDS_SCHEMA,
1125
+ "Awards2": AWARDS2_SCHEMA,
1115
1126
  "BudgetAppropriation": BUDGET_APPROPRIATION_SCHEMA,
1116
1127
  "BudgetSpending": BUDGET_SPENDING_SCHEMA,
1117
1128
  "Children": CHILDREN_SCHEMA,
@@ -1214,6 +1225,9 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
1214
1225
  ),
1215
1226
  },
1216
1227
  "BudgetAccount": {
1228
+ "account_category": FieldSchema(
1229
+ name="account_category", type=str, is_optional=True, is_list=False
1230
+ ),
1217
1231
  "account_narrative_excerpt": FieldSchema(
1218
1232
  name="account_narrative_excerpt", type=str, is_optional=True, is_list=False
1219
1233
  ),
@@ -1399,6 +1413,9 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
1399
1413
  "requested_personnel_share": FieldSchema(
1400
1414
  name="requested_personnel_share", type=str, is_optional=True, is_list=False
1401
1415
  ),
1416
+ "source_anomalies": FieldSchema(
1417
+ name="source_anomalies", type=dict, is_optional=True, is_list=True
1418
+ ),
1402
1419
  "subfunction_code": FieldSchema(
1403
1420
  name="subfunction_code", type=str, is_optional=True, is_list=False
1404
1421
  ),
@@ -2228,6 +2245,10 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
2228
2245
  is_list=True,
2229
2246
  nested_model="Attachments",
2230
2247
  ),
2248
+ "award_amount": FieldSchema(name="award_amount", type=str, is_optional=True, is_list=False),
2249
+ "award_date": FieldSchema(name="award_date", type=date, is_optional=True, is_list=False),
2250
+ "awardee": FieldSchema(name="awardee", type=str, is_optional=True, is_list=False),
2251
+ "awardee_uei": FieldSchema(name="awardee_uei", type=str, is_optional=True, is_list=False),
2231
2252
  "meta": FieldSchema(
2232
2253
  name="meta", type=dict, is_optional=True, is_list=False, nested_model="Meta"
2233
2254
  ),
@@ -2476,6 +2497,15 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
2476
2497
  is_list=False,
2477
2498
  nested_model="Attachments5",
2478
2499
  ),
2500
+ "award_amount": FieldSchema(name="award_amount", type=str, is_optional=True, is_list=False),
2501
+ "award_count": FieldSchema(name="award_count", type=int, is_optional=True, is_list=False),
2502
+ "award_date": FieldSchema(name="award_date", type=date, is_optional=True, is_list=False),
2503
+ "awarded": FieldSchema(name="awarded", type=bool, is_optional=True, is_list=False),
2504
+ "awardee": FieldSchema(name="awardee", type=str, is_optional=True, is_list=False),
2505
+ "awardee_uei": FieldSchema(name="awardee_uei", type=str, is_optional=True, is_list=False),
2506
+ "awards": FieldSchema(
2507
+ name="awards", type=dict, is_optional=True, is_list=True, nested_model="Awards2"
2508
+ ),
2479
2509
  "department": FieldSchema(
2480
2510
  name="department",
2481
2511
  type=dict,
@@ -2529,6 +2559,9 @@ GENERATED_OVERLAY: dict[str, dict[str, FieldSchema]] = {
2529
2559
  nested_model="CodeDescription",
2530
2560
  ),
2531
2561
  "snippet": FieldSchema(name="snippet", type=str, is_optional=True, is_list=False),
2562
+ "solicitation_opportunity_id": FieldSchema(
2563
+ name="solicitation_opportunity_id", type=str, is_optional=True, is_list=False
2564
+ ),
2532
2565
  },
2533
2566
  "Organization": {
2534
2567
  "aac_code": FieldSchema(name="aac_code", type=str, is_optional=True, is_list=False),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tango-python
3
- Version: 1.10.0
3
+ Version: 1.12.0
4
4
  Summary: Python SDK for the Tango API
5
5
  Project-URL: Homepage, https://github.com/makegov/tango-python
6
6
  Project-URL: Documentation, https://docs.makegov.com/tango-python
@@ -1,11 +1,11 @@
1
- tango/__init__.py,sha256=rSPFjxFs-K1mVSICHc9nU94SvN5N2dhBEDVrD9y2BII,2535
2
- tango/client.py,sha256=Srf-68uv9VvO5Dv0hi9SP-bHorOQl97zOysmX1owTeA,237960
1
+ tango/__init__.py,sha256=XRADxf8CwMbNTUr999g7MgiFKFuzoSQ0L2szhIIDWtU,2739
2
+ tango/client.py,sha256=YkbWxdNE91229sDWwPCizAYfTl1LZK517wuD2I5hPhQ,239391
3
3
  tango/exceptions.py,sha256=oPiLCccBoWLK5SjHkzP-oI6mUD9twEeKSvEuLV5h01I,4224
4
- tango/models.py,sha256=MwloM-gYq0ng9obnDn_lqYFG_cJ5aQZq6OvDqrW7S2s,66647
4
+ tango/models.py,sha256=vviVPF0gJQTpqMJE7oGhZrJuavGgwhU5NazlVJBzpYw,69578
5
5
  tango/shapes/__init__.py,sha256=7ea1WU74jp4znhNw-gXruag6m6eyPZtbVgbDFmFUWro,1072
6
6
  tango/shapes/explicit_schemas.py,sha256=spYyMmWKOiMJygz9yViBQrjIxUPFfv7HpVkJcWy13x8,84021
7
7
  tango/shapes/factory.py,sha256=uDPhnp6VVzrLBKodxteZy5AcU43juEv-qKVRWth51j0,31550
8
- tango/shapes/generated_overlay.py,sha256=udDQ1vQ9d4DpAOyhjRhsTD_8gx9QqhZfwk_PKXwZUJE,138465
8
+ tango/shapes/generated_overlay.py,sha256=9SNrqVFBz59Xo8-u24-x-74g42juhCKjAArQr9_MM8o,140723
9
9
  tango/shapes/generator.py,sha256=xzakpwTwqTMUr89I06hp54-FIYZziviWHk2Glscg-LM,22775
10
10
  tango/shapes/models.py,sha256=h3pIhOqrrdlN953Y6r0oney5HFbKPOD-frRndRWimJ0,3018
11
11
  tango/shapes/parser.py,sha256=AecNx5oT_hV_pf2AWZnsR8Y2aLmCtjfgS9-znA2Pesc,27429
@@ -16,8 +16,8 @@ tango/webhooks/cli.py,sha256=f_vQbJ2AeSQjKnQo7MxHFyUB2SAKcgHYmGQULS0isqQ,13858
16
16
  tango/webhooks/receiver.py,sha256=5yhsVhlLyoxmOCGvmbynWAIlDB2OaCPVf1H4GA1SxmU,9279
17
17
  tango/webhooks/signing.py,sha256=92Ee-0B6PR7ZkvY3Np3gzl88-mtfKkh-I7lxqCe2lGw,2374
18
18
  tango/webhooks/simulate.py,sha256=g2Osa0FYU5mJuon07T2aUCtmkUoTEzsY261tlp76fF0,3165
19
- tango_python-1.10.0.dist-info/METADATA,sha256=GhxRXx0J567G_IXyAKx2JcDMXoyFFUbqezsYjNjvFig,22118
20
- tango_python-1.10.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
21
- tango_python-1.10.0.dist-info/entry_points.txt,sha256=kGLUbglUjuaAqEFvOZ1QuSW0vWb6VeSpCIFKaOFkKoQ,50
22
- tango_python-1.10.0.dist-info/licenses/LICENSE,sha256=j2kYVHMwTkoGn3ZNScnrdIueG0k2XzB_LCPFoyBc2wk,1064
23
- tango_python-1.10.0.dist-info/RECORD,,
19
+ tango_python-1.12.0.dist-info/METADATA,sha256=JsVs05maoR9Q7YGHhn4RQLpqyPG_ptX6BgpGnakcaeI,22118
20
+ tango_python-1.12.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
21
+ tango_python-1.12.0.dist-info/entry_points.txt,sha256=kGLUbglUjuaAqEFvOZ1QuSW0vWb6VeSpCIFKaOFkKoQ,50
22
+ tango_python-1.12.0.dist-info/licenses/LICENSE,sha256=j2kYVHMwTkoGn3ZNScnrdIueG0k2XzB_LCPFoyBc2wk,1064
23
+ tango_python-1.12.0.dist-info/RECORD,,