Compare commits
4 commits
Author | SHA1 | Date | |
---|---|---|---|
1801082c71 | |||
3ac6c2d40c | |||
e1f338f1f1 | |||
2d9cfd818d |
7 changed files with 55 additions and 30 deletions
|
@ -1,5 +1,9 @@
|
||||||
# Revision history for pilu
|
# Revision history for pilu
|
||||||
|
|
||||||
|
## 0.1.1.0 -- 2019-08-31
|
||||||
|
|
||||||
|
* Add support for a comment column in timelines
|
||||||
|
|
||||||
## 0.1.0.0 -- YYYY-mm-dd
|
## 0.1.0.0 -- YYYY-mm-dd
|
||||||
|
|
||||||
* First version. Released on an unsuspecting world.
|
* First version. Released on an unsuspecting world.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
-- see http://haskell.org/cabal/users-guide/
|
-- see http://haskell.org/cabal/users-guide/
|
||||||
|
|
||||||
name: pilu
|
name: pilu
|
||||||
version: 0.1.0.0
|
version: 0.1.1.0
|
||||||
-- synopsis:
|
-- synopsis:
|
||||||
-- description:
|
-- description:
|
||||||
license: BSD3
|
license: BSD3
|
||||||
|
|
35
src/CLI.hs
35
src/CLI.hs
|
@ -1,30 +1,47 @@
|
||||||
module CLI (
|
module CLI (
|
||||||
Invocation(..)
|
Invocation(..)
|
||||||
|
, Mode(..)
|
||||||
, invoked
|
, invoked
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Options.Applicative (
|
import Options.Applicative (
|
||||||
Parser, execParser, flag, flag', fullDesc, header, help, helper, info, long
|
Parser, auto, execParser, flag, fullDesc, header, help, helper, info
|
||||||
|
, infoOption, long, option, short, value
|
||||||
)
|
)
|
||||||
import Control.Applicative ((<**>), (<|>))
|
import Control.Applicative ((<**>), optional)
|
||||||
import Data.Monoid ((<>))
|
import Data.Monoid ((<>))
|
||||||
|
import Data.Time (Day)
|
||||||
import Data.Version (showVersion)
|
import Data.Version (showVersion)
|
||||||
import qualified Paths_pilu as Pilu (version)
|
import qualified Paths_pilu as Pilu (version)
|
||||||
|
|
||||||
data Invocation = Inventory | Schedule | Version String
|
data Mode = Inventory | Schedule
|
||||||
|
|
||||||
|
data Invocation = Invocation {
|
||||||
|
mode :: Mode
|
||||||
|
, date :: Maybe Day
|
||||||
|
}
|
||||||
|
|
||||||
versionStr :: String
|
versionStr :: String
|
||||||
versionStr = showVersion Pilu.version
|
versionStr = showVersion Pilu.version
|
||||||
|
|
||||||
|
version :: Parser (Invocation -> Invocation)
|
||||||
|
version =
|
||||||
|
infoOption versionStr
|
||||||
|
(short 'v' <> long "version" <> help "Show the version number")
|
||||||
|
|
||||||
invocation :: Parser Invocation
|
invocation :: Parser Invocation
|
||||||
invocation =
|
invocation = Invocation
|
||||||
flag' Schedule
|
<$> flag Schedule Inventory
|
||||||
(long "schedule" <> help "Show when next to go for provisioning")
|
(short 'i' <> long "inventory" <> help "Show a full inventory")
|
||||||
<|> flag Inventory (Version versionStr)
|
<*> option (optional auto) (
|
||||||
(long "version" <> help "Show the version number")
|
value Nothing
|
||||||
|
<> short 'd'
|
||||||
|
<> long "date"
|
||||||
|
<> help "Evaluate the situation at that date"
|
||||||
|
)
|
||||||
|
|
||||||
invoked :: IO Invocation
|
invoked :: IO Invocation
|
||||||
invoked = execParser $
|
invoked = execParser $
|
||||||
info
|
info
|
||||||
(invocation <**> helper)
|
(invocation <**> version <**> helper)
|
||||||
(fullDesc <> header ("Pilu v" ++ versionStr))
|
(fullDesc <> header ("Pilu v" ++ versionStr))
|
||||||
|
|
|
@ -7,7 +7,7 @@ import CSV (Row(..))
|
||||||
import Control.Monad (foldM)
|
import Control.Monad (foldM)
|
||||||
import Data.List (isSuffixOf, takeWhile)
|
import Data.List (isSuffixOf, takeWhile)
|
||||||
import Data.Map (Map)
|
import Data.Map (Map)
|
||||||
import qualified Data.Map as Map (delete, empty, insert, lookup, toList)
|
import qualified Data.Map as Map (difference, empty, fromList, insert, lookup, toList)
|
||||||
import Data.Time (Day)
|
import Data.Time (Day)
|
||||||
import Medicine (MedicineName)
|
import Medicine (MedicineName)
|
||||||
import Text.ParserCombinators.Parsec.Error (Message(..))
|
import Text.ParserCombinators.Parsec.Error (Message(..))
|
||||||
|
@ -23,7 +23,7 @@ data Event = Event {
|
||||||
instance Row Event where
|
instance Row Event where
|
||||||
fromRow assoc = do
|
fromRow assoc = do
|
||||||
rowDate <- read <$> get "date"
|
rowDate <- read <$> get "date"
|
||||||
(kind, ints) <- foldM addAmount (Nothing, Map.empty) . Map.toList $ Map.delete "date" assoc
|
(kind, ints) <- foldM addAmount (Nothing, Map.empty) $ Map.toList medicineNames
|
||||||
maybe
|
maybe
|
||||||
(Left $ Expect "A prescription or provisioning for a medicine")
|
(Left $ Expect "A prescription or provisioning for a medicine")
|
||||||
(Right . Event rowDate ints)
|
(Right . Event rowDate ints)
|
||||||
|
@ -31,6 +31,8 @@ instance Row Event where
|
||||||
where
|
where
|
||||||
get key = maybe (Left $ Expect key) Right $ Map.lookup key assoc
|
get key = maybe (Left $ Expect key) Right $ Map.lookup key assoc
|
||||||
unexpected = Left . UnExpect . show
|
unexpected = Left . UnExpect . show
|
||||||
|
ignoredKeys = Map.fromList [("date", ""), ("comment", "")]
|
||||||
|
medicineNames = Map.difference assoc ignoredKeys
|
||||||
addAmount (evType, ints) (key, val) = do
|
addAmount (evType, ints) (key, val) = do
|
||||||
(newEvType, amount) <- readAmount val
|
(newEvType, amount) <- readAmount val
|
||||||
let newFloats = Map.insert key amount ints
|
let newFloats = Map.insert key amount ints
|
||||||
|
|
25
src/Main.hs
25
src/Main.hs
|
@ -1,11 +1,11 @@
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
module Main where
|
module Main where
|
||||||
|
|
||||||
import CLI (Invocation(..), invoked)
|
import CLI (Invocation(..), Mode(..), invoked)
|
||||||
import CSV (Row, parse)
|
import CSV (Row, parse)
|
||||||
import Data.Time (UTCTime(..), getCurrentTime)
|
import Data.Time (Day, UTCTime(..), getCurrentTime)
|
||||||
import Medicine (Pharmacy, pharmacy)
|
import Medicine (Pharmacy, pharmacy)
|
||||||
import System.Exit (exitFailure, exitSuccess)
|
import System.Exit (exitFailure)
|
||||||
import Schedule (schedule)
|
import Schedule (schedule)
|
||||||
import Timeline (State(..), stateAt)
|
import Timeline (State(..), stateAt)
|
||||||
import YAML (encode)
|
import YAML (encode)
|
||||||
|
@ -17,20 +17,19 @@ readCSV filePath = do
|
||||||
Left e -> (putStrLn $ show e) >> exitFailure
|
Left e -> (putStrLn $ show e) >> exitFailure
|
||||||
Right rows -> return rows
|
Right rows -> return rows
|
||||||
|
|
||||||
getState :: Pharmacy -> IO State
|
getState :: Pharmacy -> Maybe Day -> IO State
|
||||||
getState aPharmacy =
|
getState aPharmacy atDate =
|
||||||
stateAt
|
stateAt
|
||||||
<$> (utctDay <$> getCurrentTime)
|
<$> maybe (utctDay <$> getCurrentTime) return atDate
|
||||||
<*> (return aPharmacy)
|
<*> return aPharmacy
|
||||||
<*> readCSV "timeline.csv"
|
<*> readCSV "timeline.csv"
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
invocation <- invoked
|
invocation <- invoked
|
||||||
display <- case invocation of
|
|
||||||
Inventory -> return $ const encode
|
|
||||||
Schedule -> return $ \ph -> encode . schedule ph
|
|
||||||
Version version -> putStrLn version >> exitSuccess
|
|
||||||
thePharmacy <- pharmacy <$> readCSV "medicine.csv"
|
thePharmacy <- pharmacy <$> readCSV "medicine.csv"
|
||||||
theState <- getState thePharmacy
|
theState <- getState thePharmacy $ date invocation
|
||||||
putStrLn $ display thePharmacy theState
|
let display = case mode invocation of
|
||||||
|
Inventory -> encode
|
||||||
|
Schedule -> encode . schedule thePharmacy
|
||||||
|
putStrLn $ display theState
|
||||||
|
|
|
@ -4,7 +4,7 @@ module Schedule (
|
||||||
schedule
|
schedule
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.Map ((!), Map, elems, lookupMin, mapWithKey, partition, size, toList)
|
import Data.Map ((!), Map, elems, lookupMin, mapWithKey, partition, size)
|
||||||
import qualified Data.Map as Map (filter)
|
import qualified Data.Map as Map (filter)
|
||||||
import Data.Time (Day, addDays)
|
import Data.Time (Day, addDays)
|
||||||
import Medicine (Medicine(..), MedicineName, Pharmacy)
|
import Medicine (Medicine(..), MedicineName, Pharmacy)
|
||||||
|
@ -56,7 +56,7 @@ scheduleByMedicine pharmacy day medicineName stockLeft rate =
|
||||||
where
|
where
|
||||||
(Medicine {minStock, content}) = pharmacy ! medicineName
|
(Medicine {minStock, content}) = pharmacy ! medicineName
|
||||||
truncateF = fromInteger . truncate
|
truncateF = fromInteger . truncate
|
||||||
leftInBox = content - (truncateF (content / stockLeft) * stockLeft)
|
leftInBox = stockLeft - (truncateF (stockLeft / content) * content)
|
||||||
|
|
||||||
typeAndAmount :: Float -> Float -> Float -> (ScheduleType, Float)
|
typeAndAmount :: Float -> Float -> Float -> (ScheduleType, Float)
|
||||||
typeAndAmount stockLeft minStock leftInBox =
|
typeAndAmount stockLeft minStock leftInBox =
|
||||||
|
|
|
@ -51,7 +51,10 @@ setDay newDay state@(State {day, stock, consumptionRate}) = state {
|
||||||
max 0 $ initialAmount - duration * (consumptionRate ! medicineName)
|
max 0 $ initialAmount - duration * (consumptionRate ! medicineName)
|
||||||
|
|
||||||
stateAt :: Day -> Pharmacy -> [Event] -> State
|
stateAt :: Day -> Pharmacy -> [Event] -> State
|
||||||
stateAt targetDay pharmacy events =
|
stateAt targetDay pharmacy =
|
||||||
setDay targetDay lastState
|
setDay targetDay . lastState
|
||||||
where
|
where
|
||||||
lastState = foldl applyEvent (initState pharmacy) (sortOn date events)
|
lastState =
|
||||||
|
foldl applyEvent (initState pharmacy)
|
||||||
|
. sortOn date
|
||||||
|
. filter ((<= targetDay) . date)
|
||||||
|
|
Loading…
Reference in a new issue