1414import logging
1515import toolz as tz
1616from lenses import lens
17-
1817import pandas as pd
1918
2019numVal = Or (float ,int )
@@ -48,7 +47,7 @@ def mkDatePattern(x):
4847 return mkTag (("EveryNMonth" , [vDate (d ), vInt (n )]))
4948 case ["Weekday" , n ] if n >= 0 and n <= 6 :
5049 return mkTag (("Weekday" , vInt (n )))
51- case ["All" , * _dps ] | ["AllDatePattern" , * _dps ] | ["+" , * _dps ]:
50+ case ["all" , * _dps ] | [ " All" , * _dps ] | ["AllDatePattern" , * _dps ] | ["+" , * _dps ]:
5251 return mkTag (("AllDatePattern" , lmap (mkDatePattern , _dps )))
5352 case [">" , _d , dp ] | ["After" , _d , dp ] | ["之后" , _d , dp ]:
5453 return mkTag (("StartsAt" , ["Exc" , vDate (_d ), mkDatePattern (dp ) ]))
@@ -450,12 +449,14 @@ def queryType(y):
450449
451450def mkAccInt (x ):
452451 match x :
453- case {"周期" : _dp , "利率" : idx , "利差" : spd , "最近结息日" : lsd } \
454- | {"period" : _dp , "index" : idx , "spread" : spd , "lastSettleDate" : lsd }:
455- return mkTag (("InvestmentAccount" , [idx , spd , lsd , mkDatePattern (_dp )]))
452+ case {"周期" : _dp , "重置周期" :_dp2 , "参考利率" : idx , "利差" : spd , "最近结息日" : lsd ,"利率" : r } \
453+ | {"period" : _dp ,"reset" : _dp2 , "index" : idx , "spread" : spd , "lastSettleDate" : lsd
454+ ,"rate" : r }:
455+ return mkTag (("InvestmentAccount" , [idx , spd , mkDatePattern (_dp )
456+ ,mkDatePattern (_dp2 ), lsd , r ]))
456457 case {"周期" : _dp , "利率" : br , "最近结息日" : lsd } \
457458 | {"period" : _dp , "rate" : br , "lastSettleDate" : lsd }:
458- return mkTag (("BankAccount" , [br , lsd , mkDatePattern (_dp )]))
459+ return mkTag (("BankAccount" , [br , mkDatePattern (_dp ), lsd ]))
459460 case None :
460461 return None
461462 case _:
@@ -517,7 +518,10 @@ def mkAcc(an, x=None):
517518 return {"accBalance" : vNum (b ), "accName" : vStr (an ), "accType" : mkAccType (t ), "accInterest" : mkAccInt (i ), "accStmt" : mkAccTxn (tx )}
518519
519520 case {"余额" : b } | {"balance" : b }:
520- return mkAcc (vStr (an ), x | {"计息" : x .get ("计息" , None ), "interest" : x .get ("interest" , None ), "记录" : x .get ("记录" , None ), "txn" : x .get ("txn" , None ), "类型" : x .get ("类型" , None ), "type" : x .get ("type" , None )})
521+ extraFields = [ (_ , None ) for _ in ["计息" ,"interest" ,"类型" ,"type" ,"记录" ,"txn" ] ]
522+ extraInfo = subMap (x , extraFields )
523+ # extraInfo = {"计息": x.get("计息", None), "interest": x.get("interest", None), "记录": x.get("记录", None), "txn": x.get("txn", None), "类型": x.get("类型", None), "type": x.get("type", None)}
524+ return mkAcc (vStr (an ), x | extraInfo )
521525 case None :
522526 return mkAcc (vStr (an ), {"balance" : 0 })
523527 case _:
@@ -604,6 +608,7 @@ def mkBnd(bn, x:dict):
604608 lastAccrueDate = getValWithKs (x , ["计提日" , "lastAccrueDate" ])
605609 lastIntPayDate = getValWithKs (x , ["付息日" , "lastIntPayDate" ])
606610 dueInt = getValWithKs (x , ["应付利息" , "dueInt" ], defaultReturn = 0 )
611+ duePrin = getValWithKs (x , ["应付本金" , "duePrin" ], defaultReturn = 0 )
607612 dueIntOverInt = getValWithKs (x , ["拖欠利息" , "dueIntOverInt" ], defaultReturn = 0 )
608613 mSt = earlyReturnNone (mkStepUp , getValWithKs (x , ["调息" , "stepUp" ], defaultReturn = None ))
609614 match x :
@@ -612,14 +617,14 @@ def mkBnd(bn, x:dict):
612617 return {"bndName" : vStr (bn ), "bndBalance" : bndBalance , "bndRate" : bndRate
613618 , "bndOriginInfo" : {"originBalance" : originBalance , "originDate" : originDate , "originRate" : originRate } | {"maturityDate" : md }
614619 , "bndInterestInfo" : mkBondRate (bndInterestInfo ), "bndType" : mkBondType (bndType )
615- , "bndDuePrin" : 0 , "bndDueInt" : dueInt , "bndDueIntOverInt" :dueIntOverInt , "bndDueIntDate" : lastAccrueDate , "bndStepUp" : mSt
620+ , "bndDuePrin" : duePrin , "bndDueInt" : dueInt , "bndDueIntOverInt" :dueIntOverInt , "bndDueIntDate" : lastAccrueDate , "bndStepUp" : mSt
616621 , "bndLastIntPayDate" : lastIntPayDate }
617622 case {"初始余额" : originBalance , "初始利率" : originRate , "起息日" : originDate , "利率" : bndInterestInfo , "债券类型" : bndType } | \
618623 {"originBalance" : originBalance , "originRate" : originRate , "startDate" : originDate , "rateType" : bndInterestInfo , "bondType" : bndType }:
619624 return {"bndName" : vStr (bn ), "bndBalance" : originBalance , "bndRate" : originRate
620625 , "bndOriginInfo" : {"originBalance" : originBalance , "originDate" : originDate , "originRate" : originRate } | {"maturityDate" : md }
621626 , "bndInterestInfo" : mkBondRate (bndInterestInfo ), "bndType" : mkBondType (bndType )
622- , "bndDuePrin" : 0 , "bndDueInt" : dueInt , "bndDueIntOverInt" :dueIntOverInt , "bndDueIntDate" : lastAccrueDate , "bndStepUp" : mSt
627+ , "bndDuePrin" : duePrin , "bndDueInt" : dueInt , "bndDueIntOverInt" :dueIntOverInt , "bndDueIntDate" : lastAccrueDate , "bndStepUp" : mSt
623628 , "bndLastIntPayDate" : lastIntPayDate }
624629 case _:
625630 raise RuntimeError (f"Failed to match bond:{ bn } ,{ x } :mkBnd" )
@@ -643,14 +648,6 @@ def mkLiqMethod(x):
643648 raise RuntimeError (f"Failed to match { x } :mkLiqMethod" )
644649
645650
646- def mkPDA (x ):
647- match x :
648- case {"公式" : ds } | {"formula" : ds }:
649- return mkTag (("DS" , mkDs (ds )))
650- case _:
651- raise RuntimeError (f"Failed to match { x } :mkPDA" )
652-
653-
654651def mkAccountCapType (x ):
655652 match x :
656653 case {"余额百分比" : pct } | {"balPct" : pct }:
@@ -1106,18 +1103,6 @@ def mkThreshold(x):
11061103 raise RuntimeError (f"Failed to match :{ x } :mkThreshold" )
11071104
11081105
1109- def _rateTypeDs (x ):
1110- h = x [0 ]
1111- if h in set (["资产池累积违约率"
1112- , "cumPoolDefaultedRate"
1113- , "债券系数"
1114- , "bondFactor"
1115- , "资产池系数"
1116- , "poolFactor" ]):
1117- return True
1118- return False
1119-
1120-
11211106def mkTrigger (x : dict ):
11221107 match x :
11231108 case {"condition" :p , "effects" :e , "status" :st , "curable" :c } | {"条件" :p , "效果" :e , "状态" :st , "重置" :c }:
@@ -1137,13 +1122,13 @@ def mkTriggerEffect(x):
11371122 return mkTag (("DealStatusTo" , mkStatus (s )))
11381123 case ("动作" , * actions ) | ("actions" , * actions ):
11391124 return mkTag (("RunActions" , lmap (mkAction , actions )))
1140- case ["计提费用" , * fn ] | ["accrueFees" , * fn ]:
1125+ case ["计提费用" , * fn ] | ["accrueFees" , * fn ] | ( "accrueFees" , * fn ) :
11411126 return mkTag (("DoAccrueFee" , vList (fn , str )))
11421127 case ["新增事件" , trg ] | ["newTrigger" , trg ]: # not implementd in Hastructure
11431128 return mkTag (("AddTrigger" , mkTrigger (trg )))
1144- case ["新储备目标" , accName , newReserve ] | ["newReserveBalance" , accName , newReserve ]:
1129+ case ["新储备目标" , accName , newReserve ] | ["newReserveBalance" , accName , newReserve ] | ( "newReserveBalance" , accName , newReserve ) :
11451130 return mkTag (("ChangeReserveBalance" , [vStr (accName ), mkAccType (newReserve )]))
1146- case ["结果" , * efs ] | ["Effects" , * efs ]:
1131+ case ["结果" , * efs ] | ["Effects" , * efs ] | ( "Effects" , * efs ) :
11471132 return mkTag (("TriggerEffects" , [mkTriggerEffect (e ) for e in efs ]))
11481133 case None :
11491134 return mkTag (("DoNothing" ))
@@ -2065,51 +2050,72 @@ def uplift_ds(df:pd.DataFrame) -> pd.DataFrame:
20652050 r ['waterfall' ] = pd .DataFrame (data = [ [c ['contents' ][0 ],readTagMap (c ['contents' ][1 ])] for c in waterfall_logs ]
20662051 ,columns = ["Date" ,"Waterfall Location" ])
20672052
2068-
20692053 # build financial reports
2070- def mapItem (z ):
2071- match z :
2072- case {"tag" :"Item" ,"contents" :[accName ,accBal ]}:
2073- return {accName :accBal }
2074- case {"tag" :"ParentItem" ,"contents" :[accName ,subItems ]}:
2075- items = map (mapItem , subItems )
2076- return {accName : items }
2077-
2078- def buildBalanceSheet (bsData ):
2079- bsRptDate = bsData .pop ("reportDate" )
2080- bs = mapListValBy (bsData , mapItem )
2081- return mapValsBy (bs , uplift_m_list ) | {"reportDate" :bsRptDate }
2082-
2083- def buildBsType (yname , y :dict )-> pd .DataFrame :
2084- mi = pd .MultiIndex .from_product ([[yname ],y .keys ()])
2085- d = y .values ()
2086- return pd .DataFrame (d , index = mi ).T
2087-
2088- def buildBS (bs ):
2089- bs_df = pd .concat ([ buildBsType (k ,v ) for k ,v in bs .items () if k != "reportDate" ],axis = 1 )
2090- bs_df ['reportDate' ] = bs ['reportDate' ]
2091- return bs_df .set_index ("reportDate" )
2092-
2093- def buildCashReport (cashData ):
2094- sd = cashData .pop ('startDate' )
2095- ed = cashData .pop ('endDate' )
2096- net = cashData .pop ('net' )
2097- cashList = mapListValBy (cashData , mapItem )
2098- cashMap = {k :uplift_m_list (v ) for k ,v in cashList .items () }
2099- cashMap = pd .concat ([ buildBsType (k ,v ) for k ,v in cashMap .items () ],axis = 1 )
2100- cashMap ['startDate' ] = sd
2101- cashMap ['endDate' ] = ed
2102- cashMap ['Net' ] = net
2103- return cashMap .set_index (["startDate" ,"endDate" ])
2104-
2105- balanceSheetIdx = 2
2106- cashReportIdx = 3
2054+ def buildTree (x :dict ):
2055+ def lookParent (x :dict ):
2056+ match x :
2057+ case {'tag' : 'ParentItem' , 'contents' : [itemName , []]}:
2058+ return {itemName :0 }
2059+ case {'tag' :'ParentItem' ,'contents' :[itemName , v ]}:
2060+ subBranchNum = len (v )
2061+ subVals_ = [ lookParent (_ ) for _ in v ]
2062+ subVals = reduce (lambda d1 , d2 : d1 | d2 , subVals_ )
2063+ return {itemName : subVals }
2064+ case {'tag' :'Item' , 'contents' :[itemName ,itemValue ]}:
2065+ return {itemName :itemValue }
2066+ case _:
2067+ return {}
2068+
2069+ match x :
2070+ case {'tag' : 'ParentItem' , 'contents' : [itemName , []]}:
2071+ return {itemName : 0 }
2072+ case {'tag' :'ParentItem' ,'contents' :[itemName , v ]}:
2073+ subVals = reduce (lambda d1 , d2 : d1 | d2 , [ lookParent (_ ) for _ in v ])
2074+ return {itemName : subVals }
2075+ case {'tag' :'Item' , 'contents' :[itemName ,itemValue ]}:
2076+ return {itemName : itemValue }
2077+ case _:
2078+ return {}
2079+
2080+ def buildBalanceSheet (bsData , rptDate ):
2081+ comp = ["asset" ,"liability" ,"equity" ]
2082+ x = reduce (lambda d1 , d2 : d1 | d2 , [ bsData [_ ] & lens .modify (buildTree ) for _ in comp ])
2083+ x = pd .json_normalize (x | {"date" :rptDate })
2084+ x .columns = pd .MultiIndex .from_tuples ([tuple (col .split ("." )) for col in x .columns ])
2085+ x .set_index ("date" )
2086+ return x [["Asset" ,"Liability" ,"Net Asset" ]]
2087+
2088+ def buildCashReport (cashData , begDate , endDate , missingFields ):
2089+ comp = ["inflow" ,"outflow" ,"net" ]
2090+ x = reduce (lambda d1 , d2 : d1 | d2 , [ cashData [_ ] & lens .modify (buildTree ) for _ in comp ])
2091+ x = {k : {} if (v == 0 and k != "Net Cash" ) else v for k ,v in x .items () }
2092+ x = {k : missingFields [k ] | v if k != "Net Cash" else v for k ,v in x .items () }
2093+ x = pd .json_normalize (x | {"startDate" :begDate ,"endDate" :endDate })
2094+ x .columns = pd .MultiIndex .from_tuples ([tuple (col .split ("." )) for col in x .columns ])
2095+ x .set_index (["startDate" ,"endDate" ])
2096+ return x [["startDate" ,"endDate" ,"Inflow" ,"Outflow" ,"Net Cash" ]]
2097+
2098+ def patchMissingField (_rpts ):
2099+ comp = ["inflow" ,"outflow" ,"net" ]
2100+ x = [ reduce (lambda d1 , d2 : d1 | d2 , [ rpt [_ ] & lens .modify (buildTree ) for _ in comp ])
2101+ for rpt in _rpts ]
2102+ x = [ tz .valmap (lambda v : set (v .keys ()) if isinstance (v ,dict ) else set ([]),_ ) for _ in x ]
2103+ x = tz .merge_with (lambda vs : reduce (lambda x ,y : x | y ,vs ), * x )
2104+ x = tz .valmap (lambda d : {_ :0 for _ in d } ,x )
2105+ return x
2106+
2107+ [ beginDateIdx , endDateIdx , balanceSheetIdx , cashReportIdx ] = [ 0 , 1 , 2 , 3 ]
21072108 rpts = [ _ ['contents' ] for _ in (filter_by_tags (x , ["FinancialReport" ])) ]
2109+
21082110 if rpts :
21092111 r ['report' ] = {}
2110- r ['report' ]['balanceSheet' ] = pd .concat ([buildBS (buildBalanceSheet (rpt [balanceSheetIdx ])) for rpt in rpts ])
2111- r ['report' ]['cash' ] = pd .concat ([buildCashReport (rpt [cashReportIdx ]) for rpt in rpts ])[["inflow" ,"outflow" ,"Net" ]]
2112-
2112+ r ['report' ]['balanceSheet' ] = pd .concat ([(buildBalanceSheet (rpt [balanceSheetIdx ],rpt [endDateIdx ])) for rpt in rpts ])
2113+
2114+ cashReportRaw = [ rpt [cashReportIdx ] for rpt in rpts ]
2115+ cfAllFieldsMap = tz .dissoc (patchMissingField (cashReportRaw ), "Net Cash" )
2116+ cfRpts = [buildCashReport (rpt [cashReportIdx ],rpt [beginDateIdx ],rpt [endDateIdx ],cfAllFieldsMap )
2117+ for rpt in rpts ]
2118+ r ['report' ]['cash' ] = pd .concat (cfRpts )
21132119
21142120 return r
21152121
0 commit comments