ci: do not error when nothing with nitunit_some
[nit.git] / contrib / refund / src / refund_base.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2015 Alexandre Terrasa <alexandre@moz-code.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Insurance refunds calculation base classes.
18 module refund_base
19
20 import counter
21
22 # `RefundProcessor` manages the calculation of the refunds.
23 #
24 # See `process`.
25 class RefundProcessor
26
27 # Where to generate output file.
28 var output_file: String is noinit, writable
29
30 # Where to save usage statistics.
31 var stats_file = "stats.json"
32
33 # Processes the `input_file` and write the output in `output_file`.
34 #
35 # Steps:
36 #
37 # 1. Parses the input_file and check json validity (see `load_input`).
38 # 2. Instantiates and checks the reclamation sheet against client rules
39 # (see `ReclamationSheet.from_json`).
40 # 3. Processes refunds (see `proces_refunds`).
41 # 4. Writes the output file (see `write_output`).
42 fun process(input_file, output_file: String) is abstract
43
44 # Refunds allowed for the current reclamation sheet.
45 var current_refunds = new HashMap[Care, Dollar]
46
47 # Computes allowed refunds for a given `Reclamation` found in a `ReclamationSheet`.
48 fun process_refund(sheet: ReclamationSheet, recl: Reclamation): Dollar is abstract
49
50 # Shows stats values in console
51 fun show_stats do print load_stats
52
53 # Loads stats from file as a RefundStats instance.
54 fun load_stats: RefundStats is abstract
55
56 # Saves stats in file.
57 fun save_stats(stats: RefundStats) is abstract
58
59 # Outputs error object then exit.
60 fun die(msg: String) is abstract
61
62 # Clears stats.
63 #
64 # Basically delete the stats file.
65 fun clear_stats do if stats_file.file_exists then stats_file.file_delete
66 end
67
68 # Stats representation using a `Counter`.
69 class RefundStats
70 super Counter[String]
71 end
72
73 # A `Client` can ask for refunds from the insurance company.
74 class Client
75
76 # Client number.
77 var number: String
78
79 redef fun to_s do return "#{number}"
80 end
81
82 # A `ReclamationSheet` is filled by the `Client` to obtain a `RefundSheet`.
83 class ReclamationSheet
84
85 # File used for this refund.
86 var file: ReclFile is writable
87
88 # Month concerned by the refund.
89 var month: ReclMonth is writable
90
91 # Array of reclamations.
92 var recls = new Array[Reclamation] is writable
93
94 redef fun to_s do
95 return "Refund (file: {file}, month: {month}, recls: {recls.length})"
96 end
97 end
98
99 # A File found in a `ReclamationSheet`.
100 #
101 # A File points to a `Contract` and a `Client`.
102 #
103 # Allowed format is: `X12345` where `X` is the contract kind and `12345` is the
104 # client number.
105 class ReclFile
106
107 # File string id.
108 var id: String is writable
109
110 # Contract instance linked to this file.
111 var contract: Contract is noinit, writable
112
113 # Client instance linked to this file.
114 var client: Client is noinit, writable
115
116 # Returns the contract instance corresponding to `kind`.
117 fun contract_factory(proc: RefundProcessor, kind: String): Contract do
118 if kind == "A" then return new ContractA
119 if kind == "B" then return new ContractB
120 if kind == "C" then return new ContractC
121 if kind == "D" then return new ContractD
122 if kind == "E" then return new ContractE
123 proc.die("Unknown contract {kind}")
124 abort
125 end
126
127 redef fun to_s do return "{contract.kind}{client.number}"
128 end
129
130 # Month date formatted for contracts.
131 #
132 # Mainly used to factorize treatments on date calculation.
133 class ReclMonth
134
135 # Internal date used to store the month.
136 var date: ReclDate is writable
137
138 # Is `date` in this month?
139 fun has(date: ReclDate): Bool do return self.date.month == date.month
140
141 redef fun to_s do
142 if date.month < 10 then
143 return "{date.year}-0{date.month}"
144 end
145 return "{date.year}-0{date.month}"
146 end
147 end
148
149 # The date on which a `Care` occured.
150 class ReclDate
151 # Year of the month.
152 var year: Int is writable
153
154 # Month number (`1` is January).
155 var month: Int is writable
156
157 # Day number.
158 var day: Int is writable
159
160 redef fun to_s do
161 var res = new FlatBuffer
162 res.append "{year}-"
163 if month < 10 then
164 res.append "0{month}-"
165 else
166 res.append "{month}-"
167 end
168 if day < 10 then
169 res.append "0{day}"
170 else
171 res.append day.to_s
172 end
173 return res.write_to_string
174 end
175 end
176
177 # `RefundRecl` are parts of the `RefundReclamation`.
178 class Reclamation
179 # `Care` id concerned by this reclamation.
180 var care_id: Int is writable
181
182 # Date this care was applied.
183 var date: ReclDate is writable
184
185 # Amount of money given by the `Client` in exchange of this care.
186 var fees: Dollar is writable
187
188 redef fun to_s do return "Entry (care: {care_id}, date: {date}, fees: {fees})"
189 end
190
191 # A `Contract` specifies the refund applicable on care.
192 class Contract
193
194 # Kind of the contract (specified by a letter).
195 var kind: String is noinit, writable
196
197 # Covered cares for this kind of contract.
198 var cares = new Array[Care] is writable
199
200 # Adds a care to this contract.
201 fun add_care(care: Care) do cares.add care
202
203 # Gets a `Care` instance by its id.
204 #
205 # Returns `null` if no `Care` found.
206 fun care_by_id(id: Int): nullable Care do
207 for care in cares do
208 if care.match_id(id) then return care
209 end
210 return null
211 end
212
213 redef fun to_s do return "{kind} ({cares.length} cares)"
214 end
215
216 # Contracts
217 # FIXME move contracts to a JSON configuration file.
218
219 private class ContractA
220 super Contract
221
222 init do
223 kind = "A"
224 add_care(new UniqCare.with_vals(0, 25.0, null, null))
225 add_care(new UniqCare.with_vals(100, 35.0, null, 250.0.to_dollar))
226 add_care(new UniqCare.with_vals(150, 0.0, null, null))
227 add_care(new UniqCare.with_vals(175, 50.0, null, 200.0.to_dollar))
228 add_care(new UniqCare.with_vals(200, 25.0, null, 250.0.to_dollar))
229 add_care(new RangeCare.with_vals([300..399], 0.0, null, null))
230 add_care(new UniqCare.with_vals(400, 0.0, null, null))
231 add_care(new UniqCare.with_vals(500, 25.0, null, 150.0.to_dollar))
232 add_care(new UniqCare.with_vals(600, 40.0, null, 300.0.to_dollar))
233 add_care(new UniqCare.with_vals(700, 0.0, null, null))
234 end
235 end
236
237 private class ContractB
238 super Contract
239
240 init do
241 kind = "B"
242 add_care(new UniqCare.with_vals(0, 50.0, 40.0.to_dollar, null))
243 add_care(new UniqCare.with_vals(100, 50.0, 50.0.to_dollar, 250.0.to_dollar))
244 add_care(new UniqCare.with_vals(150, 0.0, null, null))
245 add_care(new UniqCare.with_vals(175, 75.0, null, 200.0.to_dollar))
246 add_care(new UniqCare.with_vals(200, 100.0,null, 250.0.to_dollar))
247 add_care(new RangeCare.with_vals([300..399], 50.0, null, null))
248 add_care(new UniqCare.with_vals(400, 0.0, null, null))
249 add_care(new UniqCare.with_vals(500, 50.0, 50.0.to_dollar, 150.0.to_dollar))
250 add_care(new UniqCare.with_vals(600, 100.0,null, 300.0.to_dollar))
251 add_care(new UniqCare.with_vals(700, 70.0, null, null))
252 end
253 end
254
255 private class ContractC
256 super Contract
257
258 init do
259 kind = "C"
260 add_care(new UniqCare.with_vals(0, 90.0, null, null))
261 add_care(new UniqCare.with_vals(100, 95.0, null, 250.0.to_dollar))
262 add_care(new UniqCare.with_vals(150, 85.0, null, null))
263 add_care(new UniqCare.with_vals(175, 90.0, null, 200.0.to_dollar))
264 add_care(new UniqCare.with_vals(200, 90.0, null, 250.0.to_dollar))
265 add_care(new RangeCare.with_vals([300..399], 90.0, null, null))
266 add_care(new UniqCare.with_vals(400, 90.0, null, null))
267 add_care(new UniqCare.with_vals(500, 90.0, null, 150.0.to_dollar))
268 add_care(new UniqCare.with_vals(600, 75.0, null, 300.0.to_dollar))
269 add_care(new UniqCare.with_vals(700, 90.0, null, null))
270 end
271 end
272
273 private class ContractD
274 super Contract
275
276 init do
277 kind = "D"
278 add_care(new UniqCare.with_vals(0, 100.0, 85.0.to_dollar, null))
279 add_care(new UniqCare.with_vals(100, 100.0, 75.0.to_dollar, 250.0.to_dollar))
280 add_care(new UniqCare.with_vals(150, 100.0, 150.0.to_dollar, null))
281 add_care(new UniqCare.with_vals(175, 95.0, null, 200.0.to_dollar))
282 add_care(new UniqCare.with_vals(200, 100.0, 100.0.to_dollar, 250.0.to_dollar))
283 add_care(new RangeCare.with_vals([300..399],100.0, null, null))
284 add_care(new UniqCare.with_vals(400, 100.0, 65.0.to_dollar, null))
285 add_care(new UniqCare.with_vals(500, 100.0, null, 150.0.to_dollar))
286 add_care(new UniqCare.with_vals(600, 100.0, 100.0.to_dollar, 300.0.to_dollar))
287 add_care(new UniqCare.with_vals(700, 100.0, 90.0.to_dollar, null))
288 end
289 end
290
291 private class ContractE
292 super Contract
293
294 init do
295 kind = "E"
296 add_care(new UniqCare.with_vals(0, 15.0, null, null))
297 add_care(new UniqCare.with_vals(100, 25.0, null, 250.0.to_dollar))
298 add_care(new UniqCare.with_vals(150, 15.0, null, null))
299 add_care(new UniqCare.with_vals(175, 25.0, 20.0.to_dollar, 200.0.to_dollar))
300 add_care(new UniqCare.with_vals(200, 12.0, null, 250.0.to_dollar))
301 add_care(new RangeCare.with_vals([300..399], 60.0, null, null))
302 add_care(new UniqCare.with_vals(400, 25.0, 15.0.to_dollar, null))
303 add_care(new UniqCare.with_vals(500, 30.0, 20.0.to_dollar, 150.0.to_dollar))
304 add_care(new UniqCare.with_vals(600, 15.0, null, 300.0.to_dollar))
305 add_care(new UniqCare.with_vals(700, 22.0, null, null))
306 end
307 end
308
309 # A `Care` is payed by the `Client` and can raises a `Refund`.
310 interface Care
311
312 # Does `id` is acceptable for this care?
313 fun match_id(id: Int): Bool is abstract
314
315 # Percent covered for this kind of care.
316 fun cover: Float is abstract
317
318 # Max amount covered for this kind of care by reclamation.
319 fun max: nullable Dollar is abstract
320
321 # Max amount covered for this kind of care by month.
322 fun month_max: nullable Dollar is abstract
323
324 # Computes the refund for this care.
325 fun process_refund(fees: Dollar): Dollar do
326 var max = self.max
327 var val = ((fees.value.to_f * (cover / 100.0)) / 100.0).to_dollar
328 if max != null and val > max then val = max
329 return val
330 end
331 end
332
333 # A `UniqCare` refers to one and only one kind of `Care`.
334 #
335 # For example, the care `Ostéopathie` as the uniq id `200`.
336 class UniqCare
337 super Care
338
339 # Care id.
340 var id: Int
341
342 redef fun match_id(id) do return self.id == id
343
344 redef var cover = 0.0
345 redef var max = null
346 redef var month_max = null
347
348 # Inits this `Care` with values.
349 #
350 # * `id`: the `Care` id.
351 # * `cover`: refund percentage covered for this `Care`.
352 # * `max`: max amount refunded for this `Care` in a reclamation sheet.
353 # * `month_max`: max amount refunded by month.
354 init with_vals(id: Int, cover: Float, max, month_max: nullable Dollar) do
355 init(id)
356 self.cover = cover
357 self.max = max
358 self.month_max = month_max
359 end
360
361 redef fun to_s do return id.to_s
362 end
363
364 # A `RangeCare` refers to a set of id corresponding to the same `Care`.
365 #
366 # For example, the care `Soins Dentaires` is refered by the ids 300 to 399.
367 class RangeCare
368 super Care
369
370 # Care id range.
371 var id: Range[Int]
372
373 redef fun match_id(id) do return self.id.has(id)
374 redef var cover = 0.0
375 redef var max = null
376 redef var month_max = null
377
378 # Inits this `Care` with values.
379 #
380 # * `id`: the `Care` id.
381 # * `cover`: refund percentage covered for this `Care`.
382 # * `max`: max amount refunded for this `Care` in a reclamation sheet.
383 # * `month_max`: max amount refunded by month.
384 init with_vals(id: Range[Int], cover: Float, max, month_max: nullable Dollar) do
385 init(id)
386 self.cover = cover
387 self.max = max
388 self.month_max = month_max
389 end
390
391 redef fun to_s do return id.first.to_s
392 end
393
394 # Used to represent currencies values.
395 class Dollar
396 super Comparable
397
398 redef type OTHER: Dollar
399
400 # Amount of cents.
401 var value: Int
402
403 # Inits `self` from a float `value`.
404 init from_float(value: Float) do
405 init((value * 100.0).to_i)
406 end
407
408 redef fun to_s do return "{value / 100}.{value % 100}$"
409 redef fun <(o) do return value < o.value
410
411 # Dollars addition.
412 fun +(o: Dollar): Dollar do return new Dollar(value + o.value)
413
414 # Dollars substraction.
415 fun -(o: Dollar): Dollar do return new Dollar(value - o.value)
416 end
417
418 redef class Float
419 # Returns `self` as a Dollar instance.
420 fun to_dollar: Dollar do return new Dollar.from_float(self)
421 end