You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As a homeowner writing a report for a bank, I want one obvious "Enhance with AI" action that produces text explaining why each cost was incurred so that I do not have to opt in twice, and the generated prose adds information the table does not already show.
Priority: Should Have Parent Epic: none — part of the parent-less Bank Report Wizard cluster (#1876–#1879, #1898–#1901, #1923–#1925). Found during: user review of the AI feature shipped in #1901 (2026-08-01).
Problem
Two separate defects, one UX and one content-quality, both in the AI assistance path added by #1901.
1. Double opt-in
client/src/pages/ReportWizardPage/Step4Settings.tsx L~108–125 renders an "Enable AI assistance" checkbox (only when llmEnabled), which sets aiEnabled. client/src/pages/ReportWizardPage/ReportWizardPage.tsx L860 then gates the step-5 action on aiEnabled:
{aiEnabled&&(<divclassName={styles.aiGenerateRow}><button...onClick={handleGenerateWithAiClick}...>{t('sourceReports.editable.generateWithAi')} // "Generate with AI" / "Mit KI generieren"
The toggle carries no state of its own — it exists purely to reveal a button that the user must then also click. The user must opt in twice to do one thing, and there is no way to discover the feature from step 5 if the toggle was left off. llmEnabled (from GET /api/config) already tells us whether the capability exists; the toggle adds nothing.
The label is also wrong for what the action does. It runs against content that already exists (the deterministic table and cover letter are built before AI is ever invoked, and the AI result overwrites them — hence the "overwrite your edits?" confirmation in handleGenerateWithAiClick, L595). The action improves existing content; it does not generate content from nothing.
2. The generated text restates the table
REPORT_CONTENT_SYSTEM_PROMPT (server/src/services/budgetExtraction/prompts.ts L133) asks for "one-line factual descriptions for invoices" and "a collective summary of work completed", and buildReportContentUserPrompt (L149) feeds the model vendor, invoice number, date, amount, notes and budget-line descriptions.
The model therefore writes back what it was given — the what. But the vendor, the invoice number, the date and the amount are already columns in the very same table row, and the linked item name is often the budget-line description verbatim. The Usage column ends up as a redundant restatement, consuming page width (see the PDF-layout issue in this batch) without adding information a bank reader can use.
What a bank reader cannot see, and what the homeowner actually needs to convey, is why the cost was incurred — what role that purchase or service played in the construction project.
3. Length caps disagree between the prompt and the validator
Field
Prompt states
Validator enforces
letterSubject
max 150 (prompts.ts L140, L193)
truncate at 200 (openAICompatibleProvider.ts L330)
letterBody
max 2000 (L141, L194)
truncate at 3000 (L337)
description (per invoice)
max 200 (L139, L195)
truncate at 300 (L367)
The model is instructed to one limit and silently permitted a wider one, so overlong output passes through unclipped and lands in a table cell sized for something shorter.
Acceptance Criteria
Settings step
1.1 Given the report wizard's step 4 (Settings), When the step renders with an LLM configured, Then there is no "Enable AI assistance" control of any kind — the whole AI-assistance section is gone.
1.2 Given step 4 after the change, When the step renders, Then the report-language, attach-documents, and include-cover-letter controls behave exactly as before, including the cover-letter disabled hint.
1.3 The aiEnabled state and its prop plumbing are removed, and the now-unused sourceReports.settingsStep.enableAiAssistance / enableAiAssistanceHelper keys are removed from bothen and de locale files (no orphan keys, no missing keys).
Step 5 action
2.1 Given an instance where the LLM is configured (llmEnabled: true from GET /api/config), When step 5 renders, Then exactly one AI action button is present, with no prior opt-in required.
2.2 Given an instance where the LLM is not configured (llmEnabled: false), When step 5 renders, Then no AI action button, spinner, note, or error slot is rendered anywhere in the step.
2.3 Given the AI action button, When it renders, Then its label reads "Enhance with AI" in English and an equivalent in German that uses "KI" for AI, consistent with the existing de copy in this namespace.
2.4 Given the user has edited one or more fields in the step-5 editor, When the AI action is clicked, Then the existing overwrite confirmation still appears before any request is made (no regression to handleGenerateWithAiClick).
2.5 Given the AI action is in flight, When the user observes step 5, Then the existing loading spinner, elapsed-seconds caption, error display, and post-generation "generated with AI — review before submitting" note all still behave as they do today.
2.6 German copy for any changed or added key is present and glossary-consistent; en and de are at parity for this namespace.
Prompt focus
3.1 Given the report-content generation prompt after the change, When it is read, Then it instructs the model to explain why each cost was incurred — its purpose or role in the construction project — and explicitly instructs it not to restate the vendor, invoice number, date, or amount, because those are already columns in the report table.
3.2 Given a set of invoices with budget lines and linked items, When AI enhancement runs with report language English, Then each returned usage description reads as a purpose statement (what the cost was for in the build) rather than a restatement of the row's own columns, and contains no vendor name, invoice number, or currency amount.
3.3 AC 3.2 holds identically when the report language is German: output is entirely in German (regardless of the interface language or the language of the source data), is grammatical, and uses natural German construction terminology consistent with the project glossary — no untranslated English fragments, no anglicised calques.
3.4 Given the cover-letter body prompt after the change, When AI enhancement runs, Then the generated body explains the purpose of the spending in the context of the construction project and its relevance to the report's purpose (overview / claim / proof of funds), rather than enumerating the invoices already listed in the table.
3.5 The existing safety and accuracy constraints are preserved verbatim in effect: untrusted-input / prompt-injection handling, "never invent or alter amounts or dates", "every invoice ID must appear in descriptions keyed by exact invoiceId", output language forced to the report language, and JSON-only output.
4.1 Given the codebase after this change, When the maximum lengths for letterSubject, letterBody, and per-invoice description are looked up, Then each has exactly one definition, and both the prompt text and the response validator derive from that same definition — it is impossible for them to disagree.
4.3 Given an LLM response that exceeds any of those limits, When the response is validated, Then the field is capped to the limit (existing truncation behaviour) — the response is not rejected — and a test pins each boundary.
Interacts with the PDF-layout issue in this batch. Shorter, purpose-focused descriptions reduce Usage-column pressure, but the layout issue's ACs must hold independently of description length — neither issue may rely on the other for its fix.
Scope guard: this issue does not change when AI runs (still user-initiated from step 5), does not add per-field AI regeneration, does not change the LLM provider/transport layer, and does not change the deterministic (non-AI) content that the wizard builds by default.
[product-owner]
As a homeowner writing a report for a bank, I want one obvious "Enhance with AI" action that produces text explaining why each cost was incurred so that I do not have to opt in twice, and the generated prose adds information the table does not already show.
Priority: Should Have
Parent Epic: none — part of the parent-less Bank Report Wizard cluster (#1876–#1879, #1898–#1901, #1923–#1925).
Found during: user review of the AI feature shipped in #1901 (2026-08-01).
Problem
Two separate defects, one UX and one content-quality, both in the AI assistance path added by #1901.
1. Double opt-in
client/src/pages/ReportWizardPage/Step4Settings.tsxL~108–125 renders an "Enable AI assistance" checkbox (only whenllmEnabled), which setsaiEnabled.client/src/pages/ReportWizardPage/ReportWizardPage.tsxL860 then gates the step-5 action onaiEnabled:The toggle carries no state of its own — it exists purely to reveal a button that the user must then also click. The user must opt in twice to do one thing, and there is no way to discover the feature from step 5 if the toggle was left off.
llmEnabled(fromGET /api/config) already tells us whether the capability exists; the toggle adds nothing.The label is also wrong for what the action does. It runs against content that already exists (the deterministic table and cover letter are built before AI is ever invoked, and the AI result overwrites them — hence the "overwrite your edits?" confirmation in
handleGenerateWithAiClick, L595). The action improves existing content; it does not generate content from nothing.2. The generated text restates the table
REPORT_CONTENT_SYSTEM_PROMPT(server/src/services/budgetExtraction/prompts.tsL133) asks for "one-line factual descriptions for invoices" and "a collective summary of work completed", andbuildReportContentUserPrompt(L149) feeds the model vendor, invoice number, date, amount, notes and budget-line descriptions.The model therefore writes back what it was given — the what. But the vendor, the invoice number, the date and the amount are already columns in the very same table row, and the linked item name is often the budget-line description verbatim. The Usage column ends up as a redundant restatement, consuming page width (see the PDF-layout issue in this batch) without adding information a bank reader can use.
What a bank reader cannot see, and what the homeowner actually needs to convey, is why the cost was incurred — what role that purchase or service played in the construction project.
3. Length caps disagree between the prompt and the validator
letterSubjectprompts.tsL140, L193)openAICompatibleProvider.tsL330)letterBodydescription(per invoice)The model is instructed to one limit and silently permitted a wider one, so overlong output passes through unclipped and lands in a table cell sized for something shorter.
Acceptance Criteria
Settings step
aiEnabledstate and its prop plumbing are removed, and the now-unusedsourceReports.settingsStep.enableAiAssistance/enableAiAssistanceHelperkeys are removed from bothenanddelocale files (no orphan keys, no missing keys).Step 5 action
llmEnabled: truefromGET /api/config), When step 5 renders, Then exactly one AI action button is present, with no prior opt-in required.llmEnabled: false), When step 5 renders, Then no AI action button, spinner, note, or error slot is rendered anywhere in the step.decopy in this namespace.handleGenerateWithAiClick).enanddeare at parity for this namespace.Prompt focus
descriptionskeyed by exactinvoiceId", output language forced to the report language, and JSON-only output.prompts.test.tsregression guard against the ×100 unit defect continues to pass.Length caps
letterSubject,letterBody, and per-invoicedescriptionare looked up, Then each has exactly one definition, and both the prompt text and the response validator derive from that same definition — it is impossible for them to disagree.letterSubject150 characters,letterBody2000 characters, per-invoicedescription200 characters.Notes
client/src/pages/ReportWizardPage/Step4Settings.tsx,client/src/pages/ReportWizardPage/ReportWizardPage.tsx,client/src/i18n/{en,de}/budget.json,server/src/services/budgetExtraction/prompts.ts,server/src/services/budgetExtraction/openAICompatibleProvider.ts(+ tests).buildReportContentUserPromptL153, where the language ternary produces"German construction project"for English output and"Konstruktionsprojekt"for German — inverted and wrong in both branches. This issue rewrites that prompt wholesale and must fix that line as part of the rewrite. Remove the item from refactor(llm): consolidate LLM integration boundaries and shared report math (PR #1916 follow-ups) #1917 when this lands; leave the rest of refactor(llm): consolidate LLM integration boundaries and shared report math (PR #1916 follow-ups) #1917 open.decopy already uses "KI" ("Mit KI generieren", "KI-Unterstützung aktivieren"), so this issue must stay consistent with that; adding the formal glossary entry remains refactor(llm): consolidate LLM integration boundaries and shared report math (PR #1916 follow-ups) #1917's job.