Let someone file on behalf of a party they are not - #213
Merged
Conversation
The parties screen refused to continue until the person signed in gave *themselves* a court party type, so anyone filing for someone else -- a parent for a child, a neighbour helping answer an eviction -- could not get through it at all. Being the filer and being a party are different questions, and Tyler already keeps them apart: `users` is the list of parties a filing is made on behalf of, and `lead_contact` is whoever filed it. FilingParty.is_filing_party records the first of those. The filer's own row keeps the second, its party_type is now optional, and an empty one means they are not in the case at all. The role question offers "I am filing for someone else" alongside the court's party types, and then asks which party the filing is for. Also fixes the same assumption one step later: the payment screen sent a filer with no party type back to the people step, which a filer who is not a party could never satisfy. It now asks whether the filing has a party at all, which is the thing that actually has to be settled. Closes #207 by making "add me as a party" an explicit button rather than a step nobody could skip: * on the party list, for a filer who is not yet a party * on the review page, next to who the filing is on behalf of * on the add-a-person screen, as "Actually, this party is me" -- which drops the blank row instead of writing a second copy of the filer, who is already on the draft with a name and address Drafts answered before the question existed still file as the filer: the migration backfills them, and both filing_parties() and the payload builder fall back to the filer when nothing is marked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MLBD8Co7jdvH2ZmtQeEwx
Three things that meet on the parties screen. **"Unknown" is not a person.** A model asked to list the parties will sometimes answer, in the field where the names go, that it could not find any -- and "Unknown", "N/A" and "None" became parties the filer then had to work out how to get rid of. They are dropped now, on a whole-name comparison and never a substring: "All Unknown Occupants" is a real defendant in a real eviction, and a guard that reached inside names would delete them. The same name read onto two sides is also one person misread rather than two people, and is listed once. Two other ways an unnamed party appeared: adding a person makes the row before the form that names them, so leaving that form without saving stranded a nameless entry on the list, which is now cleared on the way back in. A nameless row with a party type is left alone -- that is the court's own required-party placeholder, waiting for a name rather than missing one by accident. **A caption name matching the filer is a question, not an answer.** It used to be folded into the filer silently, which was safe while the filer was always a party. Now that they need not be, deleting a party because they share a name with the person filing takes a real party out of the case -- someone filing for a relative they are named after is exactly the case this screen now exists for. So the fold waits for a filer who has said they are a party, and until then the match is put to them as "your document lists Jamie Rivera as the Defendant -- is this you?", with one button to say yes. It is the strongest suggestion available (the document said which side they were on) so it replaces the vaguer case-posture guess, and it stops being offered once they have answered the role question either way. **Notices need an address that can be someone else's.** Filing for another person, `users[0].email` was quietly filled in with the filer's, because Tyler rejects a new case whose first filing party has no email. Quietly is the problem: it decided where the court writes about someone else's case without asking. FilingDraft.notice_email is that question, asked only of someone filing for a party they are not, offered filled in with their own address and editable to the party's or to whoever handles their mail. It reaches the payload as the lead contact's address and as the filing party's when they have none of their own, and the review screen says where notices go with a link back to change it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MLBD8Co7jdvH2ZmtQeEwx
…companies Two things found filing a real Kane County small claim. **Organizations reached Tyler as people with no surname.** The EFSP reads a party as a business only when the entry says `person_type`, and LITEFile has never sent that field, so a company went over as a PersonType whose PersonSurName was empty -- `partyFromDraft` puts an organization's one name in `name.first` and leaves `name.last` blank. The court rejected the whole envelope as far along as the fee quote: "the court's filing service returned status 422: PersonSurName is required or does not match regular expression." Confirmed against the live Kane endpoint with the same case codes: without the field, 422 with that exact message; with it, 200 and a fee quote. This is older than the filing-party work, but that work made it much easier to hit -- an eviction or a small claim against a company now routinely puts the company on a roster the filer never has to open. **There was no way to say "that detected party is me".** The court's required party types are routinely all taken by people the document named, so the only offers on the parties screen were to add yourself as a *second* plaintiff or to say you were not a party at all. Neither is what someone means when their own name is already in the caption under a spelling the name match did not catch, or when they are the tenant the AI read off the complaint. Every listed party now carries "This is me", which adopts that party's court role, folds them into the filer's own row -- the one with the address the court needs -- and files on their behalf. The same button is on the party-details screen for a party that already has a name, where it used to be offered only for a blank row, and the party list says so for a filer who would otherwise add themselves twice. The name-match suggestion is now the pre-answered case of this rather than the only way in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MLBD8Co7jdvH2ZmtQeEwx
"This is me" quietly took the claimed party's court role and deleted
their row, which meant the name in the case silently became the
name on the account. Two different people can be behind that click:
someone whose caption name is not the one their account carries, and
someone who read "this is me" as "I am helping this person" -- and
the second is the reading the words invite. Only the first is what
the button does, and it does it to the party list the court sees.
So the two cases are no longer offered under the same words. A row
that already carries the filer's name still says "This is me", and
claiming it asks nothing, because nothing changes. A row with someone
else's name says "Replace with me" and opens a confirmation that has
to settle two things before anything happens:
* Which name the court should see -- theirs, or the one already in
the case. Neither is pre-selected: a filer whose complaint names
them by a former name wants one, a filer correcting a misread name
wants the other, and only they know which. The server refuses the
claim without an answer, so a browser that never ran the dialog
cannot skip the question either.
* Whether they are *helping* that person rather than being them, with
a button that backs out into the answer they actually wanted --
keeping the party on the filing and marking themselves as filing on
that party's behalf.
Claiming a row nobody has named yet is neither of those and still
asks nothing: there is no name to replace.
Fixes three things found by looking at the rendered page: multi-line
{# #} comments were rendering as visible text (Django only treats
those as comments on one line), the party list's hint was inside the
heading's flex row and overlapped the buttons, and the row grid had
one fewer column than the row now has controls, wrapping the delete
button onto its own line.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015MLBD8Co7jdvH2ZmtQeEwx
The people in a case are first shown two screens before the one that asks the filer their own role, and that is the moment someone looks at a name the system read off their complaint and thinks "that is me". Until now there was nothing to do about it there, and the answer had to wait for a screen that had forgotten the question was obvious. Each person on the review screen now carries "This is me". FilingParty.is_self records it -- on the party, because the filer has no row of their own until two screens later -- and the parties screen spends it: the role question comes back already answered and the duplicate already folded in. When the two names disagree the fold waits, because replacing one name with another is the same question wherever it was asked from, and it goes through the same confirmation as any other claim. Only one person can be the filer, so choosing a row un-chooses whichever held it. The answer rides on a hidden value rather than a checkbox, so an unticked row still posts one and stays aligned with the names beside it -- the same reason every other field on that editor posts from every row. With that answered earlier, the parties screen stops pretending it is the first time. It says the list is there to be checked rather than filled in, and it no longer offers "based on this filing, you are likely the Plaintiff" over the top of people the filer has already named -- a guess that contradicts a better answer they already gave. Both routes into that screen are worded for. A filer who turned AI off gets keyword_document_analysis, which reads a form number and a case number and never a name, so their party list is entirely their own typing: they are told "these are the people you told us about", and only a list that actually came off the document is credited to having read it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MLBD8Co7jdvH2ZmtQeEwx
"Replace with me" sat next to Fox River Phone Repair LLC on the party list, which no individual can truthfully say -- accounts register as individuals (views.register sends registrationType: INDIVIDUAL), and a landlord whose LLC is the plaintiff wants to file *for* the company. It was also a way to break the filing. Claiming a company and keeping its name would have made the filer's own row an organization, and the filer's row reaches Tyler through accountUser(), which splits one name into a first and a last and never says the party is a business -- the same missing person_type that answered a fee quote with "PersonSurName is required" a few commits ago, arrived at from the other direction. Refused in the view as well as hidden on both screens, so it is not only the button that is gone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MLBD8Co7jdvH2ZmtQeEwx
The claim button was taken off the party list a commit ago but left on the screen the names first appear on, so a tick on a company was still reachable -- and led to a refusal two screens later, which is a dead end rather than an answer. Hidden there too, and ignored by self_claimed_party in case an older draft carries one. The hidden value still posts from every row. It is what keeps the lists the view reads back lined up with the names beside them, so it is the button that is conditional and never the field. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MLBD8Co7jdvH2ZmtQeEwx
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
A confirmed bug remains where an organization row can consume the single “is self” selection and block marking the actual person, due to save_reviewed_parties not gating is_self on non-organizations.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR updates LITEFile’s “People/Parties” workflow to support a filer who is not themselves a party, while still producing a valid Tyler/EFSP filing payload (including correct filing-party selection, notice email routing, and organization/business handling).
Changes:
- Add durable model + workflow support for “filing on behalf of” via
FilingParty.is_filing_party, optional filerparty_type, and a parties-screen flow to select the represented party (plusnotice_email). - Add “This is me / Replace with me” claiming UX across extraction review, parties list, and party-details, including a confirmation dialog for name-replacement cases.
- Fix organization payload semantics by sending
person_type: "business"for org parties to avoid Tyler surname validation failures.
File summaries
| File | Description |
|---|---|
| efile_app/js-tests/filing-payload.test.js | Expands JS payload unit tests for filing-on-behalf + org handling. |
| efile_app/efile/views/review.py | Adds review context for “filing for” and notice email display. |
| efile_app/efile/views/payment.py | Gates payment on having at least one filing party (not filer party_type). |
| efile_app/efile/views/party_details.py | Adds claim/replace context for party-details screen. |
| efile_app/efile/views/parties.py | Implements filer-not-a-party flow, claiming, and notice-email capture. |
| efile_app/efile/views/extraction_review.py | Reads “is self” per extracted party row from the POSTed form. |
| efile_app/efile/utils/ui_text.py | Updates role-help copy to mention filing for someone else. |
| efile_app/efile/tests/test_people_flow.py | Updates assertions for the revised role-help copy. |
| efile_app/efile/tests/test_filing_on_behalf.py | New test suite covering filing-on-behalf workflow paths. |
| efile_app/efile/tests/test_extracted_parties.py | Adds tests for placeholder-name filtering, de-dupe, and is-self behavior. |
| efile_app/efile/templates/efile/review.html | Displays “not a party / filing on behalf of …” and notice-email summary. |
| efile_app/efile/templates/efile/party_details.html | Adds claim/replace UI and includes the claim dialog + JS. |
| efile_app/efile/templates/efile/parties.html | Adds “not a party” option, filing-for checklist, claim buttons, hints, and dialog include. |
| efile_app/efile/templates/efile/extraction_review.html | Adds “This is me” toggles (hidden field + button) per extracted row. |
| efile_app/efile/templates/efile/components/claim_party_dialog.html | New reusable dialog for confirming “replace with me” claims. |
| efile_app/efile/static/js/parties.js | Syncs filing-for section visibility + “Add me as a party” shortcut. |
| efile_app/efile/static/js/filing-payload.js | Resolves filing parties distinct from filer; adds notice-email routing and org person_type. |
| efile_app/efile/static/js/extraction-review.js | Implements one-at-a-time “This is me” toggle behavior. |
| efile_app/efile/static/js/claim-party.js | New dialog controller for claim/replace confirmation + “file for them instead” path. |
| efile_app/efile/static/css/reorganized-flow.css | Styles for claim dialog and updated party/extraction review layouts. |
| efile_app/efile/services/people.py | Adds filing-party resolution helpers + claiming logic + blank-row cleanup. |
| efile_app/efile/services/extracted_parties.py | Adds placeholder-name filtering, cross-side de-dupe, and is-self persistence. |
| efile_app/efile/services/drafts.py | Includes notice_email and is_filing_party in serialized case data. |
| efile_app/efile/models.py | Adds notice_email, FilingParty.is_self, and FilingParty.is_filing_party. |
| efile_app/efile/migrations/0021_party_is_self.py | Adds DB field for FilingParty.is_self. |
| efile_app/efile/migrations/0020_notice_email.py | Adds DB field for FilingDraft.notice_email. |
| efile_app/efile/migrations/0019_filing_party.py | Adds DB field for FilingParty.is_filing_party + backfill migration. |
Review details
- Files reviewed: 27/27 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Review catch. The screens stopped offering the tick on an organization and the parties screen ignores one, but nothing stopped it being *written* -- and a name only becomes a company when apply_name runs on the same submit that ticked it, so the UI cannot be the whole guard. Two ways that bit. A tick on a company was stored as an answer nothing downstream could act on, so the filer's answer quietly went nowhere. And because a company took the one self slot, a real person ticked on a later row lost the tick to a row that could never use it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015MLBD8Co7jdvH2ZmtQeEwx
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.
The bug
The parties screen refused to continue until the person signed in gave themselves a court party type. Anyone filing for someone else — a parent for a child, a neighbour helping answer an eviction, a friend with the only e-filing account in the house — could not get through it at all.
Being the filer and being a party are different questions, and Tyler already keeps them apart:
usersis the list of parties a filing is made on behalf of, andlead_contactis whoever filed it. LITEFile was collapsing the two.What changed
FilingParty.is_filing_partyrecords who the filing is for. The filer's own row keeps the contact record, itsparty_typeis now optional, and an empty one means they are not in the case at all. The role question offers "I am not one of the parties — I am filing for someone else", and then asks which party the filing is for. The payment screen's gate moved with it: it asks whether the filing has a party, not whether the filer is one."This is me", wherever the people are. Every listed party can be claimed as yourself — on the party list, on the party-details screen, and (
FilingParty.is_self) on the extraction-review screen where the document's names first appear, which is where people actually notice. Claiming adopts that party's court role, folds them into the filer's row (the one with the address the court needs), and files on their behalf.Claiming a differently-named party asks first. It replaces a person in the case, so it is not called "This is me" — it is called "Replace with me", and it opens a dialog that settles two things: which name the court should see (neither pre-selected — a filer named by a former name wants one answer, a filer fixing a misread name wants the other), and whether they are helping that person rather than being them, with a button that backs out into "I am filing for them" instead. The server refuses the claim without a name answer, so a browser that never ran the dialog cannot skip the question. An organization is never offered, because accounts register as individuals.
Guards against parties nobody added.
"Unknown","N/A"and friends in the names field are the extraction saying it found nobody, not people — dropped on a whole-name comparison, never a substring, because All Unknown Occupants is a real eviction defendant. The same name read onto two sides is one person, listed once. A row started by "add another person" and never named is swept up rather than stranded on the list.person_typefor organizations. The EFSP reads a party as a business only when the entry says so, and LITEFile never sent the field — so a company reached Tyler as a person with an emptyPersonSurNameand the court rejected the envelope at the fee quote. This one predates the branch; the filer/party work just made it easy to reach.Notices get an address that can be someone else's. Filing for another person,
users[0].emailwas quietly filled in with the filer's, because Tyler rejects a new case whose first filing party has no email.FilingDraft.notice_emailmakes that a question — asked only when filing for someone else, offered filled in with their own address, editable to the party's — and the review screen says where notices go, with a link back to change it.Copy that matches the route taken. With the people settled earlier, the parties screen says the list is there to be checked, not filled in, and it no longer offers "based on this filing, you are likely the Plaintiff" over the top of people the filer already named. A filer who turned AI off gets
keyword_document_analysis, which reads a form number and a case number and never a name — so on that route the list is entirely their own typing, and they are told "these are the people you told us about" rather than being credited a reading that never happened.Migrations
Three, all additive:
0019_filing_party,0020_notice_email,0021_party_is_self.0019backfills existing drafts (a filer with a party type was the only possible filing party before this), and bothfiling_parties()and the payload builder fall back to the filer when nothing is marked, so drafts mid-flight keep working.Testing
test_filing_on_behalf.pyandtest_extracted_parties.py) and 42 JS unit tests (12 new injs-tests/filing-payload.test.js). Ruff, ty and ESLint clean.PersonSurNamefix was confirmed by sending two otherwise identical fee-quote payloads: withoutperson_type→422 PersonSurName is required or does not match regular expression; with it →200and a fee quote. Worth knowing for the next person who debugs this: without a realtyler_payment_idTyler fails onPaymentID is emptybefore it validates names, which masks the error entirely — my first two probe runs "passed" that way.How this got to here
Worth recording, because most of the fixes came from use rather than from tests:
422was misdiagnosed twice before the payment-ID masking above was understood.{# #}comments rendering as visible page text (Django only treats those as comments on one line), a hint overlapping the buttons in a flex row, and a row grid with fewer columns than the row had controls.PersonSurNamefailure fixed earlier in the branch, since the filer's own row would have become the company.Issues
Closes #207.
Advances #197 without closing it — a filer who is not a party is now supported and can say who they are filing for, and the account's saved details are offered as an explicit action rather than only defaulted. Still open there: asking at registration whether the account is mainly used for other people's filings, and keeping court-sourced parties read-only in an existing case.
Known gaps
absorb_filer_duplicatesstill matches on an exact normalised name, so "Q. Steenhuis" and "Quinten Steenhuis" are two people to it. The claim button is the manual answer to that.🤖 Generated with Claude Code
https://claude.ai/code/session_015MLBD8Co7jdvH2ZmtQeEwx