better faulty encoding detection

This commit is contained in:
sebastien committed 2026-09-29 08:03:47 +02:00
1 parent ec8e79bb1a
commit 2a4c5f4f7c
12 files changed
+299 -25

No files matched your search

+13 -2
View File
@@ -21,6 +21,7 @@ from copienator.commands import grouping
from copienator.answer_origins import save_origins
from copienator import prompting
from copienator.feedback_boxes import valid_feedback_box
from copienator.latex_json import repair_latex_json
from copienator import (
CliError,
EvaluationWorkspace,
@@ -100,6 +101,16 @@ def report_group_progress(completed: int, total: int) -> None:
)
def parse_correction_response(response_text: str):
"""Decode model JSON without consuming known LaTeX command prefixes."""
return json.loads(
repair_latex_json(
response_text,
additional_content_fields={"text"},
)
)
def request_graceful_stop(_signum=None, _frame=None) -> None:
"""Stop scheduling Gemini calls while allowing in-flight calls to finish."""
if not stop_requested.is_set():
@@ -319,7 +330,7 @@ def correct_boxes_with_gemini(pid, label, pdf_path, original_feedbacks,
contents, config = prompting.request_for_box_correction(pdf_path, original_feedbacks)
response_text = call_gemini_with_retries(MODEL_ID_flash, contents, config)
corrected_feedbacks = json.loads(response_text)
corrected_feedbacks = parse_correction_response(response_text)
global_feedbacks = [f for f in original_feedbacks if not f["box_2d"]]
@@ -507,7 +518,7 @@ def process_single_task(task_tuple, precomputed_response=None):
tprint(f"Asking Gemini {'Flash' if use_flash else 'Pro '}: {label} {group_name}")
full_response_text = call_gemini_with_retries(model_to_use, contents, config)
json_data = json.loads(full_response_text)
json_data = parse_correction_response(full_response_text)
# Ensure consistency of answer placements
for p in json_data:
+9
View File
@@ -554,11 +554,18 @@ def run(
client = genai.Client(api_key=api_key)
groups = group_images(images)
total_groups = len(groups)
active: dict[str, tuple[str, float]] = {}
progress_lock = Lock()
incomplete: list[Path] = []
run_started = time.monotonic()
def print_group_progress(completed: int) -> None:
print(
f"Label detection progress: {completed}/{total_groups} copy groups completed.",
flush=True,
)
def report_progress(group: str, description: str, started: float) -> None:
with progress_lock:
active[group] = (description, started)
@@ -582,6 +589,7 @@ def run(
for group_key, files in groups.items()
}
pending = set(futures)
print_group_progress(0)
while pending:
done, pending = wait(pending, timeout=PROGRESS_INTERVAL)
for future in done:
@@ -591,6 +599,7 @@ def run(
future.result()
except IncompleteCopyError as exc:
incomplete.extend(exc.images)
print_group_progress(total_groups - len(pending))
if pending:
now = time.monotonic()
with progress_lock:
+1
View File
@@ -313,6 +313,7 @@ class ImageViewer:
# Bindings
self.root.bind(PLOTTING_KB["OK"], self.on_enter)
self.root.bind(PLOTTING_KB["previous"], self.on_previous)
self.root.bind("P", self.on_previous)
self.root.bind(PLOTTING_KB["edit"], self.on_edit)
self.root.bind(PLOTTING_KB["open pdf"], self.on_open_pdf)
self.root.bind(PLOTTING_KB["open original pdf"], self.on_open_ori_pdf)
+36
View File
@@ -16,6 +16,7 @@ from copienator import (
read_json,
workspace_from_args,
)
from copienator.latex_json import repair_decoded_latex_controls
WORD_LIST_FILE = Path(__file__).resolve().parents[1] / "data" / "liste_francais.txt"
ACCENT_PATTERN = re.compile(r"[éèêëàâäîïôöùûüçœÉÈÊËÀÂÄÎÏÔÖÙÛÜÇŒ]")
@@ -89,6 +90,7 @@ def fix_hex_corruption_safe(text: str) -> str:
def repair_json_escape_corruption(text: str) -> str:
r"""Restore observed LaTeX commands consumed as JSON control escapes."""
text = repair_decoded_latex_controls(text)
replacements = (
("\x0crac", r"\frac"),
("\x0ceuille", r"\equiv"),
@@ -132,6 +134,33 @@ def clean_obj(value: Any, lookup: dict[str, str]) -> Any:
return value
def unresolved_math_control_paths(value: Any, path: str = "$") -> list[str]:
"""Locate destructive JSON control escapes left in mathematical text."""
if isinstance(value, str):
return (
[path]
if any(
control in {"\b", "\f"}
for match in MATH_PATTERN.finditer(value)
for control in match.group(0)
)
else []
)
if isinstance(value, list):
return [
issue
for index, item in enumerate(value)
for issue in unresolved_math_control_paths(item, f"{path}[{index}]")
]
if isinstance(value, dict):
return [
issue
for key, item in value.items()
for issue in unresolved_math_control_paths(item, f"{path}[{key!r}]")
]
return []
def run(
workspace: EvaluationWorkspace,
*,
@@ -141,11 +170,18 @@ def run(
lookup = build_lookup_map(word_list_path)
data = read_json(workspace.correction_file)
cleaned = clean_obj(data, lookup)
unresolved = unresolved_math_control_paths(cleaned)
backup = workspace.root / "correction_precleanup.json"
atomic_write_bytes(backup, workspace.correction_file.read_bytes())
print(f"Original JSON backed up to {backup}")
atomic_write_json(workspace.correction_file, cleaned)
print(f"Fixed JSON saved to {workspace.correction_file}")
if unresolved:
print(
"Warning: unresolved JSON control characters remain in mathematical "
"text at: " + ", ".join(unresolved)
)
return ExitCode.PARTIAL
return ExitCode.SUCCESS