Skip to content

Commit 40352bd

Browse files
committed
expose new compare function for two result
1 parent 16a9038 commit 40352bd

File tree

5 files changed

+121
-41
lines changed

5 files changed

+121
-41
lines changed

absbox/client.py

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
from requests.exceptions import ConnectionError, ReadTimeout
99
import pandas as pd
1010
from rich.console import Console
11+
from rich import print_json
1112
from schema import Schema
1213
import toolz as tz
1314
from lenses import lens
1415

15-
1616
from absbox.validation import isValidUrl, vStr
1717
from absbox.local.util import mkTag,mapValsBy \
1818
, _read_cf, _read_asset_pricing, mergeStrWithDict \
@@ -103,8 +103,9 @@ class LibraryEndpoints(str, enum.Enum):
103103
Ack = "ack"
104104
Token = "token"
105105
Query = "query"
106-
Run = "run"
107-
DealAdd = "deal/add"
106+
Run = "run" # run a deal from the library
107+
Add = "add" # add new deal to library
108+
Get = "get" # get deal from library
108109
# Data = "data"
109110
# DataList = "data/list"
110111
# DealFetch = "deal/fetch"
@@ -888,7 +889,9 @@ def __post_init__(self):
888889
self.session = requests.Session()
889890
self.libraryInfo = json.loads(_r.text)
890891
console.print(f"✅ Connected to library server")
891-
console.print(f"✅ absbox version:{self.libraryInfo['absbox']}, Hastructure:{self.libraryInfo['Hastructure']}")
892+
#print_json(data=self.libraryInfo['Hastructure'])
893+
console.print(f"absbox version:{self.libraryInfo['absbox']}")
894+
console.print(f"Hastructure:{self.libraryInfo['Hastructure']}")
892895
else:
893896
console.print(f"❌ Failed to connect to library server")
894897

@@ -910,10 +913,8 @@ def login(self, user, pw, **q):
910913
cred = {"user": vStr(user), "password": pw}
911914
r = self._send_req(json.dumps(cred), deal_library_url)
912915
if 'token' in r:
913-
console.print(f"✅ login successfully, user-> {r['user']},company-> {r['group']}")
914-
#console.print("✅ token is updated",r['token'])
916+
console.print(f"✅ login successfully, user -> {r['user']},group -> {r['group']}")
915917
self.token = r['token']
916-
#self._send_req(json.dumps({"token": self.token}), deal_library_url)
917918
else:
918919
if hasattr(self, 'token'):
919920
delattr(self, 'token')
@@ -968,7 +969,7 @@ def query(self, k = {}, read=True):
968969
else:
969970
return result
970971

971-
def dealAdd(self, d, **p):
972+
def add(self, d, **p):
972973
"""add deal to library"""
973974

974975
if not hasattr(self, "token"):
@@ -979,9 +980,8 @@ def dealAdd(self, d, **p):
979980
"deal":d
980981
,"name": d.name
981982
,"json": d.json
982-
,"extra": p.get("extra",{})
983983
,"period": p.get("period",0)
984-
,"buildVersion": absbox.__version__
984+
,"buildVersion": "0.43.1"
985985
,"stage": p.get("stage","")
986986
,"comment": p.get("comment","")
987987
,"permission": p.get("permission","700")
@@ -993,9 +993,22 @@ def dealAdd(self, d, **p):
993993
r = self._send_req(bData, deal_library_url
994994
, headers={"Authorization": f"Bearer {self.token}"
995995
,"Content-Type":"application/octet-stream"})
996+
997+
996998

997-
console.print(f"✅ add success")
999+
console.print(f"✅ add success with deal id={r['dealId']}, name={r['name']}")
9981000

1001+
def dealGet(self, q):
1002+
if not hasattr(self, "token"):
1003+
raise AbsboxError(f"❌ No token found, please call login() to login")
1004+
deal_library_url = self.url+f"/{LibraryEndpoints.DealGet.value}"
1005+
1006+
r = self._send_req(pickle.dumps({"q":q}), deal_library_url
1007+
, headers={"Authorization": f"Bearer {self.token}"
1008+
,"Content-Type":"application/octet-stream"})
1009+
1010+
return r
1011+
console.print(f"✅ get deal success")
9991012

10001013
def run(self, _id, **p):
10011014
"""send deal id with assumptions to remote server and get result back
@@ -1021,8 +1034,8 @@ def run(self, _id, **p):
10211034
runType = p.get("runType", "S")
10221035

10231036
runReq = {"q": _id, "runType": runType
1024-
,"runAssump": runAssump, "poolAssump": poolAssump
1025-
,"engine": p.get("engine",None) }
1037+
,"runAssump": runAssump, "poolAssump": poolAssump
1038+
,"engine": p.get("engine",None) }
10261039

10271040
bRunReq = pickle.dumps(runReq)
10281041

@@ -1032,16 +1045,17 @@ def run(self, _id, **p):
10321045

10331046
try:
10341047
result = r['result']
1048+
runInfo = tz.dissoc(r, 'result')
10351049
console.print(f"✅ run success with deal")
10361050
except Exception as e:
10371051
raise AbsboxError(f"❌ message from API server:{result},\n,{e}")
10381052
try:
10391053
if read and isinstance(result, list):
1040-
return Generic.read(result)
1054+
return (runInfo, Generic.read(result))
10411055
elif read and isinstance(result, dict):
1042-
return tz.valmap(Generic.read, result)
1056+
return (runInfo, tz.valmap(Generic.read, result))
10431057
else:
1044-
return result
1058+
return (runInfo, result)
10451059
except Exception as e:
10461060
raise AbsboxError(f"❌ Failed to read result with error = {e}")
10471061

absbox/local/base.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -111,17 +111,11 @@
111111
boolLikeFormula = set(["trigger", "事件", "isMostSenior", "最优先", "isPaidOff","清偿完毕","rateTest","allTest","anyTest","比率测试","任一测试","所有测试"
112112
"isOutstanding"])
113113

114-
bookDirection = {"Credit":"Credit"
115-
,"Debit":"Debit"
116-
,"借方":"Debit"
117-
,"贷方":"Credit"
118-
,"D":"Debit"
119-
,"C":"Credit"
120-
,"DR":"Debit"
121-
,"CR":"Credit"
122-
,"Dr":"Debit"
123-
,"Cr":"Credit"
124-
}
114+
bookDirection = {"Credit":"Credit" ,"Debit":"Debit"
115+
,"借方":"Debit" ,"贷方":"Credit"
116+
,"D":"Debit" ,"C":"Credit"
117+
,"DR":"Debit" ,"CR":"Credit"
118+
,"Dr":"Debit" ,"Cr":"Credit"}
125119

126120

127121
op_map = {">": "G", ">=": "GE", "<": "L", "<=": "LE", "=": "E"}

absbox/local/cmp.py

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,83 @@
11
import pandas as pd
22

33

4-
def comp_df(x, y):
5-
return pd.merge(x, y, on="日期", how='outer').sort_index().sort_index(axis=1)
64

7-
def comp_engines(engine1, engine2, d, pA=None,rA=[], a=None):
8-
9-
r1 = engine1.run(d, poolAssump=pA, runAssump=rA, read=True)
10-
r2 = engine2.run(d, poolAssump=pA, runAssump=rA, read=True)
5+
def compResult(r1:dict, r2:dict, names=("Left", "Right")):
6+
""" compare first to second result
7+
8+
:param r1: run result 1
9+
:type r1: dict
10+
:param r2: run result 2 ( to be compared with r1)
11+
:type r2: dict
12+
:param names: name of the results, defaults to ("Left", "Right")
13+
:type names: tuple, optional
14+
"""
1115

16+
def compDf(x, y):
17+
(nameA,nameB) = names
18+
x, y = x.align(y, fill_value=pd.NA)
19+
cmp = x.compare(y, result_names=(nameA,nameB))
20+
result = cmp.copy()
21+
for column in cmp.columns.levels[0]:
22+
if pd.api.types.is_numeric_dtype(cmp[column][nameA]) and pd.api.types.is_numeric_dtype(cmp[column][nameB]):
23+
result[column, 'diff'] = cmp[column][nameA].fillna(0) - cmp[column][nameB].fillna(0)
24+
#return result.sort_index(axis=1,level=1)
25+
return result.reindex(axis=1, level=1, labels=[nameA, nameB, 'diff'])
26+
27+
# r1 -> Left
28+
# r2 -> Right
1229
comp_result = {}
1330
# pool check
14-
if not r1['pool']['flow'].equals(r2['pool']['flow']):
15-
comp_result['pool'] = comp_df(r1['pool']['flow'], r2['pool']['flow'])
16-
else:
17-
comp_result['pool'] = True
31+
comp_result['pool'] = {}
32+
for k,v in r1['pool']['flow'].items():
33+
if not v.equals(r2['pool']['flow'][k]):
34+
comp_result['pool'][k] = compDf(v, r2['pool']['flow'][k])
35+
else:
36+
comp_result['pool'] = True
1837

1938
# expense check
2039
comp_result['fee'] = {}
2140
for fn, f in r1['fees'].items():
2241
if not f.equals(r2['fees'][fn]):
23-
comp_result['fee'][fn] = comp_df(f, r2['fees'][fn])
42+
comp_result['fee'][fn] = compDf(f, r2['fees'][fn])
2443
else:
2544
comp_result['fee'][fn] = True
2645

2746
# bond check
2847
comp_result['bond'] = {}
2948
for fn, f in r1['bonds'].items():
3049
if not f.equals(r2['bonds'][fn]):
31-
comp_result['bond'][fn] = comp_df(f, r2['bonds'][fn])
50+
comp_result['bond'][fn] = compDf(f, r2['bonds'][fn])
3251
else:
3352
comp_result['bond'][fn] = True
3453

35-
3654
# account check
3755
comp_result['account'] = {}
3856
for fn, f in r1['accounts'].items():
3957
if not f.equals(r2['accounts'][fn]):
40-
comp_result['account'][fn] = comp_df(f, r2['accounts'][fn])
58+
comp_result['account'][fn] = compDf(f, r2['accounts'][fn])
4159
else:
4260
comp_result['account'][fn] = True
4361

62+
# ledger check
63+
comp_result['ledger'] = {}
64+
if 'ledgers' in r1:
65+
for fn, f in r1['ledgers'].items():
66+
if not f.equals(r2['ledgers'][fn]):
67+
comp_result['ledger'][fn] = compDf(f, r2['ledgers'][fn])
68+
else:
69+
comp_result['ledger'][fn] = True
70+
71+
comp_result['trigger'] = {}
72+
if 'triggers' in r1:
73+
for locName, tMap in r1['triggers'].items() :
74+
if len(tMap) == 0:
75+
continue
76+
comp_result['trigger'][locName] = {}
77+
for tn, t in tMap.items():
78+
if not t.equals(r2['triggers'][locName][tn]):
79+
comp_result['trigger'][locName][tn] = compDf(t, r2['triggers'][locName][tn])
80+
else:
81+
comp_result['trigger'][locName][tn] = True
82+
4483
return comp_result

docs/source/analytics.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2150,3 +2150,34 @@ Built-in Comparision functions
21502150
.. seealso::
21512151
There are couple built-in functions will help user to get result in easier way :ref:`Read Multiple Result Map`
21522152
2153+
2154+
2155+
Compare two results
2156+
""""""""""""""""""""""""
2157+
2158+
.. versionadded:: 0.43.1
2159+
2160+
User can compare two results with a built-in function ``compResult`` , it will return a dict with difference of two results.
2161+
2162+
User can inspect difference as below:
2163+
2164+
.. code-block:: python
2165+
2166+
from absbox import API,EnginePath,compResult
2167+
2168+
r1 = localAPI.run(<Deal1>
2169+
,poolAssump = ("Pool",("Mortgage",{"CDR":0.12},None,None,None)
2170+
,None
2171+
,None)
2172+
,read=True)
2173+
2174+
2175+
r2 = localAPI.run(<Deal1>
2176+
,poolAssump = ("Pool",("Mortgage",{"CDR":0.11},None,None,None)
2177+
,None
2178+
,None)
2179+
,read=True)
2180+
2181+
diff = compResult(r1,r2,names=("highCDR","lowCDR"))
2182+
2183+
diff['bond']['senior'][['balance','cash']]

docs/source/modeling.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2124,6 +2124,8 @@ The bond interest will be calculated by a base from a :ref:`Formula`
21242124
It use a composite syntax: ``("byRefBalance",<formula>,<rate object>)``
21252125
21262126
syntax
2127+
:code:`"rateType":("refBalance",("*",("bondBalance","A","B"), 0.3) ,{"Fixed":0.06})`
2128+
21272129
:code:`"rateType":("byRefBalance",("poolBalance",),("fix",0.0569))`
21282130
21292131

0 commit comments

Comments
 (0)