feat(ai): add ai extract, quote-verified field extraction to a draft manifest - #46
Merged
Conversation
… every citation Adds corpus/, the hashed, dated plain text of every source the rule catalog cites, split into addressable passages, with scripts/build_corpus.py to rebuild it and a loader (ceqa_preflight.ai.corpus) that refuses text that does not match its manifest. It is the only text the opt-in AI explanation layer (ADR 0002) may quote, and it ships inside the wheel. Source citations now carry a kind (official, technical_reference, project_advisory) and the HTML report labels each link with it. FILE-004 and FILE-005, self-cited because no official guidance states their thresholds, now link to the 2026-07-27 source-review addendum that explains them instead of the repository root, and render as a project advisory rule, not an official source. Closes #38.
…manifest The first command of the opt-in ai group (ADR 0002). Each PDF's text layer is read through a bounded, process-isolated extractor; the configured model (Anthropic API or Amazon Bedrock through the public anthropic SDK, claude-sonnet-5 by default) is asked to copy out the facts a manifest carries; and every proposed value is verified against a verbatim quote from the document before it is shown. Unverified values are withheld and counted, unstated fields are unknown, image-only PDFs are never sent, and any model error fails closed with no draft. The output is a draft manifest for a person to review; check is unchanged, never invokes this, and a test proves the default path never imports the provider SDK.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The first command of the opt-in
aigroup (ADR 0002, role 1: the model structures input; it does not decide).ceqa_preflight/ai/text.py: bounded, process-isolated text extraction (same spawn + hard-timeout pattern as the inspector; page and character caps; image-only PDFs reported as having no text layer and never sent).ceqa_preflight/ai/client.py: provider clients over the publicanthropicSDK (Anthropic API; Amazon Bedrock viaAnthropicBedrock), imported lazily, credentials from env only, aScriptedClientfor tests/replay. Default modelclaude-sonnet-5;--provider/--modelandCEQA_PREFLIGHT_AI_PROVIDER/MODELoverride.ceqa_preflight/ai/extraction.py: the model is asked to copy out 13 manifest-relevant fields plus the document kind, each with a verbatim quote and page. The verifier then checks every quote against the document text and every value against its quote (enum fields against the allowed set). Unverified values are withheld (statusunverified, value never shown), unstated fields areunknown, anot_ceqa_noticedocument yields no fields, and a model error fails closed with no draft. Only verified values reach the draft manifest; the primary form is marked only when exactly one document identified itself as the requested form.ceqa_preflight/ai/cli.py:ceqa-preflight ai extract PKG --filing-type NOE [--format console|json] [--output FILE] [--write-manifest FILE]. States the data flow before running; never overwrites a manifest; the written draft carries a header saying it is a draft, not a finding.ceqa_preflight/ai/provenance.py: provider/model/prompt-version/tool-version/time on every output.ceqa_preflight/ai/messages.py: all user-facing strings in one module for the i18n seam (The i18n release gate blocking the first tagged release has none of its five items built #39).Default path unchanged
checkconsole output onexamples/noe-fictional-packageis byte-identical to main (verified by diff).tests/test_ai_client.py::test_default_cli_path_never_imports_the_provider_sdkruns a subprocess that imports the CLI and the catalog and assertsanthropicis not insys.modules. The SDK is an optional extra (ceqa-preflight[ai],[ai-bedrock]), present in the dev group only so evals can run.Live smoke test (not an eval; the eval lands in a later PR)
Run against one real CEQAnet NOE (SCH 2026080811) on Bedrock
global.anthropic.claude-sonnet-4-6(the only model this account can reach; Sonnet 5 returns 403): 9 of 13 fields found with verified quotes, all matching CEQAnet's own metadata (title, lead agency, county, city, location, applicant, contact, exemption status, §15301 citation); SCH number, NOD fields, and signature date honestlyunknown; 0 withheld.Verification
174 tests, 94.23% branch coverage, ruff / mypy --strict / bandit clean locally.
🤖 Generated with Claude Code