From f5bb3d31bdcec3f746cada5769ad4b4e619167cf Mon Sep 17 00:00:00 2001 From: yellowbean Date: Wed, 11 Dec 2024 19:00:58 +0800 Subject: [PATCH] update rpt; add new action change status --- absbox/__init__.py | 2 +- absbox/local/base.py | 3 +- absbox/local/cf.py | 13 +++++ absbox/local/component.py | 8 ++- absbox/report.py | 106 ++++++++++++++++++++++---------------- docs/source/modeling.rst | 19 ++++++- 6 files changed, 103 insertions(+), 48 deletions(-) diff --git a/absbox/__init__.py b/absbox/__init__.py index 6f87eb8..4f0e03b 100644 --- a/absbox/__init__.py +++ b/absbox/__init__.py @@ -15,7 +15,7 @@ from absbox.local.chart import viz from importlib.metadata import version from absbox.local.cf import readBondsCf,readToCf,readFeesCf,readAccsCf,readPoolsCf,readFlowsByScenarios,readMultiFlowsByScenarios,readFieldsByScenarios -from absbox.local.cf import readInspect, readLedgers +from absbox.local.cf import readInspect, readLedgers, readTriggers from absbox.report import toHtml,OutputType diff --git a/absbox/local/base.py b/absbox/local/base.py index 4848a6f..ac0be21 100644 --- a/absbox/local/base.py +++ b/absbox/local/base.py @@ -148,7 +148,8 @@ ,"累计违约余额":"HistoryDefaults" } -dealStatusLog = {'cn': [china_date, "旧状态", "新状态"], 'en': [english_date, "From", "To"]} +dealStatusLog = {'cn': [china_date, "旧状态", "新状态", "备注"] + ,'en': [english_date, "From", "To", "Comment"]} dealStatusMap = {"en": {'amort': "Amortizing", 'def': "Defaulted", 'acc': "Accelerated", 'end': "Ended", 'called': "Called", diff --git a/absbox/local/cf.py b/absbox/local/cf.py index 6f617a9..2f003e7 100644 --- a/absbox/local/cf.py +++ b/absbox/local/cf.py @@ -100,6 +100,8 @@ def patchMissingIndex(df,idx:set): return df.reindex(df.index.values.tolist()+list(missingIdx)).sort_index() def buildJointCf(m:dict, popColumns=[]) -> pd.DataFrame: + if len(m)==0: + return pd.DataFrame() fullColumns = list(m.values())[0].columns.to_list() columns = list(filter(lambda x: x not in set(popColumns), fullColumns)) accNames = list(m.keys()) @@ -142,6 +144,17 @@ def readPoolsCf(pMap) -> pd.DataFrame: df.columns = headerIndex return df +def readTriggers(tMap) -> pd.DataFrame: + ''' read a map of triggers in dataframes to a single dataframe, with key as 1st level index''' + if tMap == {} or tMap is None: + return pd.DataFrame() + t = tz.pipe({k:v for k,v in tMap.items() if v} + ,lambda x: tz.valmap(buildJointCf, x) + ,lambda x: tz.valfilter( lambda y: not y.empty ,x)) + tKeys = t.keys() + tVals = t.values() + return pd.concat(list(tVals), axis=1, keys=tuple(tKeys)) + def readInspect(r:dict) -> pd.DataFrame: """ read inspect result from waterfall and run assumption input , return a joined dataframe ordered by date""" diff --git a/absbox/local/component.py b/absbox/local/component.py index e36e850..30a900b 100644 --- a/absbox/local/component.py +++ b/absbox/local/component.py @@ -1046,6 +1046,10 @@ def mkMod(y: dict) -> tuple: ## Inspect case ["查看", comment, *ds] | ["inspect", comment, *ds]: return mkTag(("WatchVal", [comment, lmap(mkDs, ds)])) + case ["更改状态", p, st] | ["changeStatus", p, st]: + return mkTag(("ChangeStatus",[mkPre(p), mkStatus(st)])) + case ["更改状态", st] | ["changeStatus", st]: + return mkTag(("ChangeStatus",[None, mkStatus(st)])) case []: return mkTag(("Placeholder",[])) case _: @@ -2031,9 +2035,9 @@ def readRunSummary(x, locale) -> dict: r['bonds'] = bndSummary ## Build status change logs - status_change_logs = [(_['contents'][0], readStatus(_['contents'][1], locale), readStatus(_['contents'][2], locale)) + status_change_logs = [(_['contents'][0], readStatus(_['contents'][1], locale), readStatus(_['contents'][2], locale), _['contents'][3]) for _ in filter_by_tags(x, ["DealStatusChangeTo"])] - deal_ended_log = [ (_['contents'][0],"DealEnd",_['contents'][1]) for _ in filter_by_tags(x, ["EndRun"])] + deal_ended_log = [ (_['contents'][0],"","DealEnd",_['contents'][1]) for _ in filter_by_tags(x, ["EndRun"])] r['status'] = pd.DataFrame(data=status_change_logs+deal_ended_log, columns=dealStatusLog[locale]) # inspection variables diff --git a/absbox/report.py b/absbox/report.py index b1e26a1..ecbb61a 100644 --- a/absbox/report.py +++ b/absbox/report.py @@ -2,10 +2,10 @@ from lenses import lens import pandas as pd import json, enum, os, pathlib, re -from htpy import body, h1, head, html, li, title, ul, div, span, h3, h2, a +from htpy import body, h1, head, html, li, title, ul, div, span, h3, h2, a, h4,h5, h6 from markupsafe import Markup from absbox import readInspect -from absbox import readBondsCf,readFeesCf,readAccsCf,readPoolsCf,readLedgers +from absbox import readBondsCf,readFeesCf,readAccsCf,readPoolsCf,readLedgers,readTriggers class OutputType(int, enum.Enum): @@ -16,64 +16,84 @@ class OutputType(int, enum.Enum): Tabbed = 2 # TBD -def singleToMap(x,defaultName = "Consol")->dict: - if isinstance(x, dict): - return x - else: - return {defaultName: x} - -def mapToList(m:dict,anchor=False): +def mapToList(m:dict, title_=h3, anchor=False): m2 = {} if not anchor: - m2 = {h3[k]:div[Markup(v.to_html())] for k,v in m.items() } + m2 = {title_[k]:div[Markup(v.to_html())] for k,v in m.items() } else: - m2 = {h3(id=f"anchor-{anchor}-{k}")[k]:div[Markup(v.to_html())] + m2 = {title_(id=f"anchor-{anchor}-{k}")[k]:div[Markup(v.to_html())] for k,v in m.items() } return list(m2.items()) +def buildSection(lst:list, title_=(h2,h3), anchor=False): + return [ div[ title_[0](id=f"anchor-{_t}")[_t], mapToList(x, anchor=_t)] + for (_t, x) in lst] + +def buildSectionFlat(lst:list, title_=h2, anchor=False): + return [ div[ title_(id=f"anchor-{_t}")[_t],Markup(x.to_html())] + for (_t, x) in lst if x is not None] + def toHtml(r:dict, p:str, style=OutputType.Plain, debug=False): """ r : must be a result from "read=True" """ - dealName = r['_deal']['contents']['name'] - - poolDf = singleToMap(r['pool']['flow']) - accDf = r['accounts'] - feeDf = r['fees'] - bondDf = r['bonds'] - ledgerDf = r.get("ledgers",{}) - section1 = [ div[ h2(id=f"anchor-{_t}")[_t],mapToList(x,anchor=_t) ] - for (_t,x) in [("Pool",poolDf),("Fee",feeDf),("Bond",bondDf),("Accounts",accDf)] - ] - - section2 = [ div[ h2(id=f"anchor-{_t}")[_t],Markup(x.to_html()) ] - for (_t,x) in [("Status",r['result']['status']) - ,("Pricing",r['pricing']) + bondDf = ("Bond", tz.valfilter(lambda x: isinstance(x, pd.DataFrame), r['bonds'])) + bondGrpDf = ("BondGroup", + tz.pipe(tz.valfilter(lambda x: not isinstance(x, pd.DataFrame), r['bonds']) + ,lambda x : {f"{k}-{k2}":v2 for k,v in x.items() + for k2,v2 in v.items()} + ) + ) + + section0 = buildSection([bondDf,bondGrpDf]) + + # section 1 + poolDf = ("Pool", r['pool']['flow']) + accDf = ("Accounts", r['accounts']) + feeDf = ("Fee", r['fees']) + #section1 = [div[h2(id=f"anchor-{_t}")[_t], mapToList(x, anchor=_t)] + # for (_t, x) in [poolDf, feeDf, accDf]] + section1 = buildSection([poolDf, feeDf, accDf]) + + # section 2 + section2 = buildSectionFlat([("Status",r['result']['status']) + ,("Pricing",r["pricing"]) ,("Bond Summary",r['result']['bonds']) ,("Log",r['result']['logs']) ,("Waterfall",r['result']['waterfall']) - ,("Inspect",readInspect(r['result']))] - if x is not None] + ,("Inspect",readInspect(r['result']))]) + # section 3 + #section3 = [div[h2(id=f"anchor-{_t}")[_t], mapToList(x)] + # for (_t, x) in [("Finanical Reports", r['result'].get('report', {}))]] + section3 = buildSection([("Finanical Reports", r['result'].get('report', {}))]) - section3 = [ div[ h2(id=f"anchor-{_t}")[_t],mapToList(x) ] - for (_t,x) in [("Finanical Reports",r['result']['report'])]] - # read joint cashflows - seciont4 = [ div[ h2(id=f"anchor-{_t}")[_t],Markup(x.to_html()) ] - for (_t,x) in [("MultiFee",readFeesCf(feeDf)) - ,("MultiBond",readBondsCf(bondDf,popColumns=[])) - ,("MultiAccounts",readAccsCf(accDf)) - ,("MultiPools", readPoolsCf(poolDf)) - ,("MultiLedger", readLedgers(ledgerDf)) - - ] - ] + # section 4 + ledgerDf = ("Ledger", r.get("ledgers",{})) + section4 = buildSectionFlat([("MultiFee",readFeesCf(feeDf[1])) + ,("MultiBond",readBondsCf(bondDf[1],popColumns=[])) + ,("MultiAccounts",readAccsCf(accDf[1])) + ,("MultiPools", readPoolsCf(poolDf[1])) + ,("MultiLedger", readLedgers(ledgerDf[1])) + ,("MultiTrigger", readTriggers(r.get("triggers",{}))) + ]) + # section 5 + #triggerDf = ("Trigger", r.get("triggers",{})) + #section5 = [ div[ h2(id=f"anchor-{_t}")[_t] + # , [ div[ h3(id=f"anchor-{_t}-{k}")[k] + # #, div[">",list(v.items())] + # ,buildSection([ (j,k) for j,k in v.items()], title_=(h4,h5), anchor=f"{_t}-{k}") + # ] + # for k,v in x.items() if v is not None] + # ] + # for (_t, x) in [triggerDf] if x is not None] + dealName = r['_deal']['contents']['name'] c = html[ - head[ title[dealName]], - body[ section1 ,section2 ,section3, seciont4 ], + head[title[dealName]], + body[section0, section1, section2, section3 ,section4], ] if debug: @@ -82,13 +102,13 @@ def toHtml(r:dict, p:str, style=OutputType.Plain, debug=False): if style == OutputType.Anchor: links = c & lens._children[1]._children.Each().Each()._children[0]._attrs.Regex(r'(?<=id=").*(?=")').collect() c &= lens._children[1]._children.Each().Each()._children[0]._children.modify(lambda x:[x,a(href="#toc")[" ^Top"] ]) - + linksStr = [ l.lstrip("anchor-") for l in links ] hrefs = ul[ [li[a(href=f"#{l}")[f" < {lstr} > "]] for (lstr,l) in zip(linksStr,links) ]] c &= lens._children[1]._children.modify(lambda xs: tuple([div(id="toc")[div["Table Of Content"],hrefs]])+xs) - + absPath = None with open(p, 'wb') as f: f.write(c.encode()) diff --git a/docs/source/modeling.rst b/docs/source/modeling.rst index 0b02b03..f77699c 100644 --- a/docs/source/modeling.rst +++ b/docs/source/modeling.rst @@ -2854,6 +2854,23 @@ There are two types of `Conditional Action`, which are same in with "IF" / "IF-E ] +Change Deal Status +^^^^^^^^^^^^^^^^^^^^^^^ + +.. versionadded:: 0.40.6 + +``changeStatus`` + +This action will assign a new status to the deal, with optional a ``Condition``. + + +syntax + .. code-block:: python + + ["changeStatus",] + ["changeStatus",,] + + Inspect Variables during waterfall ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -3663,7 +3680,7 @@ Book Ledger * User can book a formula based value to ledger * query the ledger balance via formula ``("ledgerBalance","")`` - * it can be reference in all places in waterfall/trigger/accounts which are applicable to :ref:`` + * it can be reference in all places in waterfall/trigger/accounts which are applicable to :ref:`Formula` .. literalinclude:: deal_sample/book.py :language: python