DZ7G36NC3LJLRBZDFIYINFKNABIK2WIBINKLW7FCSKT5G4YSBYUQC
46OSK255NO5Q6JYMR653W542P3ZKL2A674CBVLGPYY34IMO5SPHAC
CI4OPKQMU4CIX3QPGA5GWSEOPJ2FAREDBVU6X33E7X3EEQXAICRQC
AXKKXBWN4EMUOLV43WN52JSKJPBV7TLSGLNJW5EZXHSJNKCYUWOQC
NJNMO72S7VIUV22JXB4IFPZMHWTJAOTP6EC6Z4QSKYIROSXT52MQC
NVOCQVASZWTKQJG7GPH7KHKZZR7NUG4WLV5YY4KAIRPCJRWCZPIAC
X3ES7NUA42D2BF7CQDDKXM5CLMVCYA3H5YU5KXLPTGDBFPE2LNVAC
IR75ZMX32SFFMDNV2I2L22X5JTWCOC4UUBCSPU7S6VHR6HFV6ADQC
QMRKFEPGFBCEWAIXPEIG5ILKAJ2JH5L3TOITHR4HNJXK5FN3KXBQC
UWMGUJOW5X5HQTS76T2FD7MNAJF7SESPQVU5FDIZO52V75TT2X6AC
U7YAT2ZK6GMS7KVFFEQTDRFX6GIN7HVHNWGKIFDGJGE2G2IXSF6QC
7KZP4RHZ3QSYTPPQ257A65Z5UPX44TF2LAI2U5EMULQCLDCEUK2AC
2OIPAQCBDIUJBXB4K2QVP3IEBIUOCQHSWSWFVMVSVZC7GHX2VK7AC
A2J7B4SCCJYKQV3G2LDHEFNE2GUICO3N3Y5FKF4EUZW5AG7PTDWAC
SCXG6TJWYIPRUMT27KGKIIF6FYKTUTY74UNZ2FQTT63XZ6HIF3AAC
N4NDAZYTLSI2W22KT3SYXL257DBMSH3UT2BXOYM7LH7FSZAY4RLAC
NEDDHXUK3GNFMOFO3KLU7NRIHCTYNWBT3D6HTKZAOXFDE6HMDZ6AC
Y35QCWYW2OTZ27ZVTH2BA3XJTUCJ2WMLKU32ZCOCDY3AW7TIZXRAC
LTSVBVA235BQAIU3SQURKSRHIAL33K47G4J6TSEP2K353OCHNJEAC
KNSI575VAW6HRCZYXOEPQ4DTSML4EORML5MV4DJBRKE7TXCPS4EAC
LHJ2HFXVUQ4VG25I7DADWU73G5K5WNZBDQ3SVNKFYLZ5BEYM4XCQC
EFSXYZPOGA5M4DN65IEIDO7JK6U34DMQELAPHOIL2UAT6HRC66BAC
5DRIWGLUKMQZU2ZPBXSTLAWJKAMOD5YXAHM5LEDQHDFGYYLHWCDQC
NMWWP4ZNOKHZKSJ6F5KYEREWXXR5F4UD35WOKI3EH42AZWVCTCJAC
-- |
-- - The depreciation function should return a value between 0 and 1;
-- - this result is multiplied by the length of an interval of work to determine
-- - the depreciated value of the work.
type DepF = C.UTCTime -> Interval C.UTCTime -> NDT
-- |
-- - The depreciation function should return a value between 0 and 1;
-- - this result is multiplied by the length of an interval of work to determine
-- - the depreciated value of the work.
--
-- arguments:
-- * date of first revenue (if applicable)
-- * date on which the depreciated value is being computed
-- *
type DepF = Maybe C.UTCTime -> C.UTCTime -> Interval C.UTCTime -> NDT
-- - A very simple linear function for calculating depreciation.
linearDepreciation ::
-- | The number of initial days during which no depreciation occurs
C.Days ->
-- | The number of days over which each logged interval will be depreciated
C.Days ->
-- | The resulting configured depreciation function.
DepF
linearDepreciation undepDays depDays =
let -- length of a number of days as NominalDiffTime
undepLength = daysToNDT undepDays
depLength = daysToNDT depDays
-- The percentage of the depreciation period that the end of the interval
-- represents.
depPercentage :: NDT -> Rational
depPercentage intervalAge =
if intervalAge < undepLength
then 1
else max 0 (1 - (C.toSeconds (intervalAge ^-^ undepLength) / C.toSeconds depLength))
in \firstRevenue payoutDate ival ->
let ivalEnd = case firstRevenue of
-- if the end of the interval was before first revenue, count it as
-- having ended at the first revenue date for the purpose of depreciation
Just dt -> max dt (ival ^. end)
Nothing -> ival ^. end
in depPercentage (payoutDate .-. ivalEnd) *^ ilen ival
-- |
-- |
-- - A very simple linear function for calculating depreciation.
linearDepreciation ::
-- | The number of initial days during which no depreciation occurs
C.Days ->
-- | The number of days over which each logged interval will be depreciated
C.Days ->
-- | The resulting configured depreciation function.
DepF
linearDepreciation undepLength depLength =
let daysLength :: C.Days -> NDT
daysLength d = C.fromSeconds $ 60 * 60 * 24 * d
maxDepreciable :: NDT
maxDepreciable = daysLength undepLength ^+^ daysLength depLength
depPct :: NDT -> Rational
depPct dt =
if dt < daysLength undepLength
then 1
else
C.toSeconds (max zeroV (maxDepreciable ^-^ dt))
/ C.toSeconds maxDepreciable
in \ptime ival ->
let depreciation = depPct $ ptime .-. (ival ^. end)
in depreciation *^ ilen ival
let initTime = toThyme . fromJust $ parseISO8601 "2014-01-01T00:08:00Z"
len = fromInteger @NominalDiffTime 360
timestamps = iterate (addUTCTime len) initTime
let initTime = C.toThyme . fromJust $ parseISO8601 "2014-01-01T00:08:00Z"
len = fromInteger @C.NominalDiffTime 360
timestamps = iterate (.+^ len) initTime
describe "depreciation functions" $ do
it "computes linear depreciation" $ do
let depf = linearDepreciation 10 100
hour = fromInteger (60 * 60)
t0 :: C.UTCTime = C.toThyme . fromJust $ parseISO8601 "2014-01-01T00:08:00Z"
ival = I.Interval (t0 .+^ negate hour) t0
t1 = t0 .+^ daysToNDT 5
t2 = t0 .+^ daysToNDT 10
t3 = t0 .+^ daysToNDT 20
t4 = t0 .+^ daysToNDT 60
t5 = t0 .+^ daysToNDT 110
daysToNDT 1 `shouldBe` (60 * 60 * 24)
-- undepreciated; within the first 10 days
depf Nothing t1 ival `shouldBe` 3600
-- still undepreciated if it ends at the last depreciation moment
depf Nothing t2 ival `shouldBe` 3600
-- depreciated by 10% of its value
depf Nothing t3 ival `shouldBe` 3240
-- depreciated by 50% of its value
depf Nothing t4 ival `shouldBe` 1800
-- depreciated by 100% of its value
depf Nothing t5 ival `shouldBe` 0
-- undepreciated - before first revenue
depf (Just t3) t1 ival `shouldBe` 3600
-- undepreciated - before first revenue
depf (Just t3) t2 ival `shouldBe` 3600
-- depreciated by 10% of its value
depf (Just t3) t3 ival `shouldBe` 3600
-- depreciated by 30% of its value
depf (Just t3) t4 ival `shouldBe` 2520
-- depreciated by 80% of its value
depf (Just t3) t5 ival `shouldBe` 720