From b36973b3e08e6d1f8a7d42a6984249486d0cebfe Mon Sep 17 00:00:00 2001 From: Igor Pashev Date: Thu, 22 Jun 2017 12:24:49 +0300 Subject: Initial commit --- .gitignore | 4 + ChangeLog.md | 5 + LICENSE | 13 ++ README.md | 74 ++++++++++ Setup.hs | 10 ++ cmd/Main.hs | 76 ++++++++++ doc/plot.md | 312 +++++++++++++++++++++++++++++++++++++++++ lib/Malodivo/Budget.hs | 101 +++++++++++++ lib/Malodivo/Types/Bill.hs | 44 ++++++ lib/Malodivo/Types/District.hs | 41 ++++++ lib/Malodivo/Types/Ministry.hs | 32 +++++ malodivo.cabal | 73 ++++++++++ sample/simpleBudget.json | 23 +++ stack.yaml | 8 ++ test/doctests.hs | 14 ++ 15 files changed, 830 insertions(+) create mode 100644 .gitignore create mode 100644 ChangeLog.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Setup.hs create mode 100644 cmd/Main.hs create mode 100644 doc/plot.md create mode 100644 lib/Malodivo/Budget.hs create mode 100644 lib/Malodivo/Types/Bill.hs create mode 100644 lib/Malodivo/Types/District.hs create mode 100644 lib/Malodivo/Types/Ministry.hs create mode 100644 malodivo.cabal create mode 100644 sample/simpleBudget.json create mode 100644 stack.yaml create mode 100644 test/doctests.hs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dbaee1d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.stack-work +/dist +/dist-newstyle +cabal.*.local diff --git a/ChangeLog.md b/ChangeLog.md new file mode 100644 index 0000000..eb7aca3 --- /dev/null +++ b/ChangeLog.md @@ -0,0 +1,5 @@ +0.0.0 +===== + + * Initial version. Can only process one bill with no constains. + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c6c7def --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/README.md b/README.md new file mode 100644 index 0000000..dd87c22 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +Malodivo +======== + +Budget planning in a fairy Kingdom of Malodivo. +For the whole story read the [plot](./doc/plot.md). + + +Requirements +============ + +Malodivo is written in Haskell with [GHC](http://www.haskell.org/ghc/). +All required Haskell libraries are listed in [malodivo.cabal](malodivo.cabal). +Use [cabal-install](http://www.haskell.org/haskellwiki/Cabal-Install) to fetch +and build all pre-requisites automatically. + + +Command-line utility +==================== + +The command-line utility `malodivo` provides a means to process input JSON +files and output JSON describing the actual amounts that go towards each bill +by each district. This utility reads input JSON data from standard input +and writes output JSON data to standard output. _The format of output is +unstable and subject to change_. + +Usage +----- + +``` +Usage: malodivo [options] < input.json > output.json + +Options: + + -h, --help Show this message and exit + +``` + + +Examples +-------- + +We would get this: +``` +$ malodivo < sample/simpleBudget.json +[["Lakos",100],["Palolene",66],["SouthernPalolene",133]] +``` + +with this file ([sample/simpleBudget.json](sample/simpleBudget.json)): +```json +{ + "bills": [ + { + "name": "An Act to Construct the Great Wall of Malodivo", + "ministry": "Defense", + "amount": 300 + } + ], + "districts": [ + { + "name": "Palolene", + "availableFunds": 200 + }, + { + "name": "SouthernPalolene", + "availableFunds": 400 + }, + { + "name": "Lakos", + "availableFunds": 300 + } + ] +} +``` + diff --git a/Setup.hs b/Setup.hs new file mode 100644 index 0000000..49d3f2f --- /dev/null +++ b/Setup.hs @@ -0,0 +1,10 @@ +{-# LANGUAGE CPP #-} + +module Main + ( main + ) where + +import Distribution.Extra.Doctest (defaultMainWithDoctests) + +main :: IO () +main = defaultMainWithDoctests "doctests" diff --git a/cmd/Main.hs b/cmd/Main.hs new file mode 100644 index 0000000..bbc45cb --- /dev/null +++ b/cmd/Main.hs @@ -0,0 +1,76 @@ +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE QuasiQuotes #-} + +module Main + ( main + ) where + +import Control.Arrow ((&&&)) +import Control.Monad (when) +import Data.Version (showVersion) +import GHC.Generics (Generic) +import System.Environment (getArgs) +import System.Exit (die) + +import Data.Aeson (FromJSON, eitherDecode, encode) +import qualified Data.ByteString.Lazy as L +import qualified Data.HashMap.Strict as HM +import qualified System.Console.Docopt.NoTH as O +import Text.InterpolatedString.Perl6 (qc) + +import Malodivo.Budget (manyToOne) +import Malodivo.Types.Bill (Bill) +import Malodivo.Types.District (District) + +import Paths_malodivo (version) -- from cabal + +usage :: String +usage = + "malodivo " ++ + showVersion version ++ + " - budget planning tool for the Kingdom of Malodivo" ++ + [qc| + +This utility reads input JSON data from standard input +and writes output JSON data to standard output. + +Usage: malodivo [options] < input.json > output.json + +Options: + + -h, --help Show this message and exit + + +|] + +data DistrictInfo = DistrictInfo + { name :: District + , availableFunds :: Integer + } deriving (Generic, FromJSON) + +data SimpleInput = SimpleInput + { bills :: [Bill] + , districts :: [DistrictInfo] + } deriving (Generic, FromJSON) + +process :: IO () +process = do + input <- L.getContents + case eitherDecode input of + Left err -> die err + Right si -> do + let nbills = length . bills $ si + funds = + HM.fromListWith (+) . map (name &&& availableFunds) $ districts si + when (nbills /= 1) $ die "We needs exactly one bill in input" + when (HM.null funds) $ die "We needs at least one district" + L.putStr . encode $ manyToOne funds (head . bills $ si) + +main :: IO () +main = do + doco <- O.parseUsageOrExit usage + args <- O.parseArgsOrExit doco =<< getArgs + if args `O.isPresent` O.longOption "help" + then putStrLn $ O.usage doco + else process diff --git a/doc/plot.md b/doc/plot.md new file mode 100644 index 0000000..4f1139e --- /dev/null +++ b/doc/plot.md @@ -0,0 +1,312 @@ +# Capital Match Hiring Coding Challenge + +## Introduction + +Archaeologists and anthropologists would have a hard time believing that +millenia ago, there was already a healthy and booming democracy. Yet recent +evidence surfaced suggested that in the ancient Kingdom of Malodivo, the +democratic way of life was the norm, even though it is a bit peculiar by +modern standards. Not much was known about this Kingdom; about the only thing +we have fully figured out was its parliamentary system of governance, owing to +the fact that the sole piece of written evidence was the Kingdom's +*Parliamentary Gazette*, a document recording the discussions and resolutions +of the various bills in its Parliament. + +Reading through the *Gazette*, we can now piece together a few tidbits of the +workings of the Parliament. The Parliament apparently meets every once in a +while (the exact frequency is unknown because we have not yet figured out +conversions between the Gregorian calendar and the calendar system used in the +Kingdom), and the members, who represent the constituents in districts, submit +bills for discussion and debate. The implementation of a bill requires +funding, naturally. Such funding is provided by the taxes paid by the members +of the parliament which ultimately comes from their constituents. In other +words, instead of each citizen paying taxes individually to a central +authority, each citizen pays the taxes to their elected members of the +Parliament. + +Different members of the parliament have different priorities and have +different levels of support for each bill. They signal their varying degrees +of support through funding decisions: they would pledge different amounts of +their constituents' tax dollars to each bill. It would be easy if the sum of +the funds needed by all bills is equal to all the taxes collected; in this +case, the members' wishes could be fulfilled exactly. However, more often than +not, this does not happen. Owing to a booming economy, the total amount of +funding required for all bills is often less than the total tax paid by all +members. In this case, a widely cherished Article in the *Tax Code of the +Kingdom of Malodivo* comes into effect: + +> Article XIV. All taxes collected by the Government but not used for the + implementation of any bill shall be refunded. + +Since the Parliament usually cannot pass enough bills to deplete the total +taxes it collected, it will simply reduce the amount of tax paid, and the +members will have a leftover amount. The citizens are happy because their +effective tax rate is reduced. It is important that once the Parliament has +decided on the funding needed for a bill, the bill cannot legally receive +extra funds; it is however possible that a bill will receive less funds than +the Parliament decides. + +Your task, is to write a program to determine the best combination of the +funds from each member of the Parliament that go to each bill. + +## Scenario + +Suppose in the 137th Parliamentary Session of the Kingdom of Malodivo, four +bills were introduced and passed: + +| Name of Bill | Category | Funds Needed | +| ------------------------------------------------------ | -------- | ------------ | +| An Act to Construct the Great Wall of Malodivo | Defense | ₡200,000 | +| An Act to Construct Shelters for the Homeless | Welfare | ₡40,000 | +| An Act to Fund the Development of Longer-Lasting Paper | Science | ₡14,000 | +| An Act to Increase Retirement Benefits for Veterans | Welfare | ₡90,000 | + +As one could calculate, the total fund needed to implement these bills is +₡344,000. Although in this example the sum happens to be a multiple of a +thousand, generally the funds needed could be any nonnegative integer. (It's +quite trivial to handle a bill that needs no funding: every member of the +Parliament simply contributes ₡0 to the bill.) + +Each member of the Parliament also has a total amount of taxes collected: + +| District | Total Available Funds | +| ----------------------------- | --------------------- | +| District of Palolene | ₡200,000 | +| District of Southern Palolene | ₡150,000 | +| District of Lakos | ₡400,000 | + +For each member of the parliament, the member has listened to his/her +constituents in a district and has decided how much of the taxes should go to +each category. There is a bijection between each member and each district, so +these two terms may be used interchangeably. For example, the District of +Palolene has the following table of funds allocation: + +| Category | Funding Provided to *Each* Bill In This Category | +| -------- | ------------------------------------------------ | +| Defense | ₡1,000 | +| Welfare | ₡3,000 | +| Science | ₡5,000 | + +However, the district could also provide specific funding amount for each bill: + +| Name of Bill | Category | Funding Provided | +| ------------------------------------------------------ | -------- | ------------------------ | +| An Act to Construct the Great Wall of Malodivo | Defense | Use default for category | +| An Act to Construct Shelters for the Homeless | Welfare | Use default for category | +| An Act to Fund the Development of Longer-Lasting Paper | Science | ₡7,500 | +| An Act to Increase Retirement Benefits for Veterans | Welfare | ₡500 | + +As you can see, the District of Palolene does not have a large population of +veterans, so the constituents have collectively decided to provide fewer funds +for this bill. In certain cases, the constituents may even decide to provide +zero funding to a bill they vehemently dislike. On the other hand, the +District decides that a durable writing medium is so important that it will +allocate ₡7,500 instead of ₡5,000 to the bill. Indeed a district could support +a bill so enthusiastically that it will decide to provide a higher amount than +the default for the category. However in any case the amount will never be +negative. + +Usually the total funding as decided above across all districts for a +particular bill will exceed the amount required for that bill. In this case, +you should reduce the contribution by each district proportionally. In other +words, the proportions of the actual contribution among each district should +be as similar as possible as the amount each district originally decides to +provide. For example if a bill needs ₡10, but there are two districts and they +are willing to provide ₡400 and ₡600 respectively, then these two districts +will actually contribute only ₡4 and ₡6, satisfying the principle of +proportionality. Again, since only the smallest unit of currency in this +Kingdom is ₡1, all amounts are integers, and so suitable rounding needs to be +performed. + +It should further be noted that the example above shows a prosperous year, +when the citizens have made good money and have a lot of taxes to pay. It is +quite possible that during a depression, the total available funds could +dwindle to such small amount that they could not fund all the bills the +Parliament has passed. In this case, the same principle of proportionality +applies. For example if there are two bills and a district wishes to provide +₡200 for one bill and ₡500 for another, but it only has ₡7 in total tax dollar +collected, then it should contribute only ₡2 and ₡5 respectively towards these +two bills. The issue with rounding also applies. + +Different categories of the bills will generally be implemented by different +Ministries. For example the Ministry of Defense will receive the funds for the +bills in the Defense category, etc. (There is a bijection between the +categories of the bills and the ministries, so they could also be used +interchangeably.) However, the citizens of the Kingdom of Malodivo are +generally suspicious of corruption. They are worried that giving too much +money to a particular Ministry will tempt its Ministers and staff to be +corrupt and misuse the funds. Therefore, each District also has upper limits +on how much money they will provide for each Ministry. The following table +shows the limits for the District of Palolene: + +| Ministry | Maximum Funding Provided To All Bills In This Category | +| -------- | ------------------------------------------------------ | +| Defense | ₡3,000 | +| Welfare | ₡3,199 | +| Science | ₡15,000 | + +For example, the maximum funding for all bills in the Welfare category is +capped at ₡3,199. Yet for the two bills in the Welfare category, the District +of Palolene has decided to fund ₡500 for one of them and ₡3,000 for the other. +The sum, ₡3,500, exceeds the maximum funding limit, ₡3,199. In this case, the +maximum funding limit takes precedence; the actual funding provided will sum +to ₡3,199, and ideally should be ₡457 and ₡2,742 respectively due to the +principle of proportionality. It goes without saying that rounding issues also +apply. + +The interaction between the three principles of proportionality may not always +work out so well. It is your choice which proportionality is prioritized, or +you can even devise a better principle of proportionality that takes into +account all three in a balanced way. + +In the unlikely event that across all members, the total funding provided to a +certain Ministry is capped so low that bills could not receive the full +funding, the situation should be left as is. The Ministry will simply have +less funds available to implement the bill. For example, suppose that the +suspicion towards the Ministry of Welfare is widespread, and other members +have similarly low caps for Welfare bills, then it is quite possible that the +Ministry of Welfare could not receive enough funds; it'll just have to make do +with that and heed this warning to reduce corruption, or potentially face the +deprivation of funds and the Minister could be forced to resign. + +## Requirement + +Your program will read the above data in the format of a JSON document, +perform computation, and write the result in a JSON document. + +You have the freedom to decide the schema for your input and output JSON +documents. + +As an example, you can use the following as a starting point but feel free to +change things as you see fit: + +```js +{ + "bills": [{ + "name": "An Act to Construct the Great Wall of Malodivo", + "category": "Defense", + "amount": 200000 + }, + { + "name": "An Act to Construct Shelters for the Homeless", + "category": "Welfare", + "amount": 40000 + }, + { + "name": "An Act to Fund the Development of Longer-Lasting Paper", + "category": "Science", + "amount": 14000 + }, + { + "name": "An Act to Increase Retirement Benefits for Veterans", + "category": "Welfare", + "amount": 90000 + }], + "districts": [{ + "name": "Palolene", + "availableFunds": 200000, + "categoryDefaultFunding": [{ + "category": "Defense", + "amount": 1000 + }, + { + "category": "Welfare", + "amount": 3000 + }, + { + "category": "Science", + "amount": 5000 + }], + "billSpecificFunding": [{ + "bill": "An Act to Increase Retirement Benefits for Veterans", + "amount": 500 + }, + { + "bill": "An Act to Fund the Development of Longer-Lasting Paper", + "amount": 7500 + }], + "caps": [{ + "category": "Defense", + "amount": 3000 + }, + { + "category": "Welfare", + "amount": 3199 + }, + { + "category": "Science", + "amount": 15000 + }] + }] +} +``` + +The schema for the output is entirely decided by you, but at the very least +you should include the actual amounts that go towards each bill by each +district. You may optionally include other helpful information such as whether +a bill is fully funded, or the remaining balance of each district, or anything +else that you see fit. + +### A Simplification + +It should also be noted that there are very few *hard requirements*. In fact +there are only two: + +1. reading and writing data must be done in JSON; + +2. the program must be written in Haskell, but feel free to use any GHC + extensions or open-source Haskell libraries. + +As long as you fulfill these two requirements, we are interested in taking a +look at your code and having further discussions. + +Besides these two, you should exercise your liberty in trying to follow the +description above. If you deviate from the above (likely), you should document +them either as comments in the program or in a separate writeup. + +Efficiency is not a primary concern here. We do not expect any particular +Big-O complexity, but rather ask that your program finishes running in a +reasonable human-scale of time, i.e. at most a minute or two. + +For your convenience, we have also provided a simplification of the problem +that could serve as a nice starting point: + +* You may assume there are no maximum funding limits or bill-specific + amounts; + +* There is only one bill; + +* It just so happens that the amount for the bill (i.e. + funds needed) is greater than or equal to the sum of the amounts for each + district (i.e. total funds provided). + +You should first get this simplification working, and then gradually add more +features and more complexities. Choose which features to implement according to +your taste. It's perfectly fine even if you do not implement everything. We +just want to see some working code, your design decisions, and your thought +processes. + +## Unspecified Behavior + +You should have noticed that this document does not specify every detail. For +example it specifies that the actual proportion should be as similar as +possible to the specified proportions, but the precise measure of similarity +is not specified here. You may use a widely used metric such as the L2 norm, +but also feel free to come up with your own. For many places where the +document is vague, it could very well be intentional and please pick anything +that is reasonable. + +## Submission + +You are given a week to ponder and solve this. If you finish early, feel free +to tell us; if you need a bit of extra time, also feel free to tell us so we +can arrange an extension. + +Once you are done, please put your code in a private GitHub/Bitbucket +repository or email us the code. We will look through your code. If we like +what you have written, we will then schedule a chat over Hangouts or Skype and +we will have further discussions about the code; otherwise we will provide +written feedback to let you know why we think you are not a good fit for the +role. + +Good luck! diff --git a/lib/Malodivo/Budget.hs b/lib/Malodivo/Budget.hs new file mode 100644 index 0000000..edc0668 --- /dev/null +++ b/lib/Malodivo/Budget.hs @@ -0,0 +1,101 @@ +{-| + +Budget planning in the Kingdom of Malodivo. + +-} +module Malodivo.Budget + ( DistrictFunds + , manyToOne + ) where + +import qualified Data.HashMap.Strict as HM + +import qualified Malodivo.Types.Bill as B +import qualified Malodivo.Types.District as D + +-- | Convenient type. +type DistrictFunds = HM.HashMap D.District Integer + +{-| + +Trivial case: many districts, one bill, no contraints (wishes, +limits). We assume that, with no explicit wishes, each district +wants to contribute all its funds. + +>>> :set -XOverloadedStrings +>>> import qualified Data.HashMap.Strict as HM +>>> import qualified Malodivo.Types.Bill as B +>>> import qualified Malodivo.Types.District as D +>>> import qualified Malodivo.Types.Ministry as M + +>>> let medium = B.Bill { B.amount = 30, B.name = "A medium bill", B.ministry = M.Science } +>>> let one = B.Bill { B.amount = 1, B.name = "A trivial bill", B.ministry = M.Welfare } + + +If any district can pay the bill, take funds in proportion. We use +'HM.lookup', because 'show' of 'HM.HashMap' is not determinate, +and the test can occasionally fail: + +>>> let funds = HM.fromList [(D.Palolene, 100), (D.Lakos, 200)] +>>> let contribution = manyToOne funds medium +>>> HM.lookup D.Palolene contribution +Just 10 +>>> HM.lookup D.Lakos contribution +Just 20 + +>>> let funds = HM.fromList [(D.Palolene, 30), (D.Lakos, 30)] +>>> HM.elems $ manyToOne funds medium +[15,15] + + +It works with a single district: + +>>> let funds = HM.fromList [(D.SouthernPalolene, 500)] +>>> HM.elems $ manyToOne funds medium +[30] +>>> HM.elems $ manyToOne funds one +[1] + + +__TODO__ It /should/ not have rounding issues. In particular, +when the bill's amount is bigger than the number of districts, +/each/ district would contribute some. This problem is known as +. + +>>> let funds = HM.fromList [(D.Palolene, 10000), (D.Lakos, 1)] +>>> B.amount medium > fromIntegral (HM.size funds) +True +>>> let contribution = manyToOne funds medium +>>> let taken = HM.foldl' (+) 0 contribution + + +But at the moment we make use of some freedom coming from the fact +that \"it is possible that a bill will receive less funds than the +Parliament decides\", and round down contribution of each district. +/Thus these two tests show 'False', while should show 'True':/ + +>>> HM.null $ HM.filter (== 0) contribution +False +>>> taken == B.amount medium +False + + +If all districts together can't pay the bill, take all their money. +Note that due to the principle of proportionality it is impossible +that some districts can pay their shares and others can't: + +>>> let low = HM.fromList [(D.Palolene, 10), (D.Lakos, 15)] +>>> manyToOne low medium == low +True + +-} +manyToOne :: + DistrictFunds -- ^ Amounts of available funds per district. + -> B.Bill -- ^ A bill requiring funding. + -> DistrictFunds -- ^ Contribution of each district. +manyToOne funds bill = HM.map takeMoney funds + where + needed = B.amount bill + available = sum $ HM.elems funds + requested = min needed available + takeMoney m = requested * m `div` available diff --git a/lib/Malodivo/Types/Bill.hs b/lib/Malodivo/Types/Bill.hs new file mode 100644 index 0000000..336c0fb --- /dev/null +++ b/lib/Malodivo/Types/Bill.hs @@ -0,0 +1,44 @@ +{-| + +A bill is a proposed law put before the Parliament to consider and +possibly implement. Bills can be encoded to and decoded from JSON. + +>>> :set -XOverloadedStrings +>>> import Data.Aeson (decode, encode) +>>> import Data.Maybe (fromJust) +>>> import Malodivo.Types.Ministry (Ministry(..)) + +>>> let billGreateWall = Bill { name = "The Great Wall of Malodivo", ministry = Defense, amount = 4000 } +>>> encode billGreateWall +"{\"amount\":4000,\"name\":\"The Great Wall of Malodivo\",\"ministry\":\"Defense\"}" + +>>> let billShelters = fromJust $ decode "{\"amount\":1234,\"name\":\"Shelters for the Homeless\",\"ministry\":\"Welfare\"}" +>>> billShelters :: Bill +Bill {name = "Shelters for the Homeless", ministry = Welfare, amount = 1234} + +>>> ministry <$> [billShelters, billGreateWall] +[Welfare,Defense] + +>>> sum $ amount <$> [billShelters, billGreateWall] +5234 + +-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} + +module Malodivo.Types.Bill + ( Bill(..) + ) where + +import GHC.Generics (Generic) + +import Data.Aeson (FromJSON, ToJSON) +import Data.Text (Text) + +import Malodivo.Types.Ministry (Ministry) + +data Bill = Bill + { name :: Text -- ^ the name of a bill, e. g. \"An Act to Construct the Great Wall of Malodivo\". + , ministry :: Ministry -- ^ the ministry getting funds to implement a bill. + , amount :: Integer -- ^ the amount of funds required to implement a bill. + } deriving (Show, Generic, FromJSON, ToJSON) diff --git a/lib/Malodivo/Types/District.hs b/lib/Malodivo/Types/District.hs new file mode 100644 index 0000000..a46628d --- /dev/null +++ b/lib/Malodivo/Types/District.hs @@ -0,0 +1,41 @@ +{-| +Districts can be encoded to and decoded from JSON: + +>>> import Data.Aeson (decode, encode) +>>> import Data.ByteString.Lazy.Char8 (pack) + +>>> encode Palolene +"\"Palolene\"" + +>>> encode [ Lakos, SouthernPalolene ] +"[\"Lakos\",\"SouthernPalolene\"]" + +>>> decode . pack $ "[ \"Lakos\" ]" :: Maybe [District] +Just [Lakos] +-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} + +module Malodivo.Types.District + ( District(..) + ) where + +import GHC.Generics (Generic) + +import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey) +import Data.Hashable (Hashable) + +-- | District of the Kindom of Malodivo. +data District + = Palolene + | SouthernPalolene + | Lakos + deriving ( Eq + , Hashable + , Show + , Generic + , FromJSON + , FromJSONKey + , ToJSON + , ToJSONKey + ) diff --git a/lib/Malodivo/Types/Ministry.hs b/lib/Malodivo/Types/Ministry.hs new file mode 100644 index 0000000..c3e315b --- /dev/null +++ b/lib/Malodivo/Types/Ministry.hs @@ -0,0 +1,32 @@ +{-| +Ministries can be encoded to and decoded from JSON: + +>>> import Data.Aeson (decode, encode) +>>> import Data.ByteString.Lazy.Char8 (pack) + +>>> encode Defense +"\"Defense\"" + +>>> encode [ Defense, Welfare ] +"[\"Defense\",\"Welfare\"]" + +>>> decode . pack $ "[ \"Science\" ]" :: Maybe [Ministry] +Just [Science] +-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} + +module Malodivo.Types.Ministry + ( Ministry(..) + ) where + +import GHC.Generics (Generic) + +import Data.Aeson (FromJSON, ToJSON) + +-- | Ministry of the Kingdom of Malodivo. +data Ministry + = Defense + | Science + | Welfare + deriving (Show, Generic, FromJSON, ToJSON) diff --git a/malodivo.cabal b/malodivo.cabal new file mode 100644 index 0000000..31047be --- /dev/null +++ b/malodivo.cabal @@ -0,0 +1,73 @@ +name: malodivo +version: 0.0.0 +synopsis: Budget planning in the Kingdom of Malodivo +description: Once upon a time, in a galaxy far, far away, + where no man has gone before. +license: PublicDomain +license-file: LICENSE +author: Igor Pashev +maintainer: Igor Pashev +copyright: 2017, Igor Pashev +category: Math, Finance, Utils +build-type: Custom +cabal-version: >= 1.24 +extra-source-files: + ChangeLog.md + README.md + doc/plot.md + sample/*.json + +custom-setup + setup-depends: + base >= 4.9 && < 5 + , Cabal + , cabal-doctest >= 1.0.2 + +test-suite doctests + type: exitcode-stdio-1.0 + main-is: doctests.hs + build-depends: + base + , doctest >= 0.11.1 + , bytestring + ghc-options: -Wall + hs-source-dirs: test + default-language: Haskell2010 + +flag cmd + description: Build the command-line utility. + default: True + +library + default-language: Haskell2010 + ghc-options: -Wall + hs-source-dirs: lib + build-depends: + base >= 4.9 && < 5 + , aeson + , hashable + , text + , unordered-containers + exposed-modules: + Malodivo.Budget + Malodivo.Types.Bill + Malodivo.Types.District + Malodivo.Types.Ministry + +executable malodivo + default-language: Haskell2010 + ghc-options: -Wall -static + hs-source-dirs: cmd + main-is: Main.hs + if flag(cmd) + build-depends: + base >= 4.9 && < 5 + , aeson + , bytestring + , docopt + , interpolatedstring-perl6 + , malodivo + , unordered-containers + else + buildable: False + diff --git a/sample/simpleBudget.json b/sample/simpleBudget.json new file mode 100644 index 0000000..551898a --- /dev/null +++ b/sample/simpleBudget.json @@ -0,0 +1,23 @@ +{ + "bills": [ + { + "name": "An Act to Construct the Great Wall of Malodivo", + "ministry": "Defense", + "amount": 300 + } + ], + "districts": [ + { + "name": "Palolene", + "availableFunds": 200 + }, + { + "name": "SouthernPalolene", + "availableFunds": 400 + }, + { + "name": "Lakos", + "availableFunds": 300 + } + ] +} diff --git a/stack.yaml b/stack.yaml new file mode 100644 index 0000000..2b0a8d6 --- /dev/null +++ b/stack.yaml @@ -0,0 +1,8 @@ +resolver: lts-8.17 +packages: +- '.' + +extra-deps: [] +flags: {} +extra-package-dbs: [] + diff --git a/test/doctests.hs b/test/doctests.hs new file mode 100644 index 0000000..4b5d604 --- /dev/null +++ b/test/doctests.hs @@ -0,0 +1,14 @@ +module Main + ( main + ) where + +import Build_doctests (flags, module_sources, pkgs) +import Data.Foldable (traverse_) +import Test.DocTest (doctest) + +main :: IO () +main = do + traverse_ putStrLn args + doctest args + where + args = flags ++ pkgs ++ module_sources -- cgit v1.2.3