ysoseri.us

实习日志 / English / Claimant UI / The system shows, the model talks: the claimant app goes from adding features to tidying up

The system shows, the model talks: the claimant app goes from adding features to tidying up

中文 | English

On 30 September I walked through an anonymous claim locally from start to finish. The claimant's main line should be just one: tell the story in your own words, the Agent asks about what is missing, check what has been noted as you go, and submit. Walking through it, the line broke in several places: customers could not see what had been noted, and the progress was wrong; one "confirm" button pushed people straight into payment details; you could not upload photos without logging in; and on phones the conversation was squeezed into the bottom half of the screen. There was also a string of small problems, such as Enter not sending and the title showing an internal reference.

The system shows, the model talks

At the time, what the system had noted sat in a sidebar about 400px wide on the right, long enough to need scrolling, and customers did not notice it while chatting. Things the customer had just said were labelled "Needs review" there, which read as if they had said something wrong. Progress appeared three times, under the title, at the top of the sidebar and in an overview card, and after the customer had described what happened and where, all three still showed 0, because facts the AI had proposed but not yet confirmed did not count towards progress. To let customers know what had been noted, the Agent repeated every noted value in each turn. Repetition took up space and was not reliable either; the values the model restated could differ from what the system had stored.

The redesign set one principle: what has been noted and how much is complete are shown directly by the interface from system data; the model is only responsible for chatting naturally and asking follow-ups. In concrete form, that became a "Just noted" card under the Agent's reply:

Agent: Glad no one's hurt and the car still drives. Did the other driver accept fault?
┌──────────────────────────────────────────┐
│ Just noted                  5 of 7 noted · 2 to check │
│  · Location: Queen St, at the lights      │
│  · Vehicle: drivable                      │
│  · Other vehicle: yes                     │
│  [ Looks right ]   [ Edit ]               │
└──────────────────────────────────────────┘

The card shows only fields added or changed this turn. The top right holds the only progress indicator in the interface, counting facts the customer has stated but not yet confirmed, and noting how many are still to check. "Looks right" confirms the whole card at once, reusing the existing POST /claims/{id}/form/confirmations; customers can also ignore the card and keep chatting, and when the next card appears the old one collapses into one line, "3 to check". What customers said themselves is labelled "To check", not "Needs review".

The Agent's requirements changed accordingly: at most two sentences, at most one question, no repetition, with a target of 240 characters. The hard limit stayed at 500, because OpenAI's strict mode does not support maxLength, and setting it to 240 would make an overlong reply fail the whole turn.

The same PR (#1160) also fixed the answer sheet. All fields share one kind of slot, one sheet per registry version; a field not allowed this turn, or a value of the wrong shape, is dropped on its own and recorded, while the rest take effect as normal, instead of the whole turn failing. By the repository's own estimator, the answer sheet for a first motor turn went from 4,508 tokens to 2,069.

We compared 10 cases (rear-end, hit in a car park, hail, burst pipe, kitchen fire, lost phone and so on), 2 turns each:

BeforeFirst commitFinal
Failed turns0 / 200 / 200 / 20
Median reply length (characters)17294108
Repetition ratio0.320.070.14
Pleasantry endings200
Median time (seconds)3.443.633.07
Total fields noted615465

The repetition ratio is "the share of three-word phrases from the customer's words that appear in the reply". The first commit shortened replies but noted about 12% fewer fields. The reason: once the answer sheet was fixed, the only place where the v8 model could see the allowed enum values was gone. field_value_contracts had been assumed to be in the context, but it was only on the v7 path. Enum values the model guessed wrong were dropped by the new "drop invalid fields" rule. The fix added writable_fields to the context, listing this turn's writable fields grouped by type, with allowed values for enum fields, from the same field contract used for validation, and the field count went back to 65. To judge "what the model can see", read the request that was actually sent, not code on a neighbouring path.

Confirm, and it asks for your bank account

In the old flow, a customer clicking "confirm" in the sidebar, once the fields were complete, made the front end insert a payment card into the conversation; it looked as if, right after confirming, the AI started asking for a bank account. The payment step was also redundant in itself. Payment account, contact details, policies, vehicles and property were all in the profile (/account/profile, /account/policies, /account/payment-destinations, /account/assets), along with a preference for "fill in automatically when reporting", yet the claim flow made people choose them again at Step 3.

The submission path became: once the fields are complete, a "ready to submit" card appears in the conversation, and opening it shows one review page: incident summary, list of documents, payment account (defaulting to the profile's default account, shown as "ANZ ••••1234 · Change"), contact details, agreement to the terms, and the submit button. The terms checkbox cannot be ticked until the customer has opened the terms, with the hint "Open the terms to continue". The submit button calls the three existing endpoints in order: save payment and contact details, agree to the terms, create the claim. The backend's division of steps stays the same; the front end just no longer splits them into separate steps, and the Agent no longer pushes towards payment.

When someone not logged in clicks "review and submit", login or sign-up appears first; after logging in, the existing /claims/{id}/promote moves the claim to the account and returns to the same review page.

Photos without logging in

Anonymous reporting was designed after ChatGPT, for people who have just had an accident and are in a hurry, so they can start talking without signing up. But photo upload was blocked twice, once in the front end and once in the backend: without a login token the front end reported "Sign in before uploading a file" straight away, and the backend's require_durable_claimant also refused uploads from anonymous sessions. The result was that customers were asked to log in exactly when they most needed to take photos. The repository's own front-end quality standard actually said that persisting files needs authentication, but anonymous uploads can be handled within the current session as long as their lifecycle is made clear.

So for anonymous files, the approach I set was a staging area bound to the anonymous session: as long as the session has not expired, the files stay. The key is the X-Northwind-Anonymous-Session the browser keeps locally; claims in the same session can all reference files in the staging area, and the Agent can read these photos too. On login, promote also turns referenced files into formal documents, while unreferenced ones stay in staging.

The limits are 10MB per file, and at most 20 files and 100MB per anonymous session, so it cannot be used as free cloud storage. Beyond that the message is "This report already holds 20 files. Sign in to add more." A customer who clears browser data or switches devices cannot get the session back, which matches how anonymous conversations behave anyway. The backend changes were to narrow require_durable_claimant to account-level documents and add session-keyed staging storage, reusing the existing object storage and image readiness flow. This part touches identity and privacy, so it was done on its own, with failure paths and tests meeting the requirements of the high-risk category.

The model picker only names products

The drop-down in the input used to show configuration codes like openrouter-gpt4o…. The picker stayed, but shows only product names:

  • Northwind Assist: the main cloud model. "Fast. Reads your photos and documents."
  • Northwind Private: locally deployed Qwen. "Text only. Your details stay on Northwind's own servers."

Private must say that it handles text only, because qwen-local is configured with image_input: false and document_input: false; without saying so, customers would think the AI had looked at their photos.

Two stuffed storerooms

Once these changes had landed, by 2 October the interface had a different kind of problem: features had been added one at a time with no common rule for where things go. The chat area held everything: under replies there were table-like fact cards, coloured receipts, large service consent cards and process prompt cards, and in between whole payment forms, profile auto-fill panels and 6 kinds of system event card were inserted, with seven or eight visual forms on screen at once. The right panel held everything too: to-dos, an overview card with a progress bar, grouped lists, next steps, files and the third-party service entry. And progress appeared once each in the title bar, the top right of the fact card and the panel's overview card.

This round was about tidying up: no features removed, only putting things in their place, merging and slimming. First came 8 rules, and where each item went followed from them:

  1. The chat holds only conversation. An Agent reply is text plus at most one attachment strip made of small chips (facts, receipts, services, file readings), collapsed by default and showing detail only when opened.
  2. Anything to fill in, check or read at length goes to the pinned area of the panel, leaving only an entry chip in the chat.
  3. Status appears in one place only, the title bar. Progress, stage and human assistance status merge into one status area.
  4. A system event is a divider line with one sentence in the conversation flow.
  5. The panel is the case file. Closed by default, with three permanent sections in order: to do, noted, files; the pinned area holds one item at a time.
  6. Only one accent colour means "you need to do something"; all other information is neutral.
  7. The header holds only identity and exits: brand, back, and one help entry.
  8. Tool pages do not squeeze into the middle of the conversation.

Following these, the "Just noted" card shrank from a half-screen table to a set of fact chips in the attachment strip: "Dominion Road · Today about 5

· No one hurt", with a small dot when something is still to check, opening in place into a compact editable list. The service consent card became a chip, "Towing · needs your OK ›", which opens the full consent card in the pinned area of the panel. Clause citations became clickable chips that show the clause text in the pinned area; cited_clauses in the turn record keeps only clauses actually retrieved this turn. The "External services" page was deleted and its content moved into the panel.

This round was first built as an HTML prototype referencing the token library directly, settling the forms scene by scene, and then landed in code (#1184). Visual values all still came from frontend/shared/design-tokens.css, and this round added tokens such as --claimant-chip-*, --claimant-title-bar-height, --claimant-rail-width and --claimant-case-sheet-half-height.

On phones, only the conversation

On phones the whole page used to scroll: the header, the History / Chat / Details tabs, an internal reference title wrapped onto two lines and the progress bar stacked up and took about 40% of the top of the screen, the first message came after that, and the input's toolbar was often below the first screen. To see what had been noted you had to switch to the Details tab, which meant leaving the conversation. The responsive layout had only been done early on, and later changes never kept up.

Phones have no room for a sidebar, so the fact chips in the conversation are the main way to check things. Below 640 wide, the conversation area scrolls on its own, the input is fixed to the bottom with an env(safe-area-inset-bottom) safe area and moves up with the keyboard; the title bar collapses to one line: back menu, short title, status; and the panel becomes a bottom sheet with two resting heights, half and full screen, closed by pulling down, settling at half screen automatically when there is a pinned item. Acceptance widths are 390, either side of 640 and 980, and 1366.

Staff stay out during collection

Tidying up raised a question that could not be avoided: where do questions go that the customer raises along the way and the Agent cannot answer? The old approach was to start a human handoff and bring staff into the conversation. With many customers, the staff side would be flooded with ad hoc questions.

The rule I set on 3 October: staff do not enter the conversation while a claim is being collected. Questions the Agent cannot answer along the way, points that need professional judgement, and customers' general contact requests all go onto a waiting list, each with the customer's own words and the Agent's suggested answer and its basis, and the customer is told "the claims team will handle this after you submit". Urgent safety still goes straight to handoff; so does a customer clearly and strongly asking for a person, or insisting again after a general request was deferred. That judgement is left to the Agent, written into the prompt, with no hard-coded rule in the Runtime. The help button on the customer side sends an ordinary message, judged by the Agent in the same way.

When staff take on a claim, the waiting list opens first, and each item can be "approve and send", "edit and send" or "decline" (with a reason), while contact requests are marked "contacted". Answers appear in the customer's conversation in the name of the case owner. The backend structure is:

northwind_questions:
  kind / question (in the customer's voice) / claimant_wording / proposed_answer / basis / contact_preference

POST /workbench/claims/{id}/questions/{qid}/answer
  decision: approve | edit | decline | contacted

The customer side sees only the question and its status. Once the owner has spoken after the claim is created, the Agent steps back; created claims with waiting items or customer replies go back to the processing queue.

Back to the main line

The 30 September round fixed breaks: records you could not see, payment details appearing halfway through, no photos, a phone conversation squeezed into the bottom half. The 2 October round fixed accumulation: every feature made sense on its own, and together they buried the conversation. Both rounds rested on the same judgement. The customer's attention is on the conversation; what the system knows is shown accurately by the interface, once; the model only talks; and anything the customer needs to do sits next to the message they are reading, or in the top slot of the panel.