Skip to content

Commit

Permalink
expose book ledger
Browse files Browse the repository at this point in the history
  • Loading branch information
yellowbean committed Nov 9, 2023
1 parent 3b63913 commit 781fbca
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 75 deletions.
96 changes: 47 additions & 49 deletions absbox/client.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 _:
Expand All @@ -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)
Expand All @@ -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,
Expand Down Expand Up @@ -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"]

Expand All @@ -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)
Expand Down Expand Up @@ -190,15 +187,15 @@ 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)
req = json.dumps(mkTag(("MultiDealRunReq"
,[{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:
Expand Down Expand Up @@ -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

Expand All @@ -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":
Expand All @@ -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
Expand Down
26 changes: 14 additions & 12 deletions absbox/local/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)]))
Expand All @@ -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]:
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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="")
Expand Down Expand Up @@ -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:
Expand All @@ -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}")
Expand Down
3 changes: 2 additions & 1 deletion absbox/local/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 12 additions & 12 deletions absbox/local/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'''
Expand All @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion absbox/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down

0 comments on commit 781fbca

Please sign in to comment.