orionapi 2.32.1__tar.gz → 2.34.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: orionapi
3
- Version: 2.32.1
3
+ Version: 2.34.0
4
4
  Summary: A python interface for the Orion Advisors platform web API
5
5
  Author: spencerogden-dsam
6
6
  Author-email: 67068943+spencerogden-dsam@users.noreply.github.com
@@ -1,4 +1,4 @@
1
- __version__ = "2.32.1"
1
+ __version__ = "2.34.0"
2
2
 
3
3
  import logging
4
4
  import re
@@ -1492,7 +1492,8 @@ class OrionAPI(BaseAPI):
1492
1492
  bill_type: Type of bill. One of: Unknown, Renewal, NewMoney,
1493
1493
  NewAccount, FinancialPlanningFee, Performance, AdvanceCreditDebit
1494
1494
  nickname: Optional nickname for this billing instance
1495
- keys: Optional list of integer IDs for the selected run_for scope
1495
+ keys: Optional list of integer IDs for the selected run_for scope.
1496
+ Sent as [] when omitted (required by Orion for AllHouseholds).
1496
1497
  as_of_date: Optional as-of date (YYYY-MM-DD format)
1497
1498
  end_date_override: Optional end date override (YYYY-MM-DD format)
1498
1499
  include_cash_flow: Whether to include cash flow (default False)
@@ -1529,8 +1530,9 @@ class OrionAPI(BaseAPI):
1529
1530
  }
1530
1531
  if nickname is not None:
1531
1532
  payload["nickName"] = nickname
1532
- if keys is not None:
1533
- payload["keys"] = keys
1533
+ # Always send keys: with it omitted, runFor="AllHouseholds" gets a
1534
+ # 404 "Object does not exist" (live-verified 2026-09); [] works.
1535
+ payload["keys"] = keys if keys is not None else []
1534
1536
  if as_of_date is not None:
1535
1537
  payload["asOfDate"] = as_of_date
1536
1538
  if end_date_override is not None:
@@ -1840,9 +1842,40 @@ class OrionAPI(BaseAPI):
1840
1842
  )
1841
1843
  return _json_or_none(res)
1842
1844
 
1845
+ def create_bill_data_files(self, instance_id):
1846
+ """Create the Data Files for a billing instance and advance its status.
1847
+
1848
+ What "Create" on the Data Files step of Orion's billing screen calls.
1849
+ Unlike generate_audit_files(), this completes the Data Files step: a
1850
+ forecast in "Data Files Needed" moves to "Complete". It also writes a
1851
+ fourth file, "reconciliation data.XLSX", alongside the billing audit
1852
+ CSV and the billing data / payable summary XLSX files.
1853
+
1854
+ The call returns before the files exist (they appeared within ~10s,
1855
+ 2026-09); list them with get_audit_files(instance_id=...).
1856
+
1857
+ Args:
1858
+ instance_id: Billing instance ID
1859
+
1860
+ Returns:
1861
+ None: Orion answers 200 with an empty body
1862
+ """
1863
+ if not isinstance(instance_id, int) or instance_id < 1:
1864
+ raise ValueError("instance_id must be a positive integer")
1865
+
1866
+ res = self.api_request(
1867
+ f"{self.base_url}/Billing/Instances/{instance_id}/Action/BillDataFiles",
1868
+ requests.post,
1869
+ )
1870
+ return _json_or_none(res)
1871
+
1843
1872
  def generate_audit_files(self, instance_ids, start_date=None, end_date=None, entity_list=None):
1844
1873
  """Generate the billing audit files for billing instance(s).
1845
1874
 
1875
+ This does NOT complete the Data Files step: the instance stays in
1876
+ "Data Files Needed". To do what the billing screen's "Create" does,
1877
+ use create_bill_data_files().
1878
+
1846
1879
  Works on forecast instances. For one instance Orion produced three
1847
1880
  files within ~10s: "billing audit.CSV", "billing data.XLSX" and
1848
1881
  "payable summary data.XLSX". The call returns before they exist; poll
@@ -3004,6 +3037,45 @@ class EclipseBase(BaseAPI):
3004
3037
  owner._orion_connect_firm_id = firm_ids[0]
3005
3038
  return owner._orion_connect_firm_id
3006
3039
 
3040
+ def get_analytics_status(self):
3041
+ """Check if analytics are currently running.
3042
+
3043
+ Returns:
3044
+ dict: Status with 'isAnalysisRunning' and 'doRunAnalytics' flags
3045
+ (0 = not running/no pending, 1 = running/pending)
3046
+ """
3047
+ res = self.api_request(f"{self.base_url}/dataimport/analysis/status")
3048
+ return res.json()
3049
+
3050
+ def wait_for_analytics(self, poll_interval=1, timeout=300):
3051
+ """Wait for analytics to complete.
3052
+
3053
+ Polls the analytics status until isAnalysisRunning is 0.
3054
+
3055
+ Args:
3056
+ poll_interval: Seconds between status checks (default 1)
3057
+ timeout: Max seconds to wait (default 300)
3058
+
3059
+ Returns:
3060
+ True when analytics complete
3061
+
3062
+ Raises:
3063
+ TimeoutError: If analytics don't complete within timeout
3064
+ """
3065
+ start = time.time()
3066
+ while True:
3067
+ status = self.get_analytics_status()
3068
+ if not status.get("isAnalysisRunning"):
3069
+ return True
3070
+ if time.time() - start > timeout:
3071
+ raise TimeoutError(f"Analytics did not complete within {timeout} seconds")
3072
+ time.sleep(poll_interval)
3073
+
3074
+ def _maybe_wait_for_analytics(self, sync):
3075
+ """Helper to conditionally wait for analytics."""
3076
+ if sync:
3077
+ self.wait_for_analytics()
3078
+
3007
3079
 
3008
3080
  class EclipseV1(EclipseBase):
3009
3081
  """Eclipse client targeting the v1 API surface (``/v1/...``) only.
@@ -3417,45 +3489,6 @@ class EclipseV1(EclipseBase):
3417
3489
  res = self.api_request(f"{self.base_url}/postimport/run_need_analysis")
3418
3490
  return res.json()
3419
3491
 
3420
- def get_analytics_status(self):
3421
- """Check if analytics are currently running.
3422
-
3423
- Returns:
3424
- dict: Status with 'isAnalysisRunning' and 'doRunAnalytics' flags
3425
- (0 = not running/no pending, 1 = running/pending)
3426
- """
3427
- res = self.api_request(f"{self.base_url}/dataimport/analysis/status")
3428
- return res.json()
3429
-
3430
- def wait_for_analytics(self, poll_interval=1, timeout=300):
3431
- """Wait for analytics to complete.
3432
-
3433
- Polls the analytics status until isAnalysisRunning is 0.
3434
-
3435
- Args:
3436
- poll_interval: Seconds between status checks (default 1)
3437
- timeout: Max seconds to wait (default 300)
3438
-
3439
- Returns:
3440
- True when analytics complete
3441
-
3442
- Raises:
3443
- TimeoutError: If analytics don't complete within timeout
3444
- """
3445
- start = time.time()
3446
- while True:
3447
- status = self.get_analytics_status()
3448
- if not status.get("isAnalysisRunning"):
3449
- return True
3450
- if time.time() - start > timeout:
3451
- raise TimeoutError(f"Analytics did not complete within {timeout} seconds")
3452
- time.sleep(poll_interval)
3453
-
3454
- def _maybe_wait_for_analytics(self, sync):
3455
- """Helper to conditionally wait for analytics."""
3456
- if sync:
3457
- self.wait_for_analytics()
3458
-
3459
3492
  def get_orders(self):
3460
3493
  """Deprecated alias of ``get_trades(is_pending=False)``.
3461
3494
 
@@ -3825,9 +3858,19 @@ class EclipseV1(EclipseBase):
3825
3858
  selected_method_id=None,
3826
3859
  spend_full_amount=None,
3827
3860
  filter_type=None,
3861
+ accounts=None,
3828
3862
  ):
3829
3863
  """Generate Spend Cash trade for portfolios.
3830
3864
 
3865
+ Eclipse's spend-cash generator needs the accounts spelled out: a body with
3866
+ only ``portfolioIds`` returns 500 "please verify parameters" for every
3867
+ portfolio (live-verified 2026-09-29 across 9 portfolios). So when
3868
+ ``accounts`` is omitted, the accounts of each portfolio are resolved via
3869
+ :meth:`get_portfolio_accounts` and sent as ``{"id": <internal id>,
3870
+ "amount": 0}`` (one extra GET per portfolio). ``sleevedPortfolios`` and
3871
+ ``emphasiedAccounts`` (sic, Eclipse's spelling) are sent as null, as in
3872
+ Eclipse's documented examples.
3873
+
3831
3874
  Args:
3832
3875
  portfolio_ids: List of portfolio IDs to process
3833
3876
  portfolio_trade_group_ids: List of portfolio trade group IDs (optional)
@@ -3841,12 +3884,23 @@ class EclipseV1(EclipseBase):
3841
3884
  (see ``get_spend_cash_methods``); added to the body only when provided
3842
3885
  spend_full_amount: Optional bool; added to the body only when provided
3843
3886
  filter_type: Optional filter type; added to the body only when provided
3887
+ accounts: Optional list of ``{"id": <Eclipse internal account id>,
3888
+ "amount": <number>}``. Omitted: resolved from ``portfolio_ids``.
3889
+ Account ids are Eclipse internal ids (``get_portfolio_accounts``
3890
+ ``id``), not the Orion ``accountId``.
3844
3891
 
3845
3892
  Returns:
3846
3893
  dict with 'issues', 'success', and 'instanceId' fields
3847
3894
  """
3848
3895
  if portfolio_trade_group_ids is None:
3849
3896
  portfolio_trade_group_ids = []
3897
+ if accounts is None:
3898
+ accounts = [
3899
+ {"id": acct["id"], "amount": 0}
3900
+ for pid in portfolio_ids or []
3901
+ for acct in (self.get_portfolio_accounts(pid) or [])
3902
+ if isinstance(acct, dict) and acct.get("id") is not None
3903
+ ]
3850
3904
 
3851
3905
  payload = {
3852
3906
  "portfolioIds": portfolio_ids,
@@ -3854,6 +3908,9 @@ class EclipseV1(EclipseBase):
3854
3908
  "isViewOnly": is_view_only,
3855
3909
  "reason": reason,
3856
3910
  "isExcelImport": is_excel_import,
3911
+ "accounts": accounts,
3912
+ "sleevedPortfolios": None,
3913
+ "emphasiedAccounts": None,
3857
3914
  }
3858
3915
  if selected_method_id is not None:
3859
3916
  payload["selectedMethodId"] = selected_method_id
@@ -9726,7 +9783,7 @@ class EclipseV2(EclipseBase):
9726
9783
  )
9727
9784
  return res.json()
9728
9785
 
9729
- def billing_set_aside_cash(self, payload):
9786
+ def billing_set_aside_cash(self, payload, sync=False):
9730
9787
  """Set each account's billing set-aside cash (mutating).
9731
9788
 
9732
9789
  The endpoint behind Orion's Cash Funding Export. Give it dollar
@@ -9751,6 +9808,8 @@ class EclipseV2(EclipseBase):
9751
9808
  Orion account id (the ``id`` of a get_cash_funding() row), not
9752
9809
  the Eclipse id. ``orionConnectFirmId`` is filled in from
9753
9810
  get_orion_connect_firm_id() when an entry omits it.
9811
+ sync: If True, wait for Eclipse analytics to finish after the
9812
+ POST, as create_set_aside(sync=True) does (default False)
9754
9813
 
9755
9814
  Returns:
9756
9815
  dict | None: Parsed response, or None on the empty body the
@@ -9770,7 +9829,9 @@ class EclipseV2(EclipseBase):
9770
9829
  res = self.api_request(
9771
9830
  f"{self.base_url_v2}/SetAsideCash/BillingSetAsideCash", requests.post, json=payload
9772
9831
  )
9773
- return _json_or_none(res)
9832
+ result = _json_or_none(res)
9833
+ self._maybe_wait_for_analytics(sync)
9834
+ return result
9774
9835
 
9775
9836
  def delete_account_set_aside_cash(self, payload):
9776
9837
  """Delete account set-aside cash (mutating).
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "orionapi"
3
- version = "2.32.1"
3
+ version = "2.34.0"
4
4
  description = "A python interface for the Orion Advisors platform web API"
5
5
  authors = ["spencerogden-dsam <67068943+spencerogden-dsam@users.noreply.github.com>"]
6
6
 
File without changes