Skip to content

Commit 3d1ee60

Browse files
committed
expose Report & Fix multi-run
1 parent 2c56517 commit 3d1ee60

File tree

5 files changed

+135
-12
lines changed

5 files changed

+135
-12
lines changed

absbox/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
from absbox.local.cf import readBondsCf,readToCf,readFeesCf,readAccsCf,readPoolsCf,readFlowsByScenarios,readMultiFlowsByScenarios,readFieldsByScenarios
1818
from absbox.local.cf import readInspect
1919

20+
from absbox.report import toHtml,OutputType
21+
2022
import absbox.examples as examples
2123

22-
__version__ = version("absbox")
24+
__version__ = version("absbox")

absbox/client.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ def run(self, deal,
375375
if result is None or 'error' in result or 'Left' in result:
376376
leftVal = result.get("Left","")
377377
raise AbsboxError(f"❌{MsgColor.Error.value}Failed to get response from run: {leftVal}")
378+
print(result)
378379
result = result['Right']
379380
rawWarnMsg = map( lambda x:f"{MsgColor.Warning.value}{x['contents']}", filter_by_tags(result[RunResp.LogResp.value], enumVals(ValidationMsg)))
380381
if rawWarnMsg and showWarning:
@@ -420,10 +421,11 @@ def runByScenarios(self, deal,
420421
else:
421422
result = self._send_req(req, url, timeout=30)
422423

423-
if result is None or 'error' in result or 'Left' in result:
424+
if result is None or 'error' in result or "Left" in set(tz.concat([ _.keys() for _ in result.values()])):
424425
leftVal = result.get("Left","")
425426
raise AbsboxError(f"❌{MsgColor.Error.value}Failed to get response from run: {leftVal}")
426-
result = result['Right']
427+
428+
result = tz.valmap(lambda x:x['Right'] ,result)
427429

428430
rawWarnMsgByScen = {k: [f"{MsgColor.Warning.value}{_['contents']}" for _ in filter_by_tags(v[RunResp.LogResp.value], enumVals(ValidationMsg))] for k, v in result.items()}
429431
rawWarnMsg = list(tz.concat(rawWarnMsgByScen.values()))
@@ -474,10 +476,11 @@ def runPoolByScenarios(self, pool, poolAssump, rateAssump=None, read=True, debug
474476

475477
result = self._send_req(req, url)
476478

477-
if result is None or 'error' in result or 'Left' in result:
479+
if result is None or 'error' in result or "Left" in set(tz.concat([ _.keys() for _ in result.values()])):
478480
leftVal = result.get("Left","")
479481
raise AbsboxError(f"❌{MsgColor.Error.value}Failed to get response from run: {leftVal}")
480-
result = result['Right']
482+
483+
result = tz.valmap(lambda x:x['Right'] ,result)
481484

482485
if read:
483486
return result & lens.Values().Values().modify(self.read_single)
@@ -555,10 +558,11 @@ def runStructs(self, deals, poolAssump=None, nonPoolAssump=None, runAssump=None,
555558
return req
556559
result = self._send_req(req, url)
557560

558-
if result is None or 'error' in result or 'Left' in result:
561+
if result is None or 'error' in result or "Left" in set(tz.concat([ _.keys() for _ in result.values()])):
559562
leftVal = result.get("Left","")
560563
raise AbsboxError(f"❌{MsgColor.Error.value}Failed to get response from run: {leftVal}")
561-
result = result['Right']
564+
565+
result = tz.valmap(lambda x:x['Right'] ,result)
562566

563567
if read:
564568
return {k: deals[k].read(v) for k, v in result.items()}
@@ -597,10 +601,11 @@ def runByDealScenarios(self, deal,
597601

598602
result = self._send_req(req, url, timeout=30)
599603

600-
if result is None or 'error' in result or 'Left' in result:
601-
leftVal = result.get("Left","")
604+
if result is None or 'error' in result or "Left" in set(tz.concat([ _.keys() for _ in result.values()])):
605+
602606
raise AbsboxError(f"❌{MsgColor.Error.value}Failed to get response from run: {leftVal}")
603-
result = result['Right']
607+
608+
result = tz.valmap(lambda x:x['Right'] ,result)
604609

605610
rawWarnMsgByScen = {k: [f"{MsgColor.Warning.value}{_['contents']}" for _ in filter_by_tags(v[RunResp.LogResp.value], enumVals(ValidationMsg))] for k, v in result.items()}
606611
rawWarnMsg = list(tz.concat(rawWarnMsgByScen.values()))
@@ -649,10 +654,11 @@ def runByCombo(self,
649654

650655
result = self._send_req(req, url, timeout=30)
651656

652-
if result is None or 'error' in result or 'Left' in result:
657+
if result is None or 'error' in result or "Left" in set(tz.concat([ _.keys() for _ in result.values()])):
653658
leftVal = result.get("Left","")
654659
raise AbsboxError(f"❌{MsgColor.Error.value}Failed to get response from run: {leftVal}")
655-
result = result['Right']
660+
661+
result = tz.valmap(lambda x:x['Right'] ,result)
656662

657663
assert isinstance(result, dict), f"Result should be a dict but got {type(result)}, {result}"
658664

absbox/local/component.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,8 @@ def mkMod(y: dict) -> tuple:
10441044
## Inspect
10451045
case ["查看", comment, *ds] | ["inspect", comment, *ds]:
10461046
return mkTag(("WatchVal", [comment, lmap(mkDs, ds)]))
1047+
case []:
1048+
return mkTag(("Placeholder",[]))
10471049
case _:
10481050
raise RuntimeError(f"Failed to match :{x}:mkAction")
10491051

absbox/report.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import toolz as tz
2+
from lenses import lens
3+
import pandas as pd
4+
import json, enum, os, pathlib, re
5+
from htpy import body, h1, head, html, li, title, ul, div, span, h3, h2, a
6+
from markupsafe import Markup
7+
from absbox import readInspect
8+
from absbox import readBondsCf,readFeesCf,readAccsCf
9+
10+
11+
class OutputType(int, enum.Enum):
12+
"""Internal
13+
"""
14+
Plain = 0 # print cashflow to html tables
15+
Anchor = 1 # print cashflow with book mark
16+
Tabbed = 2 # TBD
17+
18+
19+
def singleToMap(x,defaultName = "Consol")->dict:
20+
if isinstance(x, dict):
21+
return x
22+
else:
23+
return {defaultName: x}
24+
25+
def mapToList(m:dict,anchor=False):
26+
m2 = {}
27+
if not anchor:
28+
m2 = {h3[k]:div[Markup(v.to_html())]
29+
for k,v in m.items() }
30+
else:
31+
m2 = {h3(id=f"anchor-{anchor}-{k}")[k]:div[Markup(v.to_html())]
32+
for k,v in m.items() }
33+
34+
return list(m2.items())
35+
36+
def toHtml(r:dict, p:str, style=OutputType.Plain, debug=False):
37+
"""
38+
r : must be a result from "read=True"
39+
"""
40+
dealName = r['_deal']['contents']['name']
41+
42+
poolDf = singleToMap(r['pool']['flow'])
43+
accDf = r['accounts']
44+
feeDf = r['fees']
45+
bondDf = r['bonds']
46+
47+
section1 = [ div[ h2(id=f"anchor-{_t}")[_t],mapToList(x,anchor=_t) ]
48+
for (_t,x) in [("Pool",poolDf),("Fee",feeDf),("Bond",bondDf),("Accounts",accDf)]
49+
]
50+
51+
section2 = [ div[ h2(id=f"anchor-{_t}")[_t],Markup(x.to_html()) ]
52+
for (_t,x) in [("Status",r['result']['status'])
53+
,("Pricing",r['pricing'])
54+
,("Bond Summary",r['result']['bonds'])
55+
,("Log",r['result']['logs'])
56+
,("Waterfall",r['result']['waterfall'])
57+
,("Inspect",readInspect(r['result']))]
58+
if x is not None]
59+
60+
section3 = [ div[ h2(id=f"anchor-{_t}")[_t],mapToList(x) ]
61+
for (_t,x) in [("Finanical Reports",r['result']['report'])]]
62+
63+
# read joint cashflows
64+
seciont4 = [ div[ h2(id=f"anchor-{_t}")[_t],Markup(x.to_html()) ]
65+
for (_t,x) in [("MultiFee",readFeesCf(feeDf)),("MultiBond",readBondsCf(bondDf,popColumns=[])),("MultiAccounts",readAccsCf(accDf))]
66+
]
67+
68+
c = html[
69+
head[ title[dealName]],
70+
body[ section1 ,section2 ,section3, seciont4 ],
71+
]
72+
73+
if debug:
74+
return c
75+
76+
if style == OutputType.Anchor:
77+
links = c & lens._children[1]._children.Each().Each()._children[0]._attrs.Regex(r'(?<=id=").*(?=")').collect()
78+
c &= lens._children[1]._children.Each().Each()._children[0]._children.modify(lambda x:[x,a(href="#toc")[" ^Top"] ])
79+
80+
linksStr = [ l.lstrip("anchor-") for l in links ]
81+
hrefs = ul[ [li[a(href=f"#{l}")[f" < {lstr} > "]] for (lstr,l) in zip(linksStr,links) ]]
82+
83+
84+
c &= lens._children[1]._children.modify(lambda xs: tuple([div(id="toc")[div["Table Of Content"],hrefs]])+xs)
85+
86+
with open(p, 'wb') as f:
87+
f.write(c.encode())

docs/source/analytics.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,6 +1353,7 @@ A `result` is returned by a `run()` call which has two components:
13531353
13541354
* :ref:`Cashflow Results`
13551355
* :ref:`Non-Cashflow Results`
1356+
* :ref:`All-In-One HTML report`
13561357
13571358
13581359
@@ -1599,6 +1600,31 @@ There are two types of validation message
15991600
r['result']['logs']
16001601
16011602
1603+
All-In-One HTML report
1604+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
1605+
.. versionadded:: 0.40.2
1606+
1607+
In a single deal cashflow run, if user get a result object via a `read=True`, then there is a candy function `toHtml()` will help to dump all cashflow and summaries to a single HTML file.
1608+
1609+
With a simple table of content , user can easily navigate to components of interset to inspect with.
1610+
1611+
1612+
.. code-block:: python
1613+
1614+
from absbox import toHtml,OutputType
1615+
1616+
toHtml(r,"testOutHtml.html")
1617+
1618+
toHtml(r,"testOutHtml.html",style=OutputType.Anchor)
1619+
1620+
1621+
If user are running with multi-scenario , just supply with a key
1622+
1623+
1624+
.. code-block:: python
1625+
1626+
toHtml(r['scen01'],"testOutHtml.html",style=OutputType.Anchor)
1627+
16021628
16031629
Sensitivity Analysis
16041630
----------------------

0 commit comments

Comments
 (0)