From 2ca73ec01f027522820d623c967e072da8259549 Mon Sep 17 00:00:00 2001 From: Alex-Gilbert Date: Mon, 23 Feb 2026 11:35:28 -0600 Subject: [PATCH] feat: wire word mode and ensure_plural to /api/solve endpoint Co-Authored-By: Claude Opus 4.6 --- unredact/app.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/unredact/app.py b/unredact/app.py index 926ce78..15ab249 100644 --- a/unredact/app.py +++ b/unredact/app.py @@ -22,7 +22,7 @@ from unredact.pipeline.font_detect import CANDIDATE_FONTS, _find_font_path from unredact.pipeline.analyze_page import analyze_page, analyze_spot_redaction from unredact.pipeline.detect_redactions import spot_redaction from unredact.pipeline.solver import build_constraint, SolveResult -from unredact.pipeline.dictionary import solve_dictionary, solve_full_name_dictionary, solve_name_dictionary +from unredact.pipeline.dictionary import solve_dictionary, solve_full_name_dictionary, solve_name_dictionary, solve_word_dictionary from unredact.pipeline.word_filter import _get_emails from unredact.pipeline.width_table import build_width_table, CHARSETS @@ -282,10 +282,11 @@ class SolveRequest(BaseModel): left_context: str = "" right_context: str = "" hints: dict = {} - mode: str = "name" # "name", "full_name", "email", "enumerate" + mode: str = "name" # "name", "full_name", "email", "word", "enumerate" word_filter: str = "none" # only used for enumerate mode known_start: str = "" known_end: str = "" + ensure_plural: bool = False def _build_rust_solve_payload( @@ -433,6 +434,29 @@ async def solve(req: SolveRequest): "source": "emails", }) + # Word mode: English nouns + adjective/noun phrases + if req.mode == "word" and not _active_solves.get(solve_id): + for r in solve_word_dictionary( + font, req.gap_width_px, req.tolerance_px, + req.left_context, req.right_context, + casing=charset_name, + known_start=req.known_start, + known_end=req.known_end, + ensure_plural=req.ensure_plural, + ): + if _active_solves.get(solve_id): + break + if r.text in found_texts: + continue + found_texts.add(r.text) + yield json.dumps({ + "status": "match", + "text": r.text, + "width_px": round(r.width, 2), + "error_px": round(r.error, 2), + "source": "words", + }) + # Enumerate mode: Rust backend if req.mode == "enumerate" and not _active_solves.get(solve_id): charset = CHARSETS.get(charset_name, charset_name)