Add California foreclosure compliance corpus analyzer - #2227
Conversation
Runs the legalis-ca-foreclosure ruleset (Civ. Code § 2924 et seq.) over a corpus of recorded instruments and stores the compliance chronology on the Analysis. A foreclosure matter maps onto a corpus: each recorded instrument is a document. This is corpus-scoped rather than per-document because no single instrument answers the question — whether the three-month period under § 2924(a)(2) elapsed needs the Notice of Default and the Notice of Sale together. The ruleset runs as a sidecar rather than in-process. It is Rust; an FFI boundary would couple this deployment to a Rust toolchain and turn the ruleset's panics into Celery worker crashes. What is read from documents: instrument type, recording date, date mailed to the trustor, publication and posting dates. What is not read, because it appears on no recorded instrument: the sale date, loan purpose, occupancy, tenders, postponements, payoff requests. Those come from the analyzer input or Corpus.custom_meta, and anything absent reports INSUFFICIENT RECORD rather than being assumed — a finding resting on an assumed fact is worse than no finding. Instrument classification consults the document title before the body, since a Notice of Default recites the deed of trust it secures and a whole-text scan picks the wrong instrument. If the service is unreachable the task raises rather than returning "no violations found"; a compliance analyzer that reports a clean bill of health because it could not reach its ruleset is worse than one that crashes, and a test asserts this. The task is idempotent, as CELERY_TASK_ACKS_LATE requires. Adds the foreclosure-api sidecar behind a compose profile, FORECLOSURE_API_URL and FORECLOSURE_API_TIMEOUT settings, and docs. 32 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QmjyGXUNXHKZETogxWuDdu
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 529b9f02c3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| from opencontractserver.documents.models import Document | ||
|
|
||
| corpus = Corpus.objects.get(id=corpus_id) | ||
| documents = list(Document.objects.filter(corpus=corpus).order_by("id")) |
There was a problem hiding this comment.
Query corpus membership through DocumentPath
Every analyzer invocation fails here because Document has no corpus field; corpus membership is represented by active DocumentPath records. Django therefore raises FieldError before either the empty-corpus handling or evaluation runs. Use the corpus's _get_active_documents() accessor, which applies the required current/non-deleted path predicate.
Useful? React with 👍 / 👎.
| # These dates run at most three tokens ("January 15, 2024"). | ||
| candidate = " ".join(tail.split()[:3]) |
There was a problem hiding this comment.
Parse numeric dates without consuming following fields
When a supported one-token date such as Recording Date: 01/15/2024 or 2024-01-15 is followed by another field, tail.split()[:3] appends that field's first two tokens, so parse_date rejects the resulting string and the instrument is dropped for lacking a recording date. This affects normal multiline extracts despite both formats being explicitly listed in _DATE_FORMATS; try the supported token lengths independently or isolate the date value before parsing.
Useful? React with 👍 / 👎.
|
Hey! Cool contribution, thank you. Will take a look |
Runs the legalis-ca-foreclosure ruleset (Civ. Code § 2924 et seq.) over a corpus of recorded instruments and stores the compliance chronology on the Analysis.
A foreclosure matter maps onto a corpus: each recorded instrument is a document. This is corpus-scoped rather than per-document because no single instrument answers the question — whether the three-month period under § 2924(a)(2) elapsed needs the Notice of Default and the Notice of Sale together.
The ruleset runs as a sidecar rather than in-process. It is Rust; an FFI boundary would couple this deployment to a Rust toolchain and turn the ruleset's panics into Celery worker crashes.
What is read from documents: instrument type, recording date, date mailed to the trustor, publication and posting dates. What is not read, because it appears on no recorded instrument: the sale date, loan purpose, occupancy, tenders, postponements, payoff requests. Those come from the analyzer input or Corpus.custom_meta, and anything absent reports INSUFFICIENT RECORD rather than being assumed — a finding resting on an assumed fact is worse than no finding.
Instrument classification consults the document title before the body, since a Notice of Default recites the deed of trust it secures and a whole-text scan picks the wrong instrument.
If the service is unreachable the task raises rather than returning "no violations found"; a compliance analyzer that reports a clean bill of health because it could not reach its ruleset is worse than one that crashes, and a test asserts this. The task is idempotent, as CELERY_TASK_ACKS_LATE requires.
Adds the foreclosure-api sidecar behind a compose profile, FORECLOSURE_API_URL and FORECLOSURE_API_TIMEOUT settings, and docs. 32 tests.
Claude-Session: https://claude.ai/code/session_01QmjyGXUNXHKZETogxWuDdu