2424import json
2525import re
2626import subprocess
27- from dataclasses import dataclass , field
27+ from dataclasses import asdict , dataclass , field
2828from pathlib import Path
2929from typing import Any , Callable , Optional
3030
@@ -415,6 +415,9 @@ class Converted:
415415 # twice saves this reply and passes it back as `convert`'s `route_a`; a
416416 # second call to the model returns different wording.
417417 route_a : Reply_ = field (default_factory = list )
418+ # Route B's reply, the filter's two runs merged and before reconciling, and None
419+ # where no filter was given or the filter run failed.
420+ route_b : Optional [Reply_ ] = None
418421 tokens : int = 0
419422 # The counts of the reconciliation, zero where route B did not run.
420423 fields : int = 0
@@ -526,6 +529,12 @@ def convert(
526529 `on_stage`, where it is given, is called with a name and a message as each step
527530 finishes - `ocr`, `route A`, `route B`, `fields`, `build` - so that a caller watching
528531 a run shows each line as the step ends rather than the report at the end of it.
532+
533+ The run is saved in `out_dir` beside the zip: `reply-a.json` as route A answers,
534+ `reply-b.json` as the filter runs, and `flags.json` and `report.txt` after the build.
535+ Each file is written as soon as its content exists, so that a step that fails keeps
536+ the replies of the steps before it. Where no filter was given, or the filter run
537+ failed, no `reply-b.json` is written.
529538 """
530539 settings = settings or load_settings ()
531540 backend = backend or choose_backend (settings )
@@ -534,6 +543,10 @@ def said(stage: str, message: str) -> None:
534543 if on_stage is not None :
535544 on_stage (stage , message )
536545
546+ out_dir = Path (out_dir )
547+ # The build writes the zip here at the end of the run; the replies are written
548+ # into the same directory as each route answers, before the build makes it.
549+ out_dir .mkdir (parents = True , exist_ok = True )
537550 read = [f"{ Path (d ).name } : { _read_as (d , cache_dir )} " for d in (document , solutions ) if d is not None ]
538551 markdown , images = markdown_of (document , cache_dir , settings )
539552 solutions_md = markdown_of (solutions , cache_dir , settings )[0 ] if solutions else None
@@ -546,7 +559,9 @@ def said(stage: str, message: str) -> None:
546559 else :
547560 tokens = 0
548561 said ("route A" , "the reply given, no call made" )
562+ (out_dir / "reply-a.json" ).write_text (json .dumps (route_a , indent = 2 ), encoding = "utf-8" )
549563 reply = route_a
564+ route_b = None
550565 counts , error = (0 , 0 , 0 , 0 ), None
551566 flags = [Flag (k , fields (reply )[k ], "" , NOT_VERBATIM ) for k in not_verbatim (reply , source )]
552567 if lua is None :
@@ -561,6 +576,8 @@ def said(stage: str, message: str) -> None:
561576 error = (stderr .decode ("utf-8" , "replace" ) if stderr else str (problem )).strip ()
562577 said ("route B" , f"failed: { error } " )
563578 else :
579+ route_b = other
580+ (out_dir / "reply-b.json" ).write_text (json .dumps (other , indent = 2 ), encoding = "utf-8" )
564581 reconciled = reconcile (reply , other , source , backend )
565582 reply , flags = reconciled .fields , reconciled .flags
566583 # The adjudication call is the document's second call, so its tokens
@@ -576,13 +593,19 @@ def said(stage: str, message: str) -> None:
576593 flags .append (Flag (k , fields (reply )[k ], "" , STRAY_MINUS ))
577594 result = Converted (
578595 set = to_set (reply , name = name , directory = images ), zip_path = None , flags = flags ,
579- reply = reply , route_a = route_a , tokens = tokens ,
596+ reply = reply , route_a = route_a , route_b = route_b , tokens = tokens ,
580597 fields = counts [0 ], agreed = counts [1 ], defaulted = counts [2 ], adjudicated = counts [3 ],
581598 route_b_error = error ,
582599 )
583600 said ("fields" , result .counted ())
584601 result .zip_path = build (result .set , out_dir )
585602 said ("build" , str (result .zip_path ))
603+ (out_dir / "flags.json" ).write_text (
604+ json .dumps ([asdict (one ) for one in result .flags ], indent = 2 ), encoding = "utf-8"
605+ )
606+ # The report names the zip, so it is written after the build, and holds the lines
607+ # the command prints.
608+ (out_dir / "report.txt" ).write_text ("\n " .join (result .report ()) + "\n " , encoding = "utf-8" )
586609 return result
587610
588611
0 commit comments