(** Read progress.json for dashboard display *) type phase = | Solving | Blessings | Building | Gc | Completed let phase_of_string = function | "solving" -> Solving | "blessings" -> Blessings | "building" -> Building | "gc" -> Gc | "completed" -> Completed | _ -> Solving let phase_to_string = function | Solving -> "Solving" | Blessings -> "Computing Blessings" | Building -> "Building" | Gc -> "Garbage Collection" | Completed -> "Completed" type t = { run_id : string; start_time : string; phase : phase; targets : string list; solutions_found : int; solutions_failed : int; build_completed : int; build_total : int; doc_completed : int; doc_total : int; } let read ~log_dir ~run_id = let path = Filename.concat log_dir (Filename.concat "runs" (Filename.concat run_id "progress.json")) in if Sys.file_exists path then try let content = In_channel.with_open_text path In_channel.input_all in let json = Yojson.Safe.from_string content in let open Yojson.Safe.Util in Some { run_id = json |> member "run_id" |> to_string; start_time = json |> member "start_time" |> to_string; phase = json |> member "phase" |> to_string |> phase_of_string; targets = json |> member "targets" |> to_list |> List.map to_string; solutions_found = json |> member "solutions_found" |> to_int; solutions_failed = json |> member "solutions_failed" |> to_int; build_completed = json |> member "build_completed" |> to_int; build_total = json |> member "build_total" |> to_int; doc_completed = json |> member "doc_completed" |> to_int; doc_total = json |> member "doc_total" |> to_int; } with _ -> None else None