summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRutger Broekhoff2026-01-08 22:57:34 +0100
committerRutger Broekhoff2026-01-08 22:57:34 +0100
commitb4bc6aecbfc4dd78409085221a8b88ee4129b171 (patch)
tree4d244dadc7b1c877646f29fcdaf6fa57209a3c0b
parentf255ac3e6c6283812287139273cd09345c8f82e0 (diff)
downloadrdcapsis-b4bc6aecbfc4dd78409085221a8b88ee4129b171.tar.gz
rdcapsis-b4bc6aecbfc4dd78409085221a8b88ee4129b171.zip
Pre-destruction commit #2
-rw-r--r--lib/ledger.ml256
-rw-r--r--lib/ledger.mli.bak (renamed from lib/ledger.mli)0
2 files changed, 164 insertions, 92 deletions
diff --git a/lib/ledger.ml b/lib/ledger.ml
index 54a030e..7de131f 100644
--- a/lib/ledger.ml
+++ b/lib/ledger.ml
@@ -187,28 +187,34 @@ module Gh = struct
187 type global 187 type global
188 188
189 (* The five top-level categories *) 189 (* The five top-level categories *)
190 type asset 190 type asset [@@deriving sexp_of]
191 type equity 191 type equity [@@deriving sexp_of]
192 type expense 192 type expense [@@deriving sexp_of]
193 type income 193 type income [@@deriving sexp_of]
194 type liability 194 type liability [@@deriving sexp_of]
195
196 (* Subcategories of assets *)
197 type bank [@@deriving sexp_of]
195 198
196 (* No subcategories *) 199 (* No subcategories *)
197 type final 200 type final [@@deriving sexp_of]
198end 201end
199 202
200module Account_structure0 (F : sig 203module Account_structure0 (F : sig
201 type 'a t 204 type 'a t [@@deriving sexp_of]
202end) = 205end) =
203struct 206struct
204 type 'a f = 207 type 'a f =
205 | Accounts_payable : Gh.final f F.t -> Gh.liability f 208 | Accounts_payable : Gh.final f F.t -> Gh.liability f
206 | Accounts_receivable : Gh.final f F.t -> Gh.asset f 209 | Accounts_receivable : Gh.final f F.t -> Gh.asset f
207 | Bank : Gh.final f F.t -> Gh.asset f 210 | Bank : Gh.bank f F.t -> Gh.asset f
208 | Cash : Gh.final f F.t -> Gh.asset f 211 | Cash : Gh.final f F.t -> Gh.asset f
209 | Credit : Gh.final f F.t -> Gh.liability f 212 | Credit : Gh.final f F.t -> Gh.liability f
210 | Mutual_fund : Gh.final f F.t -> Gh.asset f 213 | Mutual_fund : Gh.final f F.t -> Gh.asset f
211 | Stock : Gh.final f F.t -> Gh.asset f 214 | Stock : Gh.final f F.t -> Gh.asset f
215 | Savings : Gh.final f F.t -> Gh.bank f
216 | Checking : Gh.final f F.t -> Gh.bank f
217 [@@deriving sexp_of]
212 218
213 type t0 = 219 type t0 =
214 | Asset of Gh.asset f F.t 220 | Asset of Gh.asset f F.t
@@ -216,18 +222,19 @@ struct
216 | Expense of Gh.expense f F.t 222 | Expense of Gh.expense f F.t
217 | Income of Gh.income f F.t 223 | Income of Gh.income f F.t
218 | Liability of Gh.liability f F.t 224 | Liability of Gh.liability f F.t
225 [@@deriving sexp_of]
219end 226end
220 227
221module Account_type = struct 228module Account_type = struct
222 type 'a elem = Leaf | Node of 'a 229 type 'a elem = Leaf | Node of 'a [@@deriving sexp_of]
223 230
224 include Account_structure0 (struct 231 include Account_structure0 (struct
225 type 'a t = 'a elem 232 type 'a t = 'a elem [@@deriving sexp_of]
226 end) 233 end)
227end 234end
228 235
229module Account_structure (F : sig 236module Account_structure (F : sig
230 type 'a t 237 type 'a t [@@deriving sexp_of]
231end) = 238end) =
232struct 239struct
233 include Account_structure0 (F) 240 include Account_structure0 (F)
@@ -272,14 +279,36 @@ struct
272 | Stock v -> 279 | Stock v ->
273 let%map c, v' = f.car v (fun el -> mkt (Account_type.Stock el)) in 280 let%map c, v' = f.car v (fun el -> mkt (Account_type.Stock el)) in
274 (c, Stock v') 281 (c, Stock v')
282 | Savings v ->
283 let%map c, v' = f.car v (fun el -> mkt (Account_type.Savings el)) in
284 (c, Savings v')
285 | Checking v ->
286 let%map c, v' = f.car v (fun el -> mkt (Account_type.Checking el)) in
287 (c, Checking v')
288 end
289
290 module Folder = struct
291 type nonrec 'b t = { car : 'a. 'a f F.t -> 'b }
292
293 let fold (type b c) (f : c t) : b f -> c = function
294 | Accounts_payable v -> f.car v
295 | Accounts_receivable v -> f.car v
296 | Bank v -> f.car v
297 | Cash v -> f.car v
298 | Credit v -> f.car v
299 | Mutual_fund v -> f.car v
300 | Stock v -> f.car v
301 | Savings v -> f.car v
302 | Checking v -> f.car v
275 end 303 end
276end 304end
277 305
278module Typed_account_path = struct 306module Typed_account_path = struct
279 type 'a elem = Leaf | Node of string * 'a | Ind of string * 'a elem 307 type 'a elem = Leaf | Node of string * 'a | Ind of string * 'a elem
308 [@@deriving sexp_of]
280 309
281 include Account_structure (struct 310 include Account_structure (struct
282 type 'a t = 'a elem 311 type 'a t = 'a elem [@@deriving sexp_of]
283 end) 312 end)
284end 313end
285 314
@@ -300,78 +329,158 @@ module Account_hierarchy = struct
300 | Ind of 'a account String.Map.t 329 | Ind of 'a account String.Map.t
301 (* Has no subaccounts, has a balance in a certain commodity *) 330 (* Has no subaccounts, has a balance in a certain commodity *)
302 | Leaf of Commodity_id.t * Money.Diff.t 331 | Leaf of Commodity_id.t * Money.Diff.t
332 [@@deriving sexp_of]
303 333
304 and extra = { description : String.t } 334 and extra = { description : String.t } [@@deriving sexp_of]
305 and 'a account = extra * 'a core 335 and 'a account = extra * 'a core
306 336
307 include Account_structure (struct 337 include Account_structure (struct
308 type 'a t = 'a account 338 type 'a t = 'a account [@@deriving sexp_of]
309 end) 339 end)
310 340
311 (* All accounts *) 341 (* All accounts *)
312 type world = t0 String.Map.t 342 type world = t0 String.Map.t
313 343
314 let rec alter_aux (subaid : Account_path.t) 344 let rec unsafe_alter_aux (subaid : Account_path.t)
315 (f : 345 (f :
316 Account_type.t0 -> Commodity_id.t -> Money.Diff.t -> 'a * Money.Diff.t) 346 Account_type.t0 ->
317 : 'a Mapper.t = 347 extra ->
348 Commodity_id.t ->
349 Money.Diff.t ->
350 'a * extra * Money.Diff.t) : 'a Mapper.t =
318 { 351 {
319 car = 352 car =
320 (fun in_acc mkt -> 353 (fun in_acc mkt ->
321 let open Option.Let_syntax in 354 let open Option.Let_syntax in
322 match (subaid, in_acc) with 355 match (subaid, in_acc) with
323 | [], (extra, Leaf (acc_comm, acc_bal)) -> 356 | [], (extra, Leaf (acc_comm, acc_bal)) ->
324 let x, acc_bal' = f (mkt Account_type.Leaf) acc_comm acc_bal in 357 let x, extra', acc_bal' =
325 Some (x, (extra, Leaf (acc_comm, acc_bal'))) 358 f (mkt Account_type.Leaf) extra acc_comm acc_bal
359 in
360 Some (x, (extra', Leaf (acc_comm, acc_bal')))
326 | [], _ -> None 361 | [], _ -> None
327 | subaid0 :: subaid, (extra, Node subaccs) -> 362 | subaid0 :: subaid, (extra, Node subaccs) ->
328 let open Option.Let_syntax in
329 let%bind subacc = Map.find subaccs subaid0 in 363 let%bind subacc = Map.find subaccs subaid0 in
330 let%map x, subacc' = 364 let%map x, subacc' =
331 Mapper.map (alter_aux subaid f) (fun k -> mkt (Node k)) subacc 365 Mapper.map
366 (unsafe_alter_aux subaid f)
367 (fun k -> mkt (Node k))
368 subacc
332 in 369 in
333 (x, (extra, Node (Map.set subaccs ~key:subaid0 ~data:subacc'))) 370 (x, (extra, Node (Map.set subaccs ~key:subaid0 ~data:subacc')))
334 | subaid0 :: subaid, (extra, Ind subaccs) -> 371 | subaid0 :: subaid, (extra, Ind subaccs) ->
335 let open Option.Let_syntax in
336 let%bind subacc = Map.find subaccs subaid0 in 372 let%bind subacc = Map.find subaccs subaid0 in
337 let%map x, subacc' = (alter_aux subaid f).car subacc mkt in 373 let%map x, subacc' = (unsafe_alter_aux subaid f).car subacc mkt in
338 (x, (extra, Ind (Map.set subaccs ~key:subaid0 ~data:subacc'))) 374 (x, (extra, Ind (Map.set subaccs ~key:subaid0 ~data:subacc')))
339 | _ :: _, (_, Leaf _) -> None); 375 | _ :: _, (_, Leaf _) -> None);
340 } 376 }
341 377
342 let alter (aid : Account_path.t) 378 let unsafe_alter (aid : Account_path.t)
343 (f : 379 (f :
344 Account_type.t0 -> Commodity_id.t -> Money.Diff.t -> 'a * Money.Diff.t) 380 Account_type.t0 ->
345 (w : world) : ('a * world) option = 381 extra ->
382 Commodity_id.t ->
383 Money.Diff.t ->
384 'a * extra * Money.Diff.t) (w : world) : ('a * world) option =
346 match aid with 385 match aid with
347 | [] -> None 386 | [] -> None
348 | aid0 :: subaid -> ( 387 | aid0 :: subaid -> (
349 let open Option.Let_syntax in 388 let open Option.Let_syntax in
350 match%bind Map.find w aid0 with 389 match%bind Map.find w aid0 with
351 | Asset acc -> 390 | Asset acc ->
352 let%map x, acc' = (alter_aux subaid f).car acc (fun k -> Asset k) in 391 let%map x, acc' =
392 (unsafe_alter_aux subaid f).car acc (fun k -> Asset k)
393 in
353 (x, Map.set w ~key:aid0 ~data:(Asset acc')) 394 (x, Map.set w ~key:aid0 ~data:(Asset acc'))
354 | Expense acc -> 395 | Expense acc ->
355 let%map x, acc' = 396 let%map x, acc' =
356 (alter_aux subaid f).car acc (fun k -> Expense k) 397 (unsafe_alter_aux subaid f).car acc (fun k -> Expense k)
357 in 398 in
358 (x, Map.set w ~key:aid0 ~data:(Expense acc')) 399 (x, Map.set w ~key:aid0 ~data:(Expense acc'))
359 | Income acc -> 400 | Income acc ->
360 let%map x, acc' = 401 let%map x, acc' =
361 (alter_aux subaid f).car acc (fun k -> Income k) 402 (unsafe_alter_aux subaid f).car acc (fun k -> Income k)
362 in 403 in
363 (x, Map.set w ~key:aid0 ~data:(Income acc')) 404 (x, Map.set w ~key:aid0 ~data:(Income acc'))
364 | Liability acc -> 405 | Liability acc ->
365 let%map x, acc' = 406 let%map x, acc' =
366 (alter_aux subaid f).car acc (fun k -> Liability k) 407 (unsafe_alter_aux subaid f).car acc (fun k -> Liability k)
367 in 408 in
368 (x, Map.set w ~key:aid0 ~data:(Liability acc')) 409 (x, Map.set w ~key:aid0 ~data:(Liability acc'))
369 | Equity acc -> 410 | Equity acc ->
370 let%map x, acc' = 411 let%map x, acc' =
371 (alter_aux subaid f).car acc (fun k -> Equity k) 412 (unsafe_alter_aux subaid f).car acc (fun k -> Equity k)
372 in 413 in
373 (x, Map.set w ~key:aid0 ~data:(Equity acc'))) 414 (x, Map.set w ~key:aid0 ~data:(Equity acc')))
374 415
416 (** Update the balance (debit/credit ([dc])) of account [aid] [by_amount]
417 (commodity: [in_comm]) in [world], giving the updated world and the pre
418 and post balances for [aid] iff the account exists in [world]. *)
419 let update_bal aid dc by_amount in_comm (w : world) :
420 (Money.Diff.t * Money.Diff.t * world) option =
421 let open Option.Let_syntax in
422 let%bind mres, w' =
423 unsafe_alter aid
424 (fun acc_type acc_extra acc_comm acc_bal ->
425 if not ([%equal: Commodity_id.t] acc_comm in_comm) then
426 (None, acc_extra, acc_bal)
427 else
428 let on_debit =
429 match acc_type with
430 | Asset _ -> `Incr
431 | Expense _ -> `Incr
432 | Income _ -> `Decr
433 | Liability _ -> `Decr
434 | Equity _ -> `Decr
435 in
436 let acc_bal' =
437 Money.Diff.(acc_bal + of_amount by_amount dc ~on_debit)
438 in
439 (Some (acc_bal, acc_bal'), acc_extra, acc_bal'))
440 w
441 in
442 let%map pre_bal, post_bal = mres in
443 (pre_bal, post_bal, w')
444
445 let get_bal aid (w : world) : (Commodity_id.t * Money.Diff.t) option =
446 let open Option.Let_syntax in
447 let%map cb, _world' =
448 unsafe_alter aid
449 (fun _acc_type acc_extra acc_comm acc_bal ->
450 ((acc_comm, acc_bal), acc_extra, acc_bal))
451 w
452 in
453 cb
454
455 let add_balance_maps m1 m2 : Money.Diff.t Commodity_id.Map.t =
456 Map.merge m1 m2 ~f:(fun ~key:_comm -> function
457 | `Both (b1, b2) -> Some Money.Diff.(b1 + b2)
458 | `Left b | `Right b -> Some b)
459
460 let rec collect_balances : type a.
461 a f account -> Money.Diff.t Commodity_id.Map.t = function
462 | _extra, Leaf (acc_comm, acc_bal) ->
463 Commodity_id.Map.singleton acc_comm acc_bal
464 | _extra, Ind subaccs ->
465 Map.fold subaccs ~init:Commodity_id.Map.empty
466 ~f:(fun ~key:_ ~data:subacc comm_bal_sums ->
467 add_balance_maps comm_bal_sums (collect_balances subacc))
468 | _extra, Node subaccs ->
469 Map.fold subaccs ~init:Commodity_id.Map.empty
470 ~f:(fun ~key:_ ~data:subacc comm_bal_sums ->
471 add_balance_maps comm_bal_sums
472 (Folder.fold { car = collect_balances } subacc))
473
474 type delete_error = Not_found | Nonzero_balance
475
476 (*
477 let rec delete_aux : type a. (Account_path.t * a f account) -> (a f account, delete_error) result = function
478 | [], (extra, Leaf (_acc_comm, acc_bal)) ->
479 if Money.Diff.(acc_bal = ~$0) then
480
481 let delete (aid : Account_path.t) (w : world) =
482 *)
483
375 let world_inst : world = 484 let world_inst : world =
376 String.Map.of_alist_exn 485 String.Map.of_alist_exn
377 [ 486 [
@@ -406,12 +515,21 @@ module Account_hierarchy = struct
406 ] 515 ]
407end 516end
408 517
409type bal_assert = { 518module Bal_assert = struct
410 account : Account_path.t; 519 type t = { account : Account_path.t; labels : Labels.t; bal : Money.Diff.t }
411 labels : Labels.t; 520 [@@deriving sexp_of]
412 bal : Money.Diff.t; 521end
413} 522
414[@@deriving sexp_of] 523module Account_decl = struct
524 type t = {
525 type_ : Account_type.t0;
526 parent : Account_path.t;
527 name : string;
528 commodity : Commodity_id.t;
529 extra : Account_hierarchy.extra;
530 }
531 [@@deriving sexp_of]
532end
415 533
416module Tx : sig 534module Tx : sig
417 type entry = { 535 type entry = {
@@ -472,7 +590,8 @@ end
472 590
473type item = 591type item =
474 | Tx_item of Tx.t 592 | Tx_item of Tx.t
475 | Bal_assert_item of bal_assert (*| Account_decl_item of account_decl*) 593 | Bal_assert_item of Bal_assert.t
594 | Account_decl_item of Account_decl.t
476[@@deriving sexp_of] 595[@@deriving sexp_of]
477 596
478type t = item list [@@deriving sexp_of] 597type t = item list [@@deriving sexp_of]
@@ -482,77 +601,30 @@ module World = struct
482 601
483 let empty : t = String.Map.empty 602 let empty : t = String.Map.empty
484 603
485 (** Update the balance (debit/credit ([dc])) of account [aid] [by_amount]
486 (commodity: [in_comm]) in [world], giving the updated world and the pre
487 and post balances for [aid] iff the account exists in [world]. *)
488 let update_bal aid dc by_amount in_comm (world : t) :
489 (Money.Diff.t * Money.Diff.t * t) option =
490 let open Option.Let_syntax in
491 let%bind mres, world' =
492 Account_hierarchy.alter aid
493 (fun acc_type acc_comm acc_bal ->
494 if not ([%equal: Commodity_id.t] acc_comm in_comm) then (None, acc_bal)
495 else
496 match acc_type with
497 | Asset _ ->
498 let acc_bal' =
499 Money.Diff.(acc_bal + of_amount by_amount dc ~on_debit:`Incr)
500 in
501 (Some (acc_bal, acc_bal'), acc_bal')
502 | Expense _ ->
503 let acc_bal' =
504 Money.Diff.(acc_bal + of_amount by_amount dc ~on_debit:`Incr)
505 in
506 (Some (acc_bal, acc_bal'), acc_bal')
507 | Income _ ->
508 let acc_bal' =
509 Money.Diff.(acc_bal + of_amount by_amount dc ~on_debit:`Decr)
510 in
511 (Some (acc_bal, acc_bal'), acc_bal')
512 | Liability _ ->
513 let acc_bal' =
514 Money.Diff.(acc_bal + of_amount by_amount dc ~on_debit:`Decr)
515 in
516 (Some (acc_bal, acc_bal'), acc_bal')
517 | Equity _ ->
518 let acc_bal' =
519 Money.Diff.(acc_bal + of_amount by_amount dc ~on_debit:`Decr)
520 in
521 (Some (acc_bal, acc_bal'), acc_bal'))
522 world
523 in
524 let%map pre_bal, post_bal = mres in
525 (pre_bal, post_bal, world')
526
527 let get_bal aid (world : t) : (Commodity_id.t * Money.Diff.t) option =
528 let open Option.Let_syntax in
529 let%map cb, _world' =
530 Account_hierarchy.alter aid
531 (fun _acc_type acc_comm acc_bal -> ((acc_comm, acc_bal), acc_bal))
532 world
533 in
534 cb
535
536 let apply_tx (tx : Tx.t) world : t option = 604 let apply_tx (tx : Tx.t) world : t option =
537 Map.fold_option tx.entries ~init:world 605 Map.fold_option tx.entries ~init:world
538 ~f:(fun ~key:aid ~(data : Tx.entry) world -> 606 ~f:(fun ~key:aid ~(data : Tx.entry) world ->
539 let open Option.Let_syntax in 607 let open Option.Let_syntax in
540 let%bind _old_bal, new_bal, world = 608 let%bind _old_bal, new_bal, world =
541 update_bal aid data.dc data.amount data.commodity world 609 Account_hierarchy.update_bal aid data.dc data.amount data.commodity
610 world
542 in 611 in
543 match data.assertion with 612 match data.assertion with
544 | None -> Some world 613 | None -> Some world
545 | Some bal_ass -> 614 | Some bal_ass ->
546 if Money.Diff.(bal_ass = new_bal) then Some world else None) 615 if Money.Diff.(bal_ass = new_bal) then Some world else None)
547 616
548 let apply_ba (ba : bal_assert) world : t option = 617 let apply_ba (ba : Bal_assert.t) world : t option =
549 let open Option.Let_syntax in 618 let open Option.Let_syntax in
550 let%bind _comm, bal = get_bal ba.account world in 619 let%bind _comm, bal = Account_hierarchy.get_bal ba.account world in
551 if not Money.Diff.(bal = ba.bal) then None else Some world 620 if not Money.Diff.(bal = ba.bal) then None else Some world
552 621
622 let apply_ad (_ad : Account_decl.t) _world : t option = None
623
553 let apply : item -> t -> t option = function 624 let apply : item -> t -> t option = function
554 | Tx_item tx -> apply_tx tx 625 | Tx_item tx -> apply_tx tx
555 | Bal_assert_item ba -> apply_ba ba 626 | Bal_assert_item ba -> apply_ba ba
627 | Account_decl_item ad -> apply_ad ad
556end 628end
557 629
558module Ctxd_item = struct end 630module Ctxd_item = struct end
diff --git a/lib/ledger.mli b/lib/ledger.mli.bak
index 0b8e383..0b8e383 100644
--- a/lib/ledger.mli
+++ b/lib/ledger.mli.bak