55import requests
66from rich .console import Console
77import toolz as tz
8+ from functools import partial
89from lenses import lens
910
1011from requests .exceptions import ConnectionError , ReadTimeout
@@ -193,7 +194,7 @@ def __post_init__(self) -> None:
193194 self .session = requests .Session ()
194195 console .print (f"✅Connected, local lib:{ '.' .join (self .version )} , server:{ '.' .join (engine_version )} " )
195196
196- def build_run_deal_req (self , run_type , deal , perfAssump = None , nonPerfAssump = []) -> str :
197+ def build_run_deal_req (self , run_type , deal , perfAssump = None , nonPerfAssump = [], rtn = [] ) -> str :
197198 """build run deal requests: (single run, multi-scenario run, multi-struct run) 2
198199
199200 :meta private:
@@ -217,22 +218,22 @@ def build_run_deal_req(self, run_type, deal, perfAssump=None, nonPerfAssump=[])
217218 _deal = deal .json if hasattr (deal , "json" ) else deal
218219 _perfAssump = earlyReturnNone (mkAssumpType , perfAssump )
219220 _nonPerfAssump = mkNonPerfAssumps ({}, nonPerfAssump )
220- r = mkTag ((RunReqType .Single .value , [_deal , _perfAssump , _nonPerfAssump ]))
221+ r = mkTag ((RunReqType .Single .value , [rtn , _deal , _perfAssump , _nonPerfAssump ]))
221222 case "MultiScenarios" | "MS" :
222223 _nonPerfAssump = mkNonPerfAssumps ({}, nonPerfAssump )
223224 _deal = deal .json if hasattr (deal , "json" ) else deal
224225 mAssump = mapValsBy (perfAssump , mkAssumpType )
225- r = mkTag ((RunReqType .MultiScenarios .value , [_deal , mAssump , _nonPerfAssump ]))
226+ r = mkTag ((RunReqType .MultiScenarios .value , [rtn , _deal , mAssump , _nonPerfAssump ]))
226227 case "MultiStructs" | "MD" :
227228 _nonPerfAssump = mkNonPerfAssumps ({}, nonPerfAssump )
228229 mDeal = {k : v .json if hasattr (v , "json" ) else v for k , v in deal .items ()}
229230 _perfAssump = mkAssumpType (perfAssump )
230- r = mkTag ((RunReqType .MultiStructs .value , [mDeal , _perfAssump , _nonPerfAssump ]))
231+ r = mkTag ((RunReqType .MultiStructs .value , [rtn , mDeal , _perfAssump , _nonPerfAssump ]))
231232 case "MultiRunScenarios" | "MRS" if isinstance (nonPerfAssump ,dict ):
232233 _deal = deal .json if hasattr (deal , "json" ) else deal
233234 _perfAssump = earlyReturnNone (mkAssumpType , perfAssump )
234235 mRunAssump = mapValsBy (nonPerfAssump , lambda x : mkNonPerfAssumps ({}, x ))
235- r = mkTag ((RunReqType .MultiRunScenarios .value , [_deal , _perfAssump , mRunAssump ]))
236+ r = mkTag ((RunReqType .MultiRunScenarios .value , [rtn , _deal , _perfAssump , mRunAssump ]))
236237 case "ComboSensitivity" | "CS" if isinstance (nonPerfAssump ,dict ) and isinstance (perfAssump ,dict ) and isinstance (deal ,dict ):
237238 mDeal = {k : v .json if hasattr (v , "json" ) else v for k , v in deal .items ()}
238239 mAssump = mapValsBy (perfAssump , mkAssumpType )
@@ -241,7 +242,7 @@ def build_run_deal_req(self, run_type, deal, perfAssump=None, nonPerfAssump=[])
241242 mRunAssump = mapValsBy (nonPerfAssump , lambda x : mkNonPerfAssumps ({}, x ))
242243 if mRunAssump == {}:
243244 mRunAssump = {"Empty" :{}}
244- r = mkTag ((RunReqType .ComboSensitivity .value , [mDeal , mAssump , mRunAssump ]))
245+ r = mkTag ((RunReqType .ComboSensitivity .value , [rtn , mDeal , mAssump , mRunAssump ]))
245246 case ("RootFinder" , tweak , stop ):
246247 _deal = deal .json if hasattr (deal , "json" ) else deal
247248 _perfAssump = earlyReturnNone (mkAssumpType , perfAssump )
@@ -269,7 +270,7 @@ def build_run_deal_req(self, run_type, deal, perfAssump=None, nonPerfAssump=[])
269270
270271
271272
272- def build_pool_req (self , pool , poolAssump , rateAssumps , isMultiScenario = False ) -> str :
273+ def build_pool_req (self , pool , poolAssump , rateAssumps , isMultiScenario = False , breakdown = False ) -> str :
273274 """build pool run request: (single run, multi-scenario run)
274275
275276 :meta private:
@@ -298,9 +299,9 @@ def buildPoolType(p) -> dict:
298299 return mkTag ((assetTag , mkPoolType (assetDate , _p , False )))
299300
300301 if not isMultiScenario :
301- r = mkTag ((RunReqType .SinglePool .value , [buildPoolType (pool ), mkAssumpType (poolAssump ), _rateAssump ]))
302+ r = mkTag ((RunReqType .SinglePool .value , [breakdown , buildPoolType (pool ), mkAssumpType (poolAssump ), _rateAssump ]))
302303 else :
303- r = mkTag ((RunReqType .MultiPoolScenarios .value , [buildPoolType (pool ), mapValsBy (poolAssump , mkAssumpType ), _rateAssump ]))
304+ r = mkTag ((RunReqType .MultiPoolScenarios .value , [breakdown , buildPoolType (pool ), mapValsBy (poolAssump , mkAssumpType ), _rateAssump ]))
304305
305306 return json .dumps (r , ensure_ascii = False )
306307
@@ -320,6 +321,7 @@ def run(self, deal,
320321 runAssump = [],
321322 read = True ,
322323 showWarning = True ,
324+ rtn = [],
323325 debug = False ) -> dict :
324326 """ run deal with pool and deal run assumptions
325327
@@ -348,7 +350,7 @@ def run(self, deal,
348350 url = f"{ self .url } /{ Endpoints .RunDeal .value } "
349351
350352 # construct request
351- req = self .build_run_deal_req ("Single" , deal , poolAssump , runAssump )
353+ req = self .build_run_deal_req ("Single" , deal , poolAssump , runAssump , rtn = rtn )
352354 if debug :
353355 return req
354356
@@ -418,19 +420,28 @@ def runByScenarios(self, deal,
418420 else :
419421 return result
420422
421- def read_single (self , pool_resp ) -> tuple :
423+ def read_single (self , breakdown , pool_resp ) -> tuple :
422424 """ read pool run response from engine and convert to dataframe
423425
424426 :param pool_resp: (pool raw cashflow, pool statistics)
425427 :type pool_resp: tuple
426428 :return: (pool Cashflow in dataFrame, pool statistics)
427429 :rtype: tuple
428430 """
429- (pool_flow , pool_bals ) = pool_resp
430- result = _read_cf (pool_flow ['contents' ][1 ], self .lang )
431- return (result , pool_bals )
432431
433- def runPoolByScenarios (self , pool , poolAssump , rateAssump = None , read = True , debug = False ) -> dict :
432+ ((pool_flow , pool_bals ), pool_breakdown_flow ) = pool_resp
433+ result = _read_cf (pool_flow ['contents' ][1 ], self .lang )
434+ if not breakdown :
435+ return {"flow" :result , "stat" :pool_bals }
436+ else :
437+ assert pool_breakdown_flow is not None , "Breakdown flow is None"
438+ assert len (pool_breakdown_flow )> 0 , "Breakdown flow is empty"
439+ return {"flow" :result , "stat" :pool_bals
440+ ,"breakdown" : [ {"flow" : _read_cf (_ [0 ]['contents' ][1 ], self .lang ), "stat" :_ [1 ]}
441+ for _ in pool_breakdown_flow ]
442+ }
443+
444+ def runPoolByScenarios (self , pool , poolAssump , rateAssump = None , read = True , breakdown = False ,debug = False ) -> dict :
434445 """ run a pool with multiple scenario ,return result as map , with key same to pool assumption map
435446
436447 :param pool: pool map
@@ -462,10 +473,10 @@ def runPoolByScenarios(self, pool, poolAssump, rateAssump=None, read=True, debug
462473 result = tz .valmap (lambda x :x ['Right' ] ,result )
463474
464475 if read :
465- return result & lens .Values ().Values ().modify (self .read_single )
476+ return result & lens .Values ().Values ().modify (partial ( self .read_single , breakdown ) )
466477 return result
467478
468- def runPool (self , pool , poolAssump = None , rateAssump = None , read = True , debug = False , ** kwargs ) -> tuple :
479+ def runPool (self , pool , poolAssump = None , rateAssump = None , read = True , debug = False , breakdown = False , ** kwargs ) -> tuple :
469480 """perform pool run with pool and rate assumptions
470481
471482 :param pool: a pool object
@@ -487,7 +498,7 @@ def runPool(self, pool, poolAssump=None, rateAssump=None, read=True, debug=False
487498
488499 url = f"{ self .url } /{ Endpoints .RunPool .value } "
489500
490- req = self .build_pool_req (pool , poolAssump , rateAssump , isMultiScenario = False )
501+ req = self .build_pool_req (pool , poolAssump , rateAssump , isMultiScenario = False , breakdown = breakdown )
491502
492503 if debug :
493504 return req
@@ -497,10 +508,11 @@ def runPool(self, pool, poolAssump=None, rateAssump=None, read=True, debug=False
497508 if result is None or 'error' in result or 'Left' in result :
498509 leftVal = result .get ("Left" ,"" )
499510 raise AbsboxError (f"❌ Failed to get response from run: { leftVal } " )
511+
500512 result = result ['Right' ]
501-
513+
502514 if read :
503- return result & lens .Values ().modify (self .read_single )
515+ return result & lens .Values ().modify (partial ( self .read_single , breakdown ) )
504516 else :
505517 return result
506518
0 commit comments