diff --git a/absbox/client.py b/absbox/client.py index 07a5aef..5c27a1f 100644 --- a/absbox/client.py +++ b/absbox/client.py @@ -1,20 +1,23 @@ import json, datetime, pickle, re, urllib3, getpass, copy from importlib.metadata import version from json.decoder import JSONDecodeError -from dataclasses import dataclass,field +from dataclasses import dataclass, field import rich from rich.console import Console from rich.json import JSON import requests -from requests.exceptions import ConnectionError,ReadTimeout +from requests.exceptions import ConnectionError, ReadTimeout import pandas as pd from pyspecter import query -from absbox.local.util import mkTag, isDate, flat, guess_pool_locale, mapValsBy, guess_pool_flow_header, _read_cf, _read_asset_pricing, mergeStrWithDict, earlyReturnNone, searchByFst -from absbox.local.component import mkPool,mkAssumpType,mkNonPerfAssumps, mkPricingAssump,mkLiqMethod,mkAssetUnion,mkRateAssumption +from absbox.local.util import mkTag, isDate, flat, guess_pool_locale, mapValsBy, guess_pool_flow_header\ + , _read_cf, _read_asset_pricing, mergeStrWithDict\ + , earlyReturnNone, searchByFst +from absbox.local.component import mkPool, mkAssumpType, mkNonPerfAssumps, mkPricingAssump, mkLiqMethod\ + , mkAssetUnion, mkRateAssumption from absbox.local.base import * -from absbox.validation import valReq,valAssumption +from absbox.validation import valReq, valAssumption from absbox.local.china import SPV from absbox.local.generic import Generic @@ -61,15 +64,15 @@ def build_run_deal_req(self, run_type, deal, perfAssump=None, nonPerfAssump=[]) match run_type: case "Single" | "S": - _deal = deal.json if hasattr(deal,"json") else deal - _perfAssump = earlyReturnNone(mkAssumpType,perfAssump) + _deal = deal.json if hasattr(deal, "json") else deal + _perfAssump = earlyReturnNone(mkAssumpType, perfAssump) r = mkTag(("SingleRunReq",[_deal, _perfAssump, _nonPerfAssump])) case "MultiScenarios" | "MS": - _deal = deal.json if hasattr(deal,"json") else deal + _deal = deal.json if hasattr(deal, "json") else deal mAssump = mapValsBy(perfAssump, mkAssumpType) r = mkTag(("MultiScenarioRunReq",[_deal, mAssump, _nonPerfAssump])) case "MultiStructs" | "MD" : - mDeal = {k: v.json if hasattr(v,"json") else v for k,v in deal.items() } + mDeal = {k: v.json if hasattr(v, "json") else v for k, v in deal.items() } _perfAssump = mkAssumpType(perfAssump) r = mkTag(("MultiDealRunReq",[mDeal, _perfAssump, _nonPerfAssump])) case _: @@ -78,18 +81,13 @@ def build_run_deal_req(self, run_type, deal, perfAssump=None, nonPerfAssump=[]) def build_pool_req(self, pool, poolAssump, rateAssumps, read=None) -> str: r = None + _rateAssump = [mkRateAssumption(rateAssump) for rateAssump in rateAssumps] if rateAssumps else None if isinstance(poolAssump, tuple): r = mkTag(("SingleRunPoolReq" - ,[mkPool(pool) - ,mkAssumpType(poolAssump) - ,[mkRateAssumption(rateAssump) for rateAssump in rateAssumps] if rateAssumps else None] - )) + ,[mkPool(pool), mkAssumpType(poolAssump) ,_rateAssump])) elif isinstance(poolAssump, dict): r = mkTag(("MultiScenarioRunPoolReq" - ,[mkPool(pool) - ,mapValsBy(poolAssump, mkAssumpType) - ,[mkRateAssumption(rateAssump) for rateAssump in rateAssumps] if rateAssumps else None] - )) + ,[mkPool(pool) ,mapValsBy(poolAssump, mkAssumpType), _rateAssump])) else: raise RuntimeError("Error in build pool req") return json.dumps(r , ensure_ascii=False) @@ -99,11 +97,11 @@ def validate(self, _r) -> list: error, warning = valReq(_r) if warning: console.print(f"❕[bold yellow]Warning in model :{warning}") - if len(error)>0: + if len(error) > 0: console.print(f"❌[bold red]Error in model :{error}") - return False,error,warning + return False, error, warning else: - return True,error,warning + return True, error, warning def run(self, deal, poolAssump=None, @@ -138,11 +136,10 @@ def run(self, deal, rich.print_json(result) return None - #print out errors rawErrorMsg = [] if multi_run_flag: - _x = {k: [ f"❌[bold red]{_['contents']}" for _ in v[2] if _['tag'] == "ErrorMsg"] for k,v in result.items() } - rawErrorMsg = [ b for a in _x.values() for b in a ] + _x = {k: [f"❌[bold red]{_['contents']}" for _ in v[2] if _['tag'] == "ErrorMsg"] for k, v in result.items()} + rawErrorMsg = [ b for a in _x.values() for b in a ] else: rawErrorMsg = [ f"❌[bold red]{_['contents']}" for _ in result[2] if _['tag'] == "ErrorMsg"] @@ -161,7 +158,7 @@ def run(self, deal, def runPool(self, pool, poolAssump=None, rateAssump=None, read=True): def read_single(pool_resp): (pool_flow, pool_bals) = pool_resp - flow_header, idx, expandFlag = guess_pool_flow_header(pool_flow[0],pool_lang) + flow_header, idx, expandFlag = guess_pool_flow_header(pool_flow[0], pool_lang) try: if not expandFlag: result = pd.DataFrame([_['contents'] for _ in pool_flow], columns=flow_header) @@ -190,7 +187,7 @@ def read_single(pool_resp): return result def runStructs(self, deals, poolAssump=None, nonPoolAssump=None, read=True): - assert isinstance(deals, dict),f"Deals should be a dict but got {deals}" + assert isinstance(deals, dict), f"Deals should be a dict but got {deals}" url = f"{self.url}/runMultiDeals" _poolAssump = mkAssumpType(poolAssump) if poolAssump else None _nonPerfAssump = mkNonPerfAssumps({}, nonPoolAssump) @@ -198,7 +195,7 @@ def runStructs(self, deals, poolAssump=None, nonPoolAssump=None, read=True): ,[{k:v.json for k,v in deals.items()} ,_poolAssump ,_nonPerfAssump])) - ,ensure_ascii=False) + ,ensure_ascii=False) result = self._send_req(req, url) if read: @@ -235,14 +232,14 @@ def readResult(x): def loginLibrary(self, user, pw, **q): deal_library_url = q['deal_library']+"/token" - cred = {"user":user,"password":pw} + cred = {"user": user, "password": pw} r = self._send_req(json.dumps(cred), deal_library_url) if 'token' in r: console.print(f"✅[bold green] login successfully,{r['msg']}") self.token = r['token'] else: - if hasattr(self,'token'): - delattr(self,'token') + if hasattr(self, 'token'): + delattr(self, 'token') console.print(f"❌[bold red]Failed to login,{r['msg']}") return None @@ -253,43 +250,44 @@ def safeLogin(self, user, **q): except Exception as e: console.print(f"❌[bold red]{e}") - def queryLibrary(self,ks,**q): - if not hasattr(self,"token"): + def queryLibrary(self, ks, **q): + if not hasattr(self, "token"): console.print(f"❌[bold red] No token found , please call loginLibrary() to login") return deal_library_url = q['deal_library']+"/query" - d = {"bond_id": [k for k in ks] } - q = {"read":True} | q - result = self._send_req(json.dumps(d|q), deal_library_url,headers= {"Authorization":f"Bearer {self.token}"}) + d = {"bond_id": [k for k in ks]} + q = {"read": True} | q + result = self._send_req(json.dumps(d|q), deal_library_url, headers={"Authorization": f"Bearer {self.token}"}) console.print(f"✅[bold green] query success") - if q['read'] == True: + if q['read']: if 'data' in result: - return pd.DataFrame(result['data'],columns=result['header']) + return pd.DataFrame(result['data'], columns=result['header']) elif 'error' in result: - return pd.DataFrame([result["error"]],columns=["error"]) + return pd.DataFrame([result["error"]], columns=["error"]) else: return result - def listLibrary(self,**q): + def listLibrary(self, **q): deal_library_url = q['deal_library']+"/list" result = self._send_req(json.dumps(q), deal_library_url) console.print(f"✅[bold green]list success") if ('read' in q) and (q['read'] == True): - return pd.DataFrame(result['data'],columns=result['header']) + return pd.DataFrame(result['data'], columns=result['header']) else: return result - def runLibrary(self,_id,**p): + def runLibrary(self, _id, **p): deal_library_url = p['deal_library']+"/run" - read = p.get("read",True) - pricingAssump = p.get("pricing",None) - dealAssump = p.get("assump",None) - prod_flag = {"production":p.get("production",True)} - runReq = mergeStrWithDict (self.build_req(_id, dealAssump, pricingAssump), prod_flag ) - if not hasattr(self,"token"): + read = p.get("read", True) + pricingAssump = p.get("pricing", None) + dealAssump = p.get("assump", None) + prod_flag = {"production":p.get("production", True)} + runReq = mergeStrWithDict(self.build_req(_id, dealAssump, pricingAssump), prod_flag) + if not hasattr(self, "token"): console.print(f"❌[bold red] No token found , please call loginLibrary() to login") return result = self._send_req(runReq, deal_library_url, headers={"Authorization":f"Bearer {self.token}"}) + def lookupReader(x): match x: case "china.SPV": @@ -307,17 +305,17 @@ def lookupReader(x): return None try: classReader = lookupReader(p['reader']) - if read and isinstance(result,list): + if read and isinstance(result, list): return classReader.read(result) elif read and isinstance(result, dict): - return {k:classReader.read(v) for k,v in result.items()} + return {k: classReader.read(v) for k, v in result.items()} else: return result except Exception as e: console.print(f"❌[bold red]{e}") return None - def _send_req(self,_req,_url,timeout=10,headers={})->dict: + def _send_req(self, _req, _url: str, timeout=10, headers={})->dict: with console.status("") as status: try: hdrs = self.hdrs | headers diff --git a/absbox/local/component.py b/absbox/local/component.py index c945d81..4c9268c 100644 --- a/absbox/local/component.py +++ b/absbox/local/component.py @@ -643,20 +643,20 @@ def mkRateType(x): raise RuntimeError(f"Failed to match :{x}: Rate Type") -def mkBookType(x): +def mkBookType(x:list): match x: - case ["PDL",defaults,ledgers]: + case ["PDL", defaults, ledgers] | ["pdl", defaults, ledgers]: return mkTag(("PDL",[mkDs(defaults) ,[[ln,mkDs(ds)] for ln,ds in ledgers]])) - case ["AccountDraw", ledger]: - return mkTag(("ByAccountDraw",ledger)) - case ["ByFormula", ledger, ds]: - return mkTag(("ByDS", ledger, ds)) + #case ["AccountDraw", ledger] | ['accountDraw', ledger]: + # return mkTag(("ByAccountDraw",ledger)) + case ["ByFormula", ledger, dr, ds] | ['formula',ledger, dr, ds]: + return mkTag(("ByDS", [ledger, dr, mkDs(ds)])) case _: raise RuntimeError(f"Failed to match :{x}:mkBookType") -def mkSupport(x): +def mkSupport(x:list): match x: case ["account",accName,mBookType] | ["suppportAccount",accName,mBookType] | ["支持账户",accName,mBookType]: return mkTag(("SupportAccount",[accName,mkBookType(mBookType)])) @@ -671,7 +671,7 @@ def mkSupport(x): case _: raise RuntimeError(f"Failed to match :{x}:SupportType") -def mkAction(x): +def mkAction(x:list): ''' make waterfall actions ''' match x: case ["账户转移", source, target, m] | ["transfer", source, target, m]: @@ -760,7 +760,7 @@ def mkAction(x): raise RuntimeError(f"Failed to match :{x}:mkAction") -def mkStatus(x): +def mkStatus(x:tuple|str): match x: case "摊销" | "Amortizing": return mkTag(("Amortizing")) @@ -835,7 +835,7 @@ def _rateTypeDs(x): return False -def mkTrigger(x): +def mkTrigger(x:dict): match x: case {"condition":p,"effects":e,"status":st,"curable":c} | {"条件":p,"效果":e,"状态":st,"重置":c}: triggerName = getValWithKs(x,["name","名称"],defaultReturn="") @@ -1393,11 +1393,13 @@ def mkLiqProvider(n, x): def mkLedger(n, x): match x: + case {"balance":bal} | {"余额":bal}: + return {"ledgName":n,"ledgBalance":bal,"ledgStmt":None} case {"balance":bal,"txn":_tx} | {"余额":bal,"记录":_tx}: tx = mkAccTxn(_tx) return {"ledgName":n,"ledgBalance":bal,"ledgStmt":tx} case _: - raise RuntimeError(f"Failed to match Ledger:{x}") + raise RuntimeError(f"Failed to match Ledger:{n},{x}") def mkCf(x): if len(x) == 0: @@ -1410,7 +1412,7 @@ def mkCollection(x): match x : case [s,acc] if isinstance(acc, str): return mkTag(("Collect",[mkPoolSource(s),acc])) - case [s,pcts] if isinstance(pcts, list): + case [s,*pcts] if isinstance(pcts, list): return mkTag(("CollectByPct" ,[mkPoolSource(s) ,pcts])) case _: raise RuntimeError(f"Failed to match collection rule {x}") diff --git a/absbox/local/generic.py b/absbox/local/generic.py index f1d8c76..89188e3 100644 --- a/absbox/local/generic.py +++ b/absbox/local/generic.py @@ -2,7 +2,8 @@ import functools from absbox import * -from absbox.local.util import mkTag,mapListValBy,mapValsBy,renameKs2,guess_pool_flow_header,positionFlow,mapNone +from absbox.local.util import mkTag,mapListValBy,mapValsBy,renameKs2\ + ,guess_pool_flow_header,positionFlow,mapNone from absbox.local.component import * from absbox.local.base import * import pandas as pd diff --git a/absbox/local/util.py b/absbox/local/util.py index 2beba2a..f1be3a6 100644 --- a/absbox/local/util.py +++ b/absbox/local/util.py @@ -226,15 +226,15 @@ def renameKs(m:dict,mapping,opt_key=False): del m[o] return m -def subMap(m:dict,ks:list): +def subMap(m:dict, ks:list): ''' get a map subset by keys,if keys not found, supplied with default value ''' return {k:m.get(k,defaultVal) for (k,defaultVal) in ks} -def subMap2(m:dict,ks:list): - fieldNames = [ fName for (fName,fTargetName,fDefaultValue) in ks] - _m = {k:m.get(k,defaultVal) for (k,_,defaultVal) in ks} - _mapping = [ _[:2] for _ in ks ] - return renameKs(_m,_mapping) +def subMap2(m:dict, ks:list): + #fieldNames = [ fName for (fName, fTargetName, fDefaultValue) in ks] + _m = {k: m.get(k, defaultVal) for (k, _, defaultVal) in ks} + _mapping = [_[:2] for _ in ks] + return renameKs(_m, _mapping) def mapValsBy(m:dict, f): ''' Given a map and apply function to every vals''' @@ -247,25 +247,25 @@ def mapListValBy(m:dict, f): return {k: [f(_v) for _v in v] for k,v in m.items()} def applyFnToKey(m:dict, f, k, applyNone=False): - assert isinstance(m, dict),f"{m} is not a map" + assert isinstance(m, dict), f"{m} is not a map" assert k in m, f"{k} is not in map {m}" - match (m[k],applyNone): - case (None,True): + match (m[k], applyNone): + case (None, True): m[k] = f(m[k]) - case (None,False): + case (None, False): pass case (_, _): m[k] = f(m[k]) return m -def renameKs2(m:dict,kmapping): +def renameKs2(m:dict, kmapping): ''' Given a map, rename ks from a key-mapping ''' assert isinstance(m, dict), "M is not a map" assert isinstance(kmapping, dict), f"Mapping is not a map: {kmapping}" assert set(m.keys()).issubset(set(kmapping.keys())), f"{m.keys()} not in {kmapping.keys()}" return {kmapping[k]: v for k, v in m.items()} -def ensure100(xs,msg=""): +def ensure100(xs, msg=""): assert sum(xs)==1.0, f"Doesn't not sum up 100%: {msg}" def guess_pool_flow_header(x, l): diff --git a/absbox/validation.py b/absbox/validation.py index 322350f..bae1bda 100644 --- a/absbox/validation.py +++ b/absbox/validation.py @@ -87,7 +87,7 @@ def validateAction(action): if d['status']['tag'] != 'PreClosing': error.append(f"Deal Date is preClosing, but status is not PreClosing") - return (error,warning) + return (error, warning) def valReq(reqSent) -> list: error = []