Skip to content

Single "Enhance with AI" action and purpose-focused report content prompt #1931

Description

@steilerDev

[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.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 && (
  <div className={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 both en 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.
  • 3.6 The numeric-accuracy guarantees established by feat(reports): AI-generated usage descriptions and cover letter for bank report wizard #1916 are untouched — amounts fed to the prompt remain in the correct unit and precision, and the existing prompts.test.ts regression guard against the ×100 unit defect continues to pass.

Length caps

  • 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.2 The effective limits are: letterSubject 150 characters, letterBody 2000 characters, per-invoice description 200 characters.
  • 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.

Notes

Metadata

Metadata

Assignees

No one assigned

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions