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,4 +1,4 @@
|
|
|
1
|
-
__version__ = "2.
|
|
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
|
-
|
|
1533
|
-
|
|
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
|
-
|
|
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).
|
|
File without changes
|