packages feed

Hastructure-0.45.0: src/Deal/DealBase.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleInstances #-}

module Deal.DealBase (TestDeal(..),SPV(..),dealBonds,dealFees,dealAccounts,dealPool,PoolType(..),getIssuanceStats
                     ,getAllAsset,getAllAssetList,getAllCollectedFrame,getLatestCollectFrame,getAllCollectedTxns
                     ,getIssuanceStatsConsol,getAllCollectedTxnsList
                     ,getPoolIds,getBondByName, UnderlyingDeal(..), uDealFutureTxn,viewDealAllBonds,DateDesp(..),ActionOnDate(..)
                     ,sortActionOnDate,dealBondGroups
                     ,viewDealBondsByNames,poolTypePool,viewBondsInMap,bondGroupsBonds
                     ,increaseBondPaidPeriod,increasePoolCollectedPeriod
                     ,DealStatFields(..),getDealStatInt,isPreClosing,populateDealDates
                     ,bondTraversal,findBondByNames,updateBondInMap
		     ,_MultiPool,_ResecDeal,uDealFutureCf,uDealFutureScheduleCf
                     )                      
  where
import qualified Accounts as A
import qualified Ledger as LD
import qualified Asset as Ast
import qualified Expense as F
import qualified Liability as L
import qualified CreditEnhancement as CE
import qualified Hedge as HE
import qualified Waterfall as W
import qualified Cashflow as CF
import qualified Assumptions as AP
import qualified AssetClass.AssetBase as ACM
import qualified Call as C
import qualified InterestRate as IR
import Stmt
import Lib
import Util
import DateUtil
import Types
import Revolving
import Triggers

import qualified Data.Map as Map
import qualified Data.Time as T
import qualified Data.Set as S
import qualified Data.DList as DL
import Data.List
import Data.Fixed
import Data.Maybe
import Data.Ratio
import Data.Aeson hiding (json)
import qualified Data.Aeson.Encode.Pretty as Pretty
import Language.Haskell.TH
import Data.Aeson.TH
import Data.Aeson.Types
import GHC.Generics
import Control.Lens hiding (element)
import Control.Lens.TH
import Data.IntMap (filterWithKey)
import qualified Data.Text as T
import Text.Read (readMaybe)
import qualified Pool as P
import qualified Types as CF

import Debug.Trace
import qualified Control.Lens as P
debug = flip trace


data DealComp = CompBond 
              | CompAccount 
              | CompFee 
              | CompPool 
              | CompTrigger 
              | CompLedger 
              | CompRateSwap 
              | CompRateCap 
              | CompCurrencySwap 
              | CompLiqProvider 
              deriving (Show,Eq,Ord,Generic,Read)

data ActionTypeOnDate = DoSettle
                      | DoAccrue
                      | DoUpdateRate

data ActionOnDate = EarnAccInt Date AccName              -- ^ sweep bank account interest
                  | ChangeDealStatusTo Date DealStatus   -- ^ change deal status
                  | AccrueFee Date FeeName               -- ^ accure fee
                  | ResetLiqProvider Date String         -- ^ reset credit for liquidity provider
                  | ResetLiqProviderRate Date String     -- ^ accure interest/premium amount for liquidity provider
                  | PoolCollection Date String           -- ^ collect pool cashflow and deposit to accounts
                  | RunWaterfall Date String             -- ^ execute waterfall on distribution date
                  | DealClosed Date                      -- ^ actions to perform at the deal closing day, and enter a new deal status
                  | FireTrigger Date DealCycle String    -- ^ fire a trigger
                  | InspectDS Date [DealStats]           -- ^ inspect formulas
                  | CalcIRSwap Date String               -- ^ calc interest rate swap dates
                  | SettleIRSwap Date String             -- ^ settle interest rate swap dates
                  | AccrueCapRate Date String            -- ^ reset interest rate cap dates
                  | ResetBondRate Date String            -- ^ reset bond interest rate per bond's interest rate info
                  | StepUpBondRate Date String           -- ^ reset bond interest rate per bond's interest rate info
                  | ResetSrtRate Date String 
                  | ResetAccRate Date String 
                  | AccrueSrt Date String 
                  | MakeWhole Date Spread (Table Float Spread)
                  | IssueBond Date (Maybe Pre) String AccName L.Bond (Maybe DealStats) (Maybe DealStats)
                  | FundBond Date (Maybe Pre) String AccName Amount
                  | RefiBondRate Date AccountName BondName L.InterestInfo
                  | RefiBond Date AccountName L.Bond
                  | BuildReport StartDate EndDate        -- ^ build cashflow report between dates and balance report at end date
                  | StopRunFlag Date                     -- ^ stop the run with a message
                  | StopRunTest Date [Pre]               -- ^ stop the run with a condition
                  | HitStatedMaturity Date               -- ^ hit the stated maturity date
                  | TestCall Date                        -- ^ test call dates
                  deriving (Show,Generic,Read)

instance Ord ActionOnDate where
  compare a1 a2 = compare (getDate a1) (getDate a2)

instance Eq ActionOnDate where
  a1 == a2 = getDate a1 == getDate a2


instance TimeSeries ActionOnDate where
    getDate (RunWaterfall d _) = d
    getDate (ResetLiqProvider d _) = d
    getDate (PoolCollection d _) = d
    getDate (EarnAccInt d _) = d
    getDate (AccrueFee d _) = d
    getDate (DealClosed d) = d
    getDate (FireTrigger d _ _) = d
    getDate (ChangeDealStatusTo d _ ) = d
    getDate (InspectDS d _ ) = d
    getDate (CalcIRSwap d _ ) = d
    getDate (SettleIRSwap d _ ) = d
    getDate (AccrueCapRate d _ ) = d
    getDate (ResetBondRate d _) = d 
    getDate (StepUpBondRate d _) = d 
    getDate (ResetAccRate d _ ) = d 
    getDate (MakeWhole d _ _) = d 
    getDate (BuildReport sd ed) = ed
    getDate (IssueBond d _ _ _ _ _ _) = d
    getDate (RefiBondRate d _ _ _) = d
    getDate (RefiBond d _ _) = d
    getDate (ResetLiqProviderRate d _) = d
    getDate (TestCall d) = d
    getDate (FundBond d _ _ _ _) = d
    getDate (HitStatedMaturity d) = d
    getDate (StopRunTest d _) = d
    getDate x = error $ "Failed to match"++ show x


sortActionOnDate :: ActionOnDate -> ActionOnDate -> Ordering
sortActionOnDate a1 a2 
  | d1 == d2 = case (a1,a2) of
                  (PoolCollection {}, DealClosed {}) -> LT -- pool collection should be executed before deal closed
                  (DealClosed {}, PoolCollection {}) -> GT -- pool collection should be executed before deal closed
                  (BuildReport sd1 ed1 ,_) -> GT  -- build report should be executed last
                  (_ , BuildReport sd1 ed1) -> LT -- build report should be executed last
                  (TestCall _ ,_) -> GT  -- test call should be executed last
                  (_ , TestCall _) -> LT -- test call should be executed last
                  (CalcIRSwap _ _ ,SettleIRSwap _ _) -> LT  -- reset interest swap should be first
                  (SettleIRSwap _ _ ,CalcIRSwap _ _) -> GT  -- reset interest swap should be first
                  (_ , CalcIRSwap _ _) -> GT -- reset interest swap should be first
                  (CalcIRSwap _ _ ,_) -> LT  -- reset interest swap should be first
                  (_ , CalcIRSwap _ _) -> GT -- reset interest swap should be first
                  (StepUpBondRate {} ,_) -> LT  -- step up bond rate should be first
                  (_ , StepUpBondRate {}) -> GT -- step up bond rate should be first
                  (ResetBondRate {} ,_) -> LT  -- reset bond rate should be first
                  (_ , ResetBondRate {}) -> GT -- reset bond rate should be first
                  (EarnAccInt {} ,_) -> LT  -- earn should be first
                  (_ , EarnAccInt {}) -> GT -- earn should be first
                  (ResetLiqProvider {} ,_) -> LT  -- reset liq be first
                  (_ , ResetLiqProvider {}) -> GT -- reset liq be first
                  (PoolCollection {}, RunWaterfall {}) -> LT -- pool collection should be executed before waterfall
                  (RunWaterfall {}, PoolCollection {}) -> GT -- pool collection should be executed before waterfall
                  (_,_) -> EQ 
  | otherwise = compare d1 d2
  where 
    d1 = getDate a1 
    d2 = getDate a2 


type CutoffDate = Date
type ClosingDate = Date
type RevolvingDate = Date
type StatedDate = Date
type DistributionDates = DatePattern
type PoolCollectionDates = DatePattern


data DateDesp = PreClosingDates CutoffDate ClosingDate (Maybe RevolvingDate) StatedDate (Date,PoolCollectionDates) (Date,DistributionDates)
              -- <Pool Collection DP> <Waterfall DP> 
              --  (last collect,last pay), mRevolving end-date dp1-pool-pay dp2-bond-pay
              | CurrentDates (Date,Date) (Maybe Date) StatedDate (Date,PoolCollectionDates) (Date,DistributionDates)
              -- Dict based 
              | GenericDates (Map.Map DateType DatePattern)
              deriving (Show,Eq, Generic,Ord)


populateDealDates :: DateDesp -> DealStatus -> Either String (Date,Date,Date,[ActionOnDate],[ActionOnDate],Date,[ActionOnDate])
populateDealDates (PreClosingDates cutoff closing mRevolving end (firstCollect,poolDp) (firstPay,bondDp)) _
  = Right (cutoff,closing,firstPay,pa,ba,end, []) 
    where 
      pa = [ PoolCollection _d "" | _d <- genSerialDatesTill2 IE firstCollect poolDp end ]
      ba = [ RunWaterfall _d "" | _d <- genSerialDatesTill2 IE firstPay bondDp end ]

populateDealDates (CurrentDates (lastCollect,lastPay) mRevolving end (nextCollect,poolDp) (nextPay,bondDp)) _
  = Right (lastCollect, lastPay,head futurePayDates, pa, ba, end, []) 
    where 
      futurePayDates = genSerialDatesTill2 IE nextPay bondDp end 
      ba = [ RunWaterfall _d "" | _d <- futurePayDates]
      futureCollectDates = genSerialDatesTill2 IE nextCollect poolDp end 
      pa = [ PoolCollection _d "" | _d <- futureCollectDates]

populateDealDates (GenericDates m) 
                  (PreClosing _)
  = let 
      requiredFields = (CutoffDate, ClosingDate, FirstPayDate, StatedMaturityDate
                        , DistributionDates, CollectionDates) 
      vals = lookupTuple6 requiredFields m
      
      isCustomWaterfallKey (CustomExeDates _) _ = True
      isCustomWaterfallKey _ _ = False
      custWaterfall = Map.toList $ Map.filterWithKey isCustomWaterfallKey m
    in 
      case vals of
        (Just (SingletonDate coffDate), Just (SingletonDate closingDate), Just (SingletonDate fPayDate)
          , Just (SingletonDate statedDate), Just bondDp, Just poolDp)
          -> let 
                pa = [ PoolCollection _d "" | _d <- genSerialDatesTill2 IE closingDate poolDp statedDate ]
                ba = [ RunWaterfall _d "" | _d <- genSerialDatesTill2 IE fPayDate bondDp statedDate ]
                cu = [ RunWaterfall _d custName | (CustomExeDates custName, custDp) <- custWaterfall
                                                , _d <- genSerialDatesTill2 EE closingDate custDp statedDate ]
              in 
                Right (coffDate, closingDate, fPayDate, pa, ba, statedDate, cu)
        _ 
          -> Left "Missing required dates in GenericDates in deal status PreClosing"

populateDealDates (GenericDates m) _ 
  = let 
      requiredFields = (LastCollectDate, LastPayDate, NextPayDate, StatedMaturityDate
                        , DistributionDates, CollectionDates) 
      vals = lookupTuple6 requiredFields m
      
      isCustomWaterfallKey (CustomExeDates _) _ = True
      isCustomWaterfallKey _ _ = False
      custWaterfall = Map.toList $ Map.filterWithKey isCustomWaterfallKey m
    in 
      case vals of
        (Just (SingletonDate lastCollect), Just (SingletonDate lastPayDate), Just (SingletonDate nextPayDate)
          , Just (SingletonDate statedDate), Just bondDp, Just poolDp)
          -> let 
                pa = [ PoolCollection _d "" | _d <- genSerialDatesTill2 EE lastCollect poolDp statedDate ]
                ba = [ RunWaterfall _d "" | _d <- genSerialDatesTill2 IE nextPayDate bondDp statedDate ]
                cu = [ RunWaterfall _d custName | (CustomExeDates custName, custDp) <- custWaterfall
                                                , _d <- genSerialDatesTill2 EE lastCollect custDp statedDate ]
              in 
                Right (lastCollect, lastPayDate, nextPayDate, pa, ba, statedDate, cu) -- `debug` ("custom action"++ show cu)
        _ 
          -> Left "Missing required dates in GenericDates in deal status PreClosing"



class SPV a where
  getBondsByName :: a -> Maybe [String] -> Map.Map String L.Bond
  getActiveBonds :: a -> [String] -> [L.Bond]
  getBondBegBal :: a -> String -> Balance
  getBondStmtByName :: a -> Maybe [String] -> Map.Map String (Maybe Statement)
  getFeeByName :: a -> Maybe [String] -> Map.Map String F.Fee
  getAccountByName :: a -> Maybe [String] -> Map.Map String A.Account
  isResec :: a -> Bool
  getNextBondPayDate :: a -> Date
  getOustandingBal :: a -> Balance


type BalDealStatMap = Map.Map DealStatFields Balance
type RDealStatMap = Map.Map DealStatFields Rate
type BDealStatMap = Map.Map DealStatFields Bool
type IDealStatMap = Map.Map DealStatFields Int

data TestDeal a = TestDeal { name :: DealName
                            ,status :: DealStatus
                            ,dates :: DateDesp
                            ,accounts :: Map.Map AccountName A.Account
                            ,fees :: Map.Map FeeName F.Fee
                            ,bonds :: Map.Map BondName L.Bond
                            ,pool ::  PoolType a 
                            ,waterfall :: Map.Map W.ActionWhen W.DistributionSeq
                            ,collects :: [W.CollectionRule]
                            ,stats :: (BalDealStatMap,RDealStatMap,BDealStatMap,IDealStatMap)
                            ,liqProvider :: Maybe (Map.Map String CE.LiqFacility)
                            ,rateSwap :: Maybe (Map.Map String HE.RateSwap)
                            ,rateCap :: Maybe (Map.Map String HE.RateCap)
                            ,currencySwap :: Maybe (Map.Map String HE.CurrencySwap)
                            ,custom:: Maybe (Map.Map String CustomDataType)
                            ,triggers :: Maybe (Map.Map DealCycle (Map.Map String Trigger))
                            ,ledgers :: Maybe (Map.Map String LD.Ledger)
                            } deriving (Show,Generic,Eq,Ord)

data UnderlyingDeal a = UnderlyingDeal {
  deal :: TestDeal a
  ,futureCf :: CF.CashFlowFrame
  ,futureScheduleCf :: CF.CashFlowFrame
  ,issuanceStat :: Maybe (Map.Map CutoffFields Balance)
} deriving (Generic,Eq,Ord,Show)

uDealFutureScheduleCf :: Ast.Asset a => Lens' (UnderlyingDeal a) CF.CashFlowFrame
uDealFutureScheduleCf = lens getter setter
  where 
    getter = futureScheduleCf
    setter ud newCf = ud {futureScheduleCf = newCf}

uDealFutureCf :: Ast.Asset a => Lens' (UnderlyingDeal a) CF.CashFlowFrame
uDealFutureCf = lens getter setter
  where 
    getter = futureCf
    setter ud newCf = ud {futureCf = newCf}

uDealFutureTxn :: Ast.Asset a => Lens' (UnderlyingDeal a) [CF.TsRow]
uDealFutureTxn = lens getter setter
  where 
    getter ud = view CF.cashflowTxn $ futureCf ud
    setter ud newTxn = ud {futureCf = CF.CashFlowFrame (0,toDate "19000101",Nothing) newTxn}
        -- let 
        --    mOriginalCfFrame = futureCf ud 
        -- in 
        --    case mOriginalCfFrame of 
        --      
        --      (CF.CashFlowFrame (begBal,begDate,mInt) txns) -> ud {futureCf = CF.CashFlowFrame (0,toDate "19000101",Nothing) newTxn }


data PoolType a = MultiPool (Map.Map PoolId (P.Pool a))
                | ResecDeal (Map.Map PoolId (UnderlyingDeal a))
                deriving (Generic, Eq, Ord, Show)

makePrisms ''PoolType


instance SPV (TestDeal a) where
  getBondsByName t bns
    = case bns of
        Nothing -> bonds t
        Just _bns -> Map.filterWithKey (\k _ -> S.member k (S.fromList _bns)) (bonds t)
  
  getActiveBonds t bns = 
    let 
      bnds = (bonds t Map.!) <$> bns
    in 
      filter (not . L.isPaidOff) bnds

  getBondStmtByName t bns
    = Map.map L.bndStmt bndsM
      where
      bndsM = Map.map L.consolStmt $ getBondsByName t bns

  getNextBondPayDate t
    = case populateDealDates (dates t) (status t) of
        Right _dates -> view _3 _dates 
        Left _ -> error "Failed to populate dates"

  getBondBegBal t bn 
    = 
      case b of 
        Nothing -> 0
        Just bnd ->
          case L.bndStmt bnd of
            Nothing -> L.getCurBalance bnd  -- `debug` ("Getting beg bal nothing"++bn)
            Just (Statement txns) 
              | DL.empty == txns  -> L.getCurBalance bnd  
              | otherwise -> getTxnBegBalance $ head (DL.toList txns) -- `debug` ("Getting beg bal"++bn++"Last smt"++show (head stmts))
      where
          b = find (\x -> ((L.bndName x) == bn)) (viewDealAllBonds t) 

  getFeeByName t fns
    = case fns of
         Nothing -> fees t
         Just _fns -> Map.filterWithKey (\k _ ->  S.member k (S.fromList _fns)) (fees t)
  
  getAccountByName t ans
    = case ans of
         Nothing -> accounts t
         Just _ans -> Map.filterWithKey (\k _ ->  S.member k (S.fromList _ans)) (accounts t)
  
  isResec t = case pool t of
                 ResecDeal _ -> True
                 _ -> False

  getOustandingBal t@TestDeal{ bonds = bndMap, fees= feeMap, liqProvider = mliqMap, rateSwap = rsMap}
   = let 
      bndBal = sum $ getOutstandingAmount <$> Map.elems bndMap
      feeBal = sum $ getOutstandingAmount <$> Map.elems feeMap
      lqBalace m
        | not (Map.null m) = sum $ getOutstandingAmount <$> Map.elems m
        | otherwise = 0
      rsBalance m
        | not (Map.null m) = sum $ getOutstandingAmount <$> Map.elems m
        | otherwise = 0
     in 
      bndBal + feeBal + lqBalace (fromMaybe Map.empty mliqMap) + rsBalance (fromMaybe Map.empty rsMap)
  
isPreClosing :: TestDeal a -> Bool
isPreClosing t@TestDeal{ status = PreClosing _ } = True
isPreClosing _ = False


-- ^ list all bonds and bond groups in list
viewDealAllBonds :: TestDeal a -> [L.Bond]
viewDealAllBonds d = 
    let 
       bs = Map.elems (bonds d)
       view a@(L.Bond {} ) = [a]
       view a@(L.BondGroup bMap _) = Map.elems bMap
       view a@(L.MultiIntBond {}) = [a]
    in 
       concat $ view <$> bs

-- ^ flatten all bonds/bond groups in a map
viewBondsInMap :: TestDeal a -> Map.Map String L.Bond
viewBondsInMap t@TestDeal{ bonds = bndMap }
  = let 
      bnds = viewDealAllBonds t 
      bndNames = L.bndName <$> bnds
    in 
      Map.fromList $ zip bndNames bnds

-- ^ support bond group
viewDealBondsByNames :: Ast.Asset a => TestDeal a -> [BondName] -> [L.Bond]
viewDealBondsByNames _ [] = []
viewDealBondsByNames t@TestDeal{bonds= bndMap } bndNames
  = let 
      -- bonds and bond groups
      bnds = filter (\b -> L.bndName b `elem` bndNames) $ viewDealAllBonds t
      -- bndsFromGrp = $ Map.filter (\L.BondGroup {} -> True)  bndMap
      bndsFromGrp = Map.foldrWithKey
                      (\k (L.BondGroup bMap _) acc -> 
                        if k `elem` bndNames 
                        then 
                          acc ++ Map.elems bMap
                        else 
                          acc)
                      []
                      (view dealBondGroups t )
    in 
      bnds ++ bndsFromGrp

-- ^ find bonds with first match
findBondByNames :: Map.Map String L.Bond -> [BondName] -> Either String [L.Bond]
findBondByNames bMap bNames
  = let 
      (firstMatch, notMatched) = Map.partitionWithKey (\k _ -> k `elem` bNames) bMap
      remainNames::[String] = bNames \\ Map.keys firstMatch
      listOfBondGrps::[Map.Map String L.Bond] = [ bM |  (bM,_) <-catMaybes $ (preview L._BondGroup) <$> Map.elems notMatched ]
      (secondMatch, notMatched2) = Map.partitionWithKey (\k _ -> k `elem` remainNames) $ Map.unions listOfBondGrps
    in 
      if Map.null notMatched2 then 
        Right $ Map.elems firstMatch ++ Map.elems secondMatch
      else
        Left $ "Failed to find bonds by names:"++ show (Map.keys notMatched2)

-- ^ not support bond group
dealBonds :: Ast.Asset a => Lens' (TestDeal a) (Map.Map BondName L.Bond)
dealBonds = lens getter setter 
  where 
    getter d = bonds d 
    setter d newBndMap = d {bonds = newBndMap}

-- ^ get & set bond group only
dealBondGroups :: Ast.Asset a => Lens' (TestDeal a) (Map.Map BondName L.Bond)
dealBondGroups = lens getter setter 
  where 
    getter d = Map.filter (has L._BondGroup) (bonds d)
    setter d newBndMap = d {bonds = Map.filter (has L._BondGroup) newBndMap}

bondGroupsBonds :: Lens' L.Bond (Map.Map BondName L.Bond)
bondGroupsBonds = lens getter setter 
  where 
    getter (L.BondGroup bMap _) = bMap
    getter _ = Map.empty
    setter (L.BondGroup b x) newBMap = L.BondGroup newBMap x
    setter x _ = x

updateBondInMap :: BondName -> (L.Bond -> L.Bond) -> Map.Map BondName L.Bond ->  Map.Map BondName L.Bond
updateBondInMap bName f bMap 
  = let 
      fn _bName (L.BondGroup subMap bt) = L.BondGroup (Map.adjust f _bName subMap) bt
      fn _bName bnd 
        | _bName == bName = f bnd
        | otherwise = bnd
    in 
      Map.mapWithKey fn bMap

dealAccounts :: Ast.Asset a => Lens' (TestDeal a) (Map.Map AccountName A.Account) 
dealAccounts = lens getter setter 
  where 
    getter d = accounts d 
    setter d newAccMap = d {accounts = newAccMap}

dealFees :: Ast.Asset a => Lens' (TestDeal a) (Map.Map FeeName F.Fee) 
dealFees = lens getter setter 
  where 
    getter d = fees d 
    setter d newFeeMap = d {fees = newFeeMap}

dealPool :: Ast.Asset a => Lens' (TestDeal a) (PoolType a)
dealPool = lens getter setter 
  where 
    getter d = pool d
    setter d newPool = d {pool = newPool}

poolTypePool :: Ast.Asset a => Lens' (PoolType a) (Map.Map PoolId (P.Pool a))
poolTypePool = lens getter setter
  where
    getter = \case MultiPool pm -> pm
    setter (MultiPool pm) newPm = MultiPool newPm

poolTypeUnderDeal :: Ast.Asset a => Lens' (PoolType a) (Map.Map PoolId (UnderlyingDeal a))
poolTypeUnderDeal = lens getter setter
  where 
    getter = \case ResecDeal dm -> dm
    setter (ResecDeal dm) newDm = ResecDeal newDm

-- schedulePoolFlowLens = poolTypePool . mapped . P.futureScheduleCfLens 
-- schedulePoolFlowAggLens = schedulePoolFlowLens . _1 . _1
-- scheduleBondFlowLens = poolTypeUnderDeal . mapped . uDealFutureScheduleCf


-- dealInputCashflow :: Ast.Asset a => Lens' (TestDeal a) (Map.Map PoolId CF.PoolCashflow)
-- dealInputCashflow = lens getter setter
--   where
--     getter d = case pool d of
--                 MultiPool pm -> Map.map (P.futureScheduleCf) pm
--                 ResecDeal uds -> Map.map futureScheduleCf uds
--     setter d newCfMap = case pool d of
--                           MultiPool pm -> 
-- 			    let 
--                               newPm = Map.mapWithKey (\k p -> set (P.poolFutureScheduleCf) (newCfMap Map.! k) p) pm
--                             in
--                               set dealPool (MultiPool newPm) d
--                           ResecDeal pm -> 
--                             let 
--                               newPm = Map.mapWithKey (\k ud ->gset uDealFutureScheduleCf (newCfMap Map.! k) ud) pm
--                             in
--                               set dealPool (ResecDeal newPm) d

-- dealCashflow :: Ast.Asset a => Lens' (TestDeal a) (Map.Map PoolId (Maybe CF.CashFlowFrame))
-- dealCashflow = lens getter setter
--   where 
--     getter d = case pool d of
--                 MultiPool pm -> Map.map P.futureCf pm
--                 ResecDeal uds -> Map.map futureCf uds
--     setter d newCfMap = case pool d of 
--                           MultiPool pm -> let 
--                                             newPm = Map.mapWithKey (\k p -> set P.poolFutureCf (newCfMap Map.! k) p) pm
--                                           in 
--                                             set dealPool (MultiPool newPm) d
--                           ResecDeal pm ->
--                             let 
--                               newPm = Map.mapWithKey 
-- 			                (\k ud -> set uDealFutureCf (newCfMap Map.! k) ud)
-- 					pm
--                             in
--                               set dealPool (ResecDeal newPm) d

getPoolIds :: Ast.Asset a => TestDeal a -> [PoolId]
getPoolIds t@TestDeal{pool = pt} 
  = case pt of
      MultiPool pm -> Map.keys pm
      ResecDeal pm -> Map.keys pm
      _ -> error "failed to match pool type in pool ids"

-- ^ to handle with bond group, with flag to good deep if it is a bond group
getBondByName :: Ast.Asset a => TestDeal a -> Bool -> BondName -> Maybe L.Bond
getBondByName t False bName = Map.lookup bName (bonds t)
getBondByName t True bName = 
  let 
    bnds = viewDealAllBonds t
  in 
    find (\b -> L.bndName b == bName) bnds

-- ^ get issuance pool stat from pool map
getIssuanceStats :: Ast.Asset a => TestDeal a  -> Maybe [PoolId] -> Map.Map PoolId (Map.Map CutoffFields Balance)
getIssuanceStats t@TestDeal{pool = pt} mPoolId
  = case pt of
      ResecDeal uDeals -> 
        let 
          selecteduDeals = case mPoolId of 
                            Nothing -> uDeals
                            Just pns -> Map.filterWithKey (\k _ -> k `elem` pns ) uDeals
        in
          Map.map (fromMaybe Map.empty . issuanceStat) selecteduDeals 
      MultiPool pm -> let 
                        selectedPools = case mPoolId of 
                                          Nothing -> pm
                                          Just pns -> Map.filterWithKey (\k _ -> k `elem` pns ) pm
                      in
                        Map.map (fromMaybe Map.empty . P.issuanceStat) selectedPools

getIssuanceStatsConsol :: Ast.Asset a => TestDeal a -> Maybe [PoolId] -> Map.Map CutoffFields Balance
getIssuanceStatsConsol t mPns 
  = let 
      ms = getIssuanceStats t mPns
    in 
      Map.unionsWith (+) $ Map.elems ms

getAllAsset :: TestDeal a -> Maybe [PoolId] -> Map.Map PoolId [a]
getAllAsset t@TestDeal{pool = pt} mPns = 
  let 
    assetMap = case pt of 
                 MultiPool pm -> Map.map P.assets pm
                 ResecDeal _ -> Map.empty
                 -- ResecDeal pm -> Map.mapWithKey (\(UnderlyingBond (bn,hpct,sd), d) -> getAllAsset d Nothing) pm
  in
    case mPns of 
      Nothing -> assetMap 
      Just pns -> Map.filterWithKey (\k _ -> k `elem` pns ) assetMap
    
getAllAssetList :: Ast.Asset a => TestDeal a -> [a]
getAllAssetList t = concat $ Map.elems (getAllAsset t Nothing)

getAllCollectedFrame :: Ast.Asset a => TestDeal a -> Maybe [PoolId] -> Map.Map PoolId CF.CashFlowFrame
getAllCollectedFrame t@TestDeal{pool = poolType} mPid = 
  let 
    mCf = case poolType of 
            MultiPool pm -> Map.map (view (P.poolFutureCf . _Just . _1 )) pm -- `debug` ("MultiPool" ++ show pm)
            ResecDeal uds -> Map.map futureCf uds
  in 
    case mPid of 
      Nothing -> mCf  -- `debug` ("Nothing when collecting cfs"++show mCf)
      Just pids -> Map.filterWithKey (\k _ -> k `elem` pids) mCf -- `debug` ("Just when collecting cfs"++show mCf)

getLatestCollectFrame :: Ast.Asset a => TestDeal a -> Maybe [PoolId] -> Map.Map PoolId (Maybe CF.TsRow)
getLatestCollectFrame t mPns = Map.map (\case
                                          (CF.CashFlowFrame (_,_,_) []) -> Nothing
                                          (CF.CashFlowFrame (_,_,_) txns) -> Just $ last txns
                                          )
                                        (getAllCollectedFrame t mPns)

getAllCollectedTxns :: Ast.Asset a => TestDeal a -> Maybe [PoolId] -> Map.Map PoolId [CF.TsRow]
getAllCollectedTxns t mPns = Map.map (view CF.cashflowTxn) (getAllCollectedFrame t mPns)

getAllCollectedTxnsList :: Ast.Asset a => TestDeal a -> Maybe [PoolId] -> [CF.TsRow]
getAllCollectedTxnsList t mPns 
  = concat listOfTxns
    where 
      listOfTxns = Map.elems $ getAllCollectedTxns t mPns

increasePoolCollectedPeriod :: TestDeal a -> TestDeal a
increasePoolCollectedPeriod t@TestDeal{stats = (balMap,rateMap,boolMap,intMap)} 
  = let 
      intMap' = Map.insertWith (+) PoolCollectedPeriod 1 intMap
    in 
      t {stats = (balMap,rateMap,boolMap,intMap')}

increaseBondPaidPeriod :: TestDeal a -> TestDeal a
increaseBondPaidPeriod t@TestDeal{stats = (balMap,rateMap,boolMap,intMap)} 
  = let 
      intMap' = Map.insertWith (+) BondPaidPeriod 1 intMap
    in 
      t {stats = (balMap,rateMap,boolMap,intMap')}

getDealStatInt :: TestDeal a -> DealStatFields -> Maybe Int
getDealStatInt t@TestDeal{stats = (balMap,rateMap,boolMap,intMap)} f 
  = Map.lookup f intMap

bondTraversal :: Traversal' (TestDeal a) L.Bond
bondTraversal f t@TestDeal{bonds = bndMap} =
  (\newBndMap -> t {bonds = newBndMap}) <$> traverse f bndMap

data UnderBond b = UnderBond BondName Rate (TestDeal b)

opts :: JSONKeyOptions
opts = defaultJSONKeyOptions

instance ToJSONKey DealStatFields where
  toJSONKey = genericToJSONKey opts
instance FromJSONKey DealStatFields where
  fromJSONKey = genericFromJSONKey opts


$(concat <$> traverse (deriveJSON defaultOptions) [''TestDeal, ''UnderlyingDeal, ''PoolType, ''DateDesp, ''ActionOnDate])