Compare commits

..

4 commits

7 changed files with 55 additions and 30 deletions

View file

@ -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.

View file

@ -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

View file

@ -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))

View file

@ -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

View file

@ -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

View file

@ -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 =

View file

@ -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)