08 — Gutachten-Pipeline: Six-Step Specification (plan 5.3)
Purpose. This document is the implementation spec for the Analyzer expert-report pipeline — the six sequential AI steps (Interview → Document → SWOT → Scoring → Report → Report, prototype agents ag-05…ag-10) that turn a client's questionnaire answers and uploaded documents into a partner-approved expert report with 11 lever scores, SWOT, recommendations, and a rendered PDF. It defines the state machine over ai_runs, the per-step input/output contracts and German prompt skeletons, the deterministic rules engine (score caps, traffic light), the Partner review gates, LLM call conventions against the GRAG platform, failure handling, and final report assembly.
Status / verified against: 2026-07-06, repo document-processing-pipelines @ db63a95; prototype next-monorepo/apps/web (types.ts, mock-data); integration plan tasks/todo.md rev. 2. Review corrections applied: ai-gateway has no structured output (plan 1.14), ledger paths are /ledger/api/v1/ledger/totals|spend (plan 1.6), groundedness spans are English-only, BFF upload/ingest is unauthenticated until plan 1.8 lands.
Sibling docs: architecture in 01-architecture.md, request cookbook in 02-grag-api-cookbook.md, ID formats in 03-id-conventions.md, DB DDL in 06-knoll-db-schema.md, KG projection in 07-kg-schema-knoll-advisory.md, chat lanes in 09-chat-integration.md, upload lane in 10-document-pipeline.md, error taxonomy in 11-resilience-and-errors.md, cost model in 14-cost-model.md.
1. Overview
| # | Step | Agent | Kind | LLM? | Retrieval? | Groundedness? |
|---|---|---|---|---|---|---|
| 1 | Interview | ag-05 Interview-Agent | Structured digest of questionnaire + contradiction detection + follow-up questions | yes | no (Knoll DB only) | yes (vs. rendered answers) |
| 2 | Document | ag-06 Document-Agent | Findings per checklist document | yes | yes (analysis-KB search/rerank + segments/text:batch) | yes |
| 3 | SWOT | ag-07 SWOT-Agent | Synthesis over 1+2 | yes | no (prior outputs) | yes |
| 4 | Scoring | ag-08 Scoring-Agent | LLM score proposal → deterministic caps + traffic light in code | yes | no (prior outputs) | yes (rationales) |
| 5 | Report (sections) | ag-09 Expert-Report-Agent | Long-form German text per lever + recommendations | yes (11 calls) | yes (kb-methodology for style/method) | yes, per section |
| 6 | Report (assembly) | ag-10 Report-Agent | Executive Summary + 3 key messages; assembly, PDF, ingest | yes (1 call) | no | yes |
Design rules (from plan 5.3 / D3):
- The pipeline is a Knoll-backend state machine over
ai_runsrows. GRAG provides primitives (completions, retrieval, full-text expansion, groundedness, ledger); all sequencing, review gates, and rules live in the Knoll backend. Do not use agent-control for these single-shot steps. - Every LLM step goes through
POST $GRAG_URL/ai-gateway/api/v1/chat/completions(non-streaming) with afallbackchain. There is noresponse_format/JSON mode at the gateway (plan 1.14) — structured output = prompt-JSON + Zod validation + bounded repair retries (§6.3). - A Partner review gate sits between every step (QMS human-in-the-loop; the prototype's Einstellungen page marks HITL-Freigabe "Pflicht laut QMS – kann nicht deaktiviert werden"). Freigabe actions require the Partner role (plan 2.3).
- The pipeline honors
knoll_ai_settings(detail_level∈ Compact|Standard|Detailed,caps_enabled, model picker) — see §5.4. - Groundedness verdicts are advisory until plan 7.1 calibration — they inform the Partner, never auto-block (§4, "Bands").
2. State machine
2.1 Run and step model
One pipeline run = one attempt to produce an expert-report version for one analysis. One ai_runs row exists per step attempt (columns per plan 2.1: analysis_id, agent_id (ag-05…ag-10), step (text, well-known values interview|document|swot|scoring|report|report per 06-knoll-db-schema.md §3.12 — the "1–6" numbering below is UI ordering only), attempt, status, input/output refs, model, tokens, cost, groundedness_band, error; DDL is normative in 06-knoll-db-schema.md). Each step attempt owns its own pipeline_id (stored in ai_runs.pipeline_id at attempt start, stamped as X-Pipeline-Id on every GRAG call that attempt makes; canonical format knoll-<analysis-shortid>-<step>-<runid> per 03-id-conventions.md §8 — examples below use knoll-4b0c77de-scoring-4e21ac03 = Scoring attempt 4e21ac03 of analysis 4b0c77de). There is no run-level pipeline id: run and per-analysis spend are a Knoll-side sum over the step attempts' ids (§3.4).
2.2 Step states and transitions
┌──────────┐ start ┌─────────┐ output valid ┌─────────────────┐
│ queued │ ───────► │ running │ ──────────────► │ awaiting_review │
└──────────┘ └────┬────┘ └──────┬───┬──────┘
│ error/retries exhausted │ │
▼ approve │ │ reject
┌────────┐ ▼ ▼
│ failed │ ┌──────────┐ ┌──────────┐
└───┬────┘ │ approved │ │ rejected │
│ manual retry └──────────┘ └────┬─────┘
▼ │ spawns
new attempt (queued) ▼
new attempt (queued)
| From | To | Trigger | Side effects |
|---|---|---|---|
| — | queued | previous step approved (step 1: run started) | new ai_runs row, attempt=1 |
queued | running | worker picks up step | started_at set |
running | awaiting_review | output passed Zod validation; groundedness scored | output persisted; notification "expert-report step pending approval" (plan 2.7) |
running | failed | LLM/retrieval error after bounded retries, or JSON invalid after repair retries | error code set (§7); UI "Fehlgeschlagen" |
awaiting_review | approved | Partner Freigeben (optionally after Bearbeiten) | audit entry; next step row created queued |
awaiting_review | rejected | Partner Ablehnen with comment | audit entry; new attempt created queued with reviewer feedback injected into the prompt (§6.4) |
failed | (new attempt queued) | user clicks "Erneut ausführen" | attempt+1; approved outputs of earlier steps are reused |
Terminal states per attempt: approved, rejected, failed, cancelled — all values of ai_run_status in 06-knoll-db-schema.md §2.2. Gated pipeline steps terminate at approved (the review gate is mandatory); the enum's succeeded is used only by ungated runs outside this pipeline (lead scoring, Klassifikation etc., 06 §3.12) and never appears here. There is no stored run-level status — it is derived from the step rows: a run is completed when the report step is approved and assembly (§9) succeeded; a run is cancelled when a Partner aborts it, which sets the active step attempt to cancelled (allowed in any non-terminal state; audit entry; no GRAG cleanup needed — all writes so far are Knoll-DB rows plus ledger entries).
2.3 Resume / retry semantics
- Retry after
failed/rejectedre-executes only that step. Inputs = the approved outputs of steps 1..n-1 (immutable once approved within a run) + fresh reads of Knoll DB inputs are not taken — the run works on a snapshot (§3, step 1 input freeze) so retries are deterministic w.r.t. inputs. - "Neu ab Schritt n" (Partner action): invalidates approvals of steps ≥ n in this run (statuses stay for audit, a new attempt chain starts at step n). Use when e.g. corrected Fragebogen answers arrive mid-run — note the snapshot rule: a data change requires restart from step 1 (the UI must say so).
- Crash recovery: a step stuck in
runningpast2 × step timeout(§7) is swept tofailedwitherror=timeoutby a janitor job; the completion may still have cost money — the ledger row is the source of truth for spend (§6.5).
2.4 Concurrency rule
At most one active run per analysis. Enforced by a partial unique index on ai_runs (analysis_id) WHERE status IN ('queued','running','awaiting_review') plus a check in the start action — sufficient because steps run strictly sequentially, so one active attempt = one active run. ⚠ This unique index is not yet in 06-knoll-db-schema.md §5 (06's partial index on status is a plain work-queue index, not unique) — add it there when implementing. Starting a new run while one is active requires aborting the old one explicitly. Rationale: steps share the analysis-KB and the review gates address one draft; parallel runs would double spend and confuse approval.
2.5 UI state mapping (plan 6.4)
The prototype's PipelineSchritt.status knows only "Abgeschlossen" | "Läuft" | "Ausstehend" | "Fehlgeschlagen" (lib/types.ts:189-193). The real panel needs one more label:
ai_runs.status | UI label |
|---|---|
queued | Ausstehend |
running | Läuft |
awaiting_review | Wartet auf Freigabe (new — extend the type in 6.4) |
approved | Abgeschlossen |
rejected / failed | Fehlgeschlagen (with reason tooltip; rejected shows the Partner comment) |
3. Shared LLM call conventions (plan 1.14, 7.3)
3.1 Endpoint and headers
Every generation step calls the ai-gateway (verified CompletionRequest: model, messages, temperature, max_tokens, top_p, stop, BYOK api_key/api_base/api_version, cache, fallback (≤5), fallback_policy — no response_format, no tools, no stream):
curl -sS -X POST "$GRAG_URL/ai-gateway/api/v1/chat/completions" \
-H "Authorization: Bearer $GRAG_API_KEY" \
-H "X-Tenant-ID: $GRAG_TENANT" \
-H "X-Pipeline-Id: knoll-4b0c77de-scoring-4e21ac03" \
-H "Content-Type: application/json" \
-d '{
"model": "'"$KNOLL_PIPELINE_MODEL"'",
"messages": [
{"role": "system", "content": "…German system prompt (§5)…"},
{"role": "user", "content": "…user template with data…"}
],
"temperature": 0.2,
"max_tokens": 6000,
"fallback": ["'"$KNOLL_FALLBACK_MODEL_1"'", "'"$KNOLL_FALLBACK_MODEL_2"'"],
"fallback_policy": "retriable_only"
}'
X-Tenant-IDis mandatory on every call (grag-client hard-fails without it — plan 2.5; the platform silently falls back to tenantdefaultotherwise). Dev examples useX-Tenant-ID: knoll-dev.X-Pipeline-Idcarries the step attempt'spipeline_id(ai_runs.pipeline_id, §2.1) on every GRAG call that attempt makes (completions, retrieval, groundedness) — this is the only per-analysis cost attribution mechanism (plan 1.6/7.3; format and rules in03-id-conventions.md§8). Run/analysis spend = Knoll-side sum of/ledger/totalsover the attempts' ids (§3.4).- Response is OpenAI-shaped:
choices[0].message.content,usage.{prompt_tokens, completion_tokens, total_tokens}— persist usage into theai_runsrow.
3.2 Model selection and fallback
- Model ids are LiteLLM
provider/modelstrings. The valid catalog (pricing, context windows, availability) comes fromGET $GRAG_URL/ai-gateway/api/v1/models(this feeds the Einstellungen model picker, plan 6.6). The gateway default isopenai/gpt-4o-mini— do not rely on it for Gutachten quality; always send an explicit model. $KNOLL_PIPELINE_MODELcomes fromknoll_ai_settings(the Settings AI tab's model picker). Configure a 1–2 modelfallbackchain from the same catalog;fallback_policy: "retriable_only"(429/5xx/timeout) is the right default.- Temperature guidance: extraction/scoring steps (1, 2, 4)
0.2; prose steps (3, 5, 6)0.5. - Budget governance: the Kanzlei's provider keys live in the keyvault with
monthly_budget_usd+rate_limit_rpm(plan 1.6); the gateway rejects with 402 (budget) / 429 (rate) / 403 (expired key) — handling in §7.
3.3 Structured output (the plan-1.14 mechanism)
The gateway cannot enforce JSON. Every structured step therefore uses the grag-client helper (plan 2.5):
- The system prompt ends with: "Antworte NUR mit einem einzigen JSON-Objekt nach dem vorgegebenen Schema. Keine Markdown-Codezäune, kein Text davor oder danach." The user message embeds the JSON Schema of the expected output.
- Parse
choices[0].message.content: strip a leading/trailing```jsonfence if present,JSON.parse, validate with the step's Zod schema (versioned,schemafield mandatory, e.g."interview_digest.v1"). - On parse/validation failure: max 2 repair retries. Repair call = same system prompt + the invalid output + the Zod error list + "Korrigiere das JSON so, dass es dem Schema entspricht. Gib nur das korrigierte JSON aus."
- After 3 total failures → step
failed,error=schema_validation; store the last raw output in the run row for debugging (Knoll DB only — never log it to GRAG). - When platform gap 1.14 lands (gateway passes
response_formatthrough to LiteLLM), the helper switches transport but the Zod validation stays.
3.4 Cost attribution and readback
Per-step spend comes from /ledger/totals with that attempt's pipeline_id. Run and per-analysis spend are a Knoll-side sum: select the run's (or analysis's) ai_runs.pipeline_id values, call /totals once per id, sum — /totals takes exactly one pipeline_id, there is no prefix or wildcard query (03-id-conventions.md §8 rule 2). Assembled after each step and shown live in the pipeline panel:
# one call per step attempt; the Knoll backend sums the results for the run total
curl -sS "$GRAG_URL/ledger/api/v1/ledger/totals?pipeline_id=knoll-4b0c77de-scoring-4e21ac03" \
-H "Authorization: Bearer $GRAG_API_KEY" \
-H "X-Tenant-ID: $GRAG_TENANT"
Tenant-level budget display uses GET $GRAG_URL/ledger/api/v1/ledger/spend?group_by=service (only service|provider groupings exist — plan 1.6). Note: rerank and groundedness calls emit usd=0 bookkeeping rows; real spend is completions + embeddings.
4. Groundedness convention (all steps)
After each generation, the step scores its output against the context it was given (verified ScoreRequest: response_text, chunks[{text, chunk_id?}], lang_response, lang_context, maxsim_threshold?, entity_labels?, include_per_sentence, include_nli, include_spans):
curl -sS -X POST "$GRAG_URL/groundedness/api/v1/score" \
-H "Authorization: Bearer $GRAG_API_KEY" \
-H "X-Tenant-ID: $GRAG_TENANT" \
-H "X-Pipeline-Id: knoll-4b0c77de-dokument-7b2f91c4" \
-H "Content-Type: application/json" \
-d '{
"response_text": "…generated German section…",
"chunks": [
{"text": "…context block 1…", "chunk_id": "doc-7f3:12"},
{"text": "…context block 2…", "chunk_id": "fragebogen:kap-1"}
],
"lang_response": "de",
"lang_context": "de",
"include_nli": true,
"include_per_sentence": true
}'
include_spansstaysfalse— verbatim evidence spans are English-only (GROUNDEDNESS_SPAN_LANGS=en); German requests would getspans: null+ note. Per-sentence scoring and NLI are multilingual and are the German evidence mechanism.chunk_idconvention:"<document_id>:<ordinal>"for KB chunks,"questionnaire:<chapter>"/"step<N>:<section>"for non-KB context, soper_chunkverdicts map back.- Response:
{score, band, nli_groundedness_score?, context_coverage_ratio, context_unused_ratio, per_chunk[], per_sentence[]?, notes[]}. Persistband(andscore) toai_runs.groundedness_band. Handle notes as stable machine keys (e.g.per_sentence_unavailable,band_capped_at_amber,no_response_entities). For step 5's eleven sections usePOST /groundedness/api/v1/score/batch {items:[…]}(one item per lever section). - Cold start: first call after deploy can take 60–120 s unless
GROUNDEDNESS_PRELOAD=true(plan 1.4) — set the step timeout accordingly (§7). - Empty
chunks→band: "unknown"; never send an empty list — skip the call and recordunknownlocally instead.
Bands → behavior (defaults green ≥ 0.8 / amber ≥ 0.5 are uncalibrated placeholders; all behavior is advisory until plan 7.1 calibration is signed off — never auto-fail a step on band):
| Band | Review gate behavior |
|---|---|
green | Green badge; no friction. |
amber | Amber badge + per-sentence weak spots listed (from per_sentence). |
red | Red warning banner: "Geringe Quellenabdeckung — bitte Aussagen gegen die Belege prüfen." Partner must tick an explicit acknowledgement checkbox before Freigeben; acknowledgement is written to audit_log. |
unknown | Grey "nicht bewertet" hint (service down or no context). |
5. Step specifications
Common conventions: all prompts are German; every factual claim in an output must carry provenance (frage_ids for Fragebogen facts, belege: [{document_id, ordinal}] for KB facts, step_refs for prior-step facts). Placeholders {{…}} are filled by the Knoll backend. JSON Schemas below are the normative v1 contracts (mirror them 1:1 in Zod).
5.1 Step 1 — Interview (ag-05)
Inputs (frozen at run start into the run's input snapshot):
| Source | Content |
|---|---|
Knoll DB questionnaire_answers × questionnaire_versions | All answered questions of the analysis: 16 chapters (11 main + IT.0–IT.4), up to 151 questions (canonical Kundenfragebogen count, Anlage C.I.1 raw numbering — ADR-010 Knoll-Analyzer-Docs/wiki/adr/ADR-010-question-count-nodekey.md; = 14-cost-model.md §2.2 F; the prototype mock seeds only 105 of them, questionnaire.ts) plus the IT questionnaire (Anlage C.I.2), rendered as [{question_id, chapter, chapter_title, lever, question_text, type, answer}] |
Knoll DB clients | Master data (company name, legal form, industry, revenue, employees, HGB size class) |
Knoll DB analyses | Package, phase (must be ≥ 4 "Analysis & Evaluation" to start a run) |
No retrieval — this step reads only Knoll DB (source of truth for answers, plan resource mapping).
System prompt (draft):
Du bist der Interview-Agent der Knoll-Analyzer-Pipeline (Schritt 1 von 6).
Aufgabe: Überführe die Fragebogen-Antworten des Mandanten in ein strukturiertes
JSON nach den 11 Erfolgshebeln der Knoll-Methodik: Marktposition, Strategie,
Marke, Wettbewerb, Produkte & Dienstleistungen, Preispolitik, Kunden, Vertrieb,
Planung & Controlling, Marketingumsetzung, Personal.
Zusätzlich:
1. Widerspruchserkennung: Identifiziere inhaltliche Widersprüche zwischen
Antworten, auch kapitelübergreifend (Beispiel: Kapitel 7 nennt fünf
Großkunden mit rund 60 % Umsatzanteil, Kapitel 8 weist keine zentrale
Vertriebsleitung aus — unbewirtschaftetes Klumpenrisiko).
2. Rückfragen: Formuliere präzise, an den Mandanten adressierte Rückfragen zu
unklaren, fehlenden oder widersprüchlichen Angaben. Höflich, konkret,
jeweils mit Anlass.
3. Regelwerk-Fakten: Extrahiere wörtlich die für die deterministischen
Score-Deckelungen relevanten Antworten, mindestens: Frage 1-8
(Businessplan vorhanden: Ja/Nein) und Frage 2-5 (schriftliche
Unternehmensstrategie vorhanden: Ja/Nein).
Regeln:
- Verwende ausschließlich Informationen aus den übergebenen Antworten und
Stammdaten. Erfinde nichts. Bewerte nicht — die Bewertung erfolgt in
Schritt 4.
- Jede Aussage referenziert die Frage-ID(s), aus denen sie stammt.
- Nicht beantwortete Fragen erscheinen als Lücken beim betroffenen Hebel.
- Antworte NUR mit einem einzigen JSON-Objekt nach dem vorgegebenen Schema.
Keine Markdown-Codezäune, kein Text davor oder danach.
User template:
## Client
{{client_master_data_json}}
## Questionnaire answers (analysis {{analysis_id}}, questionnaire version {{questionnaire_version}})
{{answers_json}}
## Output schema (JSON Schema)
{{interview_digest_v1_json_schema}}
Output contract interview_digest.v1:
{
"schema": "interview_digest.v1",
"hebel": [
{
"hebel": "Marktposition",
"fakten": [
{"aussage": "Es liegt kein Businessplan vor.", "frage_ids": ["1-8"], "konfidenz": "hoch"}
],
"luecken": ["Frage 1-4 (Marktanteile je Geschäftsfeld) unbeantwortet"]
}
],
"regelwerk_fakten": {
"businessplan_vorhanden": false,
"schriftliche_strategie_vorhanden": false,
"quellen": {"businessplan_vorhanden": "1-8", "schriftliche_strategie_vorhanden": "2-5"}
},
"widersprueche": [
{
"beschreibung": "Kapitel 7 nennt 5 Großkunden mit ~60 % Umsatzanteil, Kapitel 8 weist keine zentrale Vertriebsleitung aus.",
"frage_ids": ["7-2", "8-4"],
"schweregrad": "hoch"
}
],
"rueckfragen": [
{
"frage": "Gibt es eine interne Planungsunterlage, die als Businessplan-Ersatz dienen kann?",
"anlass": "Frage 1-8 mit Nein beantwortet; Deckelung Marktposition droht.",
"frage_ids": ["1-8"],
"prioritaet": 1
}
]
}
Constraints: exactly 11 hebel entries in canonical ERFOLGSHEBEL order; konfidenz ∈ hoch|mittel|niedrig; schweregrad ∈ hoch|mittel|niedrig; prioritaet ∈ 1|2|3. regelwerk_fakten booleans are advisory extraction — the rules engine (§5.4… see §6) recomputes them deterministically from the raw answers in Knoll DB (frage 1-8 / 2-5 option "Nein"); on mismatch the deterministic value wins and the mismatch is flagged in the review gate.
Groundedness: response_text = concatenated fakten claims; chunks = the rendered Kapitel answer blocks (chunk_id: "fragebogen:kap-<nr>").
Token budget: input ≈ 151 answers (canonical F, ADR-010 / 14-cost-model.md §2.2) + Stammdaten ≈ 20–40k tokens (single call; the full Fragebogen must fit — pick a model with ≥100k context from the catalog); max_tokens: 6000.
Review gate specifics: the gate shows Widersprüche and Rückfragen prominently; gate action "Rückfragen an Mandant senden" hands the approved rueckfragen list to the email module (plan 2.7). Sending Rückfragen does not block the pipeline — the Partner decides whether to pause (abort/restart later) or continue.
5.2 Step 2 — Dokument (ag-06)
Inputs:
| Source | Content |
|---|---|
Knoll DB checklist_items + files + grag_refs | All items of the 46-position checklist (Anlage C.I.3, group A + B.1–B.11) with status Received/PartiallyReceived and their GRAG document_ids in the analysis-KB |
Step 1 approved output | gaps per lever (targets for gap-filling) |
GRAG analysis-KB (kb-analysis-<shortid>) | Indexed chunk text via retrieval |
Context-assembly recipe (per document, batched per Hebel where documents are shared):
- Search + rerank against the analysis-KB collection (collection name =
kb_id; German queries route to the multilingual reranker automatically):
curl -sS -X POST "$GRAG_URL/ai-gateway/api/v1/retrieval/collections/kb-analyse-7f3k/search/rerank" \
-H "Authorization: Bearer $GRAG_API_KEY" \
-H "X-Tenant-ID: $GRAG_TENANT" \
-H "X-Pipeline-Id: knoll-4b0c77de-dokument-7b2f91c4" \
-H "Content-Type: application/json" \
-d '{
"query_text": "Umsatzentwicklung und EBIT der letzten Geschäftsjahre",
"top_k": 12,
"rerank_top_n": 25,
"language": "multi"
}'
Queries come from a fixed German query catalog per lever/document type (versioned in code; e.g. Jahresabschluss → "Umsatzentwicklung und EBIT…", "Eigenkapitalquote und Bilanzsumme"; Preisliste → "Preisstruktur und Rabattsystem"; Organigramm → "Vertriebsorganisation und Verantwortlichkeiten") plus queries generated from step 1 gaps. Response envelope: data.hits[] (key preserved from voyager; each hit has id, score, rank, bm25_rank, dense_rank, rerank_score, rerank_score_norm, and payload.{doc_id, ordinal, vector_upsert_id, text} — payload.text is an ~800-char preview) and data.rerank.{retrieval_confidence, dropped_below_threshold, …}. Record retrieval_confidence per query; if none for all queries of a document, emit a gap instead of hallucinating.
- Full-text expansion: collect the hits'
(doc_id, ordinal)pairs, add neighbor ordinals ±1, dedupe, and resolve full segment text (≤64 keys per call;X-Workspace-IDrequired — the client workspace fromgrag_refs):
curl -sS -X POST "$GRAG_URL/kg-service/api/v1/segments/text:batch" \
-H "Authorization: Bearer $GRAG_API_KEY" \
-H "X-Tenant-ID: $GRAG_TENANT" \
-H "X-Workspace-ID: mandant-hartmann-a1b2" \
-H "X-Pipeline-Id: knoll-4b0c77de-dokument-7b2f91c4" \
-H "Content-Type: application/json" \
-d '{"keys": [{"document_id": "doc-7f3", "ordinal": 11},
{"document_id": "doc-7f3", "ordinal": 12},
{"document_id": "doc-7f3", "ordinal": 13}]}'
The response is found-only (missing keys silently absent) — fall back to payload.text previews for unresolved keys (documents ingested before KG_INGEST_ENABLED have no kg rows).
- Assemble context blocks per document:
[Beleg doc-7f3:12] <full text>, capped at the step's context budget (drop lowest-rerank_scoreblocks first).
System prompt (draft):
Du bist der Dokument-Agent der Knoll-Analyzer-Pipeline (Schritt 2 von 6).
Aufgabe: Werte die hochgeladenen Unterlagen des Mandanten aus (Jahresabschluss,
Preisliste, Marketingplan, Organigramm u. a.). Extrahiere Zahlen und Fakten,
ordne sie den 11 Erfolgshebeln zu und fülle gezielt die Lücken, die der
Fragebogen offen gelassen hat.
Regeln:
- Nutze ausschließlich die übergebenen Belegtexte. Jede Kennzahl und jeder
Fakt referenziert mindestens einen Beleg in der Form
{"document_id": "...", "ordinal": N} (aus den [Beleg ...]-Markierungen).
- Rechne nichts hoch und interpoliere nicht; fehlende Werte sind Lücken.
- Gleiche extrahierte Fakten mit den übergebenen Fragebogen-Aussagen ab und
markiere je Abgleich: bestätigt / widerspricht / ergänzt.
- Antworte NUR mit einem einzigen JSON-Objekt nach dem vorgegebenen Schema.
User template: Client short profile + step 1 gaps + per-document context blocks + document_findings_v1_json_schema.
Output contract document_findings.v1:
{
"schema": "document_findings.v1",
"dokumente": [
{
"document_id": "doc-7f3",
"checklist_item_id": "ck-03",
"typ": "Jahresabschluss",
"kennzahlen": [
{"name": "Umsatz 2025", "wert": "42,0", "einheit": "Mio. EUR",
"belege": [{"document_id": "doc-7f3", "ordinal": 12}]}
],
"fakten": [
{"aussage": "…", "hebel": "Planung & Controlling",
"belege": [{"document_id": "doc-7f3", "ordinal": 14}]}
],
"luecken": ["Keine Segmentberichterstattung je Geschäftsfeld enthalten"]
}
],
"fragebogen_abgleich": [
{"frage_id": "1-6", "befund": "bestätigt", "kommentar": "…",
"belege": [{"document_id": "doc-7f3", "ordinal": 12}]}
],
"retrieval_confidence_min": "weak"
}
befund ∈ bestätigt|widerspricht|ergänzt. retrieval_confidence_min is stamped by code (worst confidence over all queries), not by the LLM.
Groundedness: response_text = concatenated fakten/kennzahlen claims; chunks = the expanded segment texts (chunk_id: "<document_id>:<ordinal>").
Token budget: one call per document (or per lever bundle for small docs); context ≤ ~12k tokens per call after expansion, max_tokens: 4000. Typical analysis: 10–25 calls. Empty KB (no indexed documents): the step produces an all-gaps output and still goes to awaiting_review with a warning banner ("Keine Dokumente indexiert") — the Partner decides whether to proceed (deterministic skipping is not allowed; the Businessplan cap in §6 does not depend on this step).
5.3 Step 3 — SWOT (ag-07)
Inputs: approved outputs of steps 1 + 2 only (no retrieval — synthesis, all facts already carry provenance).
System prompt (draft):
Du bist der SWOT-Agent der Knoll-Analyzer-Pipeline (Schritt 3 von 6).
Aufgabe: Erstelle aus dem Gesamtdatenbild (Interview-Digest und
Dokument-Befunde) eine Stärken-Schwächen-Chancen-Risiken-Analyse als
Ankerpunkt für alle nachfolgenden Bewertungen.
Regeln:
- Faktenbasiert und kritisch: Jeder SWOT-Punkt stützt sich auf mindestens
eine Quelle (frage_ids und/oder belege) aus den übergebenen Daten.
- 3 bis 6 Punkte je Quadrant, jeweils ein prägnanter deutscher Satz.
- Stärken/Schwächen sind interne Befunde, Chancen/Risiken externe bzw.
zukunftsgerichtete. Ordne jedem Punkt die betroffenen Erfolgshebel zu.
- Antworte NUR mit einem einzigen JSON-Objekt nach dem vorgegebenen Schema.
Output contract swot.v1:
{
"schema": "swot.v1",
"staerken": [
{"text": "Technologieführerschaft bei Sondermaschinen für die Verpackungsindustrie",
"hebel": ["Produkte & Dienstleistungen"],
"quellen": {"frage_ids": ["5-2"], "belege": [{"document_id": "doc-7f3", "ordinal": 3}], "step_refs": []}}
],
"schwaechen": [], "chancen": [], "risiken": []
}
The four arrays map 1:1 to ExpertReport.swot.{strengths,weaknesses,opportunities,risks} (strings in the prototype; the structured items are flattened to text for the UI, provenance kept for the review gate). SwotCategory for recommendations later uses the singular vocabulary Strength|Weakness|Opportunity|Risk (lib/types.ts:31).
Groundedness: response_text = all SWOT texts; chunks = step 1 digest sections + step 2 findings blocks (chunk_id: "step1:<lever>" / "step2:<document_id>").
Token budget: input ≈ 8–15k tokens, max_tokens: 3000, single call.
5.4 Step 4 — Scoring (ag-08)
Hybrid: LLM proposes, code disposes.
Inputs: approved outputs 1–3; the active caps computed by the rules engine (§6) from raw Knoll-DB answers; knoll_ai_settings.caps_enabled.
System prompt (draft):
Du bist der Scoring-Agent der Knoll-Analyzer-Pipeline (Schritt 4 von 6).
Aufgabe: Bewerte die 11 Erfolgshebel auf der Skala 1,0 bis 5,0 (eine
Nachkommastelle; 5,0 = beste Bewertung) auf Basis von Interview-Digest,
Dokument-Befunden und SWOT.
Deterministisches Regelwerk (bereits geprüfte, verbindliche Obergrenzen für
diese Analyse): {{aktive_deckelungen_liste}}
Beispiel: "Marktposition maximal 1,5 (kein Businessplan, Frage 1-8)".
Dein Score-Vorschlag für einen gedeckelten Hebel darf die Obergrenze nicht
überschreiten; die Begründung nennt die Deckelung und den fachlichen Befund
dahinter.
Regeln:
- Je Hebel: Score-Vorschlag + Begründung (2-4 Sätze, sachlich, im Stil eines
Gutachtens) + Quellen (frage_ids/belege).
- Konsistenz zur Methodik: gleiche Faktenlage → gleicher Score. Nutze die
volle Skala; 3,0 ist keine Verlegenheitsnote.
- Bewerte nur die Hebel, erfinde keine Fakten. Fehlende Datenbasis drückt
den Score nicht automatisch — sie erscheint als Vorbehalt in der Begründung.
- Antworte NUR mit einem einzigen JSON-Objekt nach dem vorgegebenen Schema.
Output contract scoring_proposal.v1:
{
"schema": "scoring_proposal.v1",
"hebel_scores": [
{"hebel": "Marktposition", "score": 1.5,
"begruendung": "Es liegt kein Businessplan vor; nach dem Regelwerk der Methodik ist der Hebel damit deterministisch auf 1,5 gedeckelt. …",
"quellen": {"frage_ids": ["1-8"], "belege": []}}
]
}
Exactly 11 entries, canonical order, score ∈ [1.0, 5.0] in 0.1 steps.
Post-processing in code (deterministic, §6): clamp each score to its cap (score = min(llm_score, cap)) when caps_enabled; derive traffic_light per score; compute overall_score; produce the final LeverScore[] ({lever, score, traffic_light, comment} — comment = LLM begruendung, prefixed with a standard cap-sentence when code clamped a value the LLM had set higher). The review gate shows LLM proposal vs. capped value side by side whenever clamping changed a number.
Groundedness: response_text = concatenated Begründungen; chunks = step 1–3 outputs.
Token budget: input ≈ 10–15k tokens, max_tokens: 4000, single call.
5.5 Step 5 — Gutachten (ag-09)
Inputs: approved outputs 1–4 (final capped scores!); knoll_ai_settings.detail_level; retrieval from kb-methodology (Methodenhandbuch + historical expert reports in the general workspace, plan 3.3) for Knoll style and method conventions.
Context assembly: per lever, 2–4 search/rerank queries against kb-methodology (e.g. "Empfehlungsformulierung Hebel Vertrieb", "Gliederung Hebel-Abschnitt Gutachten") with top_k: 6, expanded via segments/text:batch (workspace general) — same recipe as §5.2. Plus the lever's facts from steps 1–3 and its final score from step 4.
Execution: one LLM call per lever (11 calls) — keeps context focused and lets a single lever be re-generated after review edits without re-running the rest (retry granularity: the step is approved only when all 11 sections are approved; the gate allows per-section regeneration).
System prompt (draft):
Du bist der Gutachten-Agent der Knoll-Analyzer-Pipeline (Schritt 5 von 6).
Aufgabe: Schreibe für den Erfolgshebel {{lever}} einen strukturierten
Gutachten-Abschnitt im Knoll-Stil: (1) Ist-Zustand, (2) konkrete Probleme,
(3) umsetzbare Empfehlungen mit Zeithorizont.
Stil (gemäß den übergebenen Methodik-Auszügen): sachlich, präzise,
wertschätzend-kritisch, direkte Ansprache vermeiden, deutsche
Fachterminologie der Knoll-Methodik. Der finale Score dieses Hebels ist
{{score}} ({{traffic_light}}) — der Text muss zum Score passen und darf ihn weder
relativieren noch verschärfen.
Detailgrad: {{detail_level}}.
- Kompakt: Ist-Zustand 3-5 Sätze, 1-2 Empfehlungen.
- Standard: Ist-Zustand 5-8 Sätze, 2-3 Empfehlungen.
- Ausführlich: Ist-Zustand 8-14 Sätze inkl. Benchmarks aus den
Methodik-Auszügen, 3-4 Empfehlungen.
Regeln:
- Nur belegte Aussagen (frage_ids/belege aus den übergebenen Daten).
- Jede Empfehlung: Titel, Beschreibung (2-3 Sätze), SWOT-Bezug
(Stärke/Schwäche/Chance/Risiko), Priorität 1-3 (1 = höchste),
Zeithorizont ("0–3 Monate", "3–6 Monate" oder "6–12 Monate").
- Ist der Hebel gedeckelt, benennt der Text die Deckelung und welche
Maßnahme sie aufhebt (z. B. Businessplan-Erstellung).
- Antworte NUR mit einem einzigen JSON-Objekt nach dem vorgegebenen Schema.
Output contract report_section.v1 (one per lever; step output = array of 11):
{
"schema": "report_section.v1",
"hebel": "Vertrieb",
"ist_zustand": "…",
"probleme": ["Keine schriftliche Vertriebsstrategie", "…"],
"abschnitt_text": "…zusammenhängender Fließtext des Abschnitts…",
"empfehlungen": [
{"titel": "Schriftliche Vertriebsstrategie mit Neukundenfokus erarbeiten",
"beschreibung": "…",
"swot": "Schwäche",
"prioritaet": 1,
"zeithorizont": "0–3 Monate"}
],
"quellen": {"frage_ids": ["2-8"], "belege": [{"document_id": "doc-7f3", "ordinal": 21}]}
}
Empfehlungen across all sections are collected, deduplicated and numbered by code into Recommendation[] ({id, lever, title, description, swot, priority, time_horizon} — lib/types.ts:178-187); target total 4–8 (mock expert reports have 6).
Groundedness: per section via /score/batch; chunks = that lever's fact blocks + methodology excerpts. Band recorded per section and shown per section in the gate and later in the expert-report UI (plan 6.4 "band per section").
Token budget: per call input ≈ 6–10k, max_tokens: 1500 (Compact) / 2500 (Standard) / 4000 (Detailed). 11 calls — this is the most expensive step (mock durationSeconds: 217).
5.6 Step 6 — Report (ag-10)
Inputs: approved outputs 1–5; deterministic aggregates from code (overall_score, traffic_light, radar data = the 11 scores).
LLM part — one call producing report.v1:
Du bist der Report-Agent der Knoll-Analyzer-Pipeline (Schritt 6 von 6).
Aufgabe: Verfasse (1) eine Executive Summary (Detailgrad {{detail_level}}:
Kompakt ≈ 150 Wörter, Standard ≈ 300, Ausführlich ≈ 500) und (2) exakt drei
Kernbotschaften für das Kundengespräch.
Regeln:
- Die Kernbotschaften sind je ein prägnanter Satz mit klarer Handlungsrichtung,
priorisiert nach Ergebniswirkung (vgl. Beispielstil: "… lebt von exzellenten
Produkten – aber ohne Businessplan und ohne schriftliche Strategie fährt das
Unternehmen auf Sicht.").
- Gesamtscore {{overall_score}} ({{traffic_light}}) und die Hebel-Scores sind fix und
werden wörtlich übernommen, nicht neu bewertet.
- Nur belegte Aussagen; keine neuen Fakten.
- Antworte NUR mit einem einzigen JSON-Objekt nach dem vorgegebenen Schema.
{
"schema": "report.v1",
"executive_summary": "…",
"kernbotschaften": ["…", "…", "…"]
}
Code part (assembly): see §9. Groundedness: summary + key messages vs. step 1–5 outputs. Token budget: input ≈ 12k, max_tokens: 4000.
6. Deterministic rules engine
Lives in the Knoll backend (versioned module, unit-tested; plan 4.3/5.3). Operates on raw Knoll-DB answers, never on LLM output.
6.1 Caps table (initial rule set, v1)
| Rule | Condition (deterministic, from questionnaire_answers) | Effect | Source |
|---|---|---|---|
| R-01 | Frage 1-8 ("Businessplan vorhanden?") = Nein or unanswered | MarketPosition ≤ 1,5 | Anlage C.I.1 via prototype fragebogen.ts:82 ("Ohne Businessplan wird der Erfolgshebel Marktposition methodisch auf max. 1,5 gedeckelt."); mock expert report g-02; Settings AI tab |
| R-02 | Frage 2-5 ("schriftliche Unternehmensstrategie?") = Nein or unanswered | Strategy ≤ 2,5 | Anlage C.I.1 via fragebogen.ts:132 ("Ohne schriftliche Strategie wird der Erfolgshebel Strategie auf max. 2,5 gedeckelt."); ag-08 description |
TODO-VERIFY: the ag-08 description says caps like these apply "z. B." (for example) — extract the complete Deckelungsregel catalog from the Methodenhandbuch/Anlagen in Knoll-Analyzer-Docs/ (docx/xlsx; likely Anlage D / Bewertungsmatrix) before pilot; only R-01/R-02 are verified in the prototype sources. New rules append to this table with a rule id and source citation.
Unanswered counts as Nein for cap purposes (conservative; the review gate flags it and step 1 generates a Rückfrage).
6.2 Application semantics
- Compute active caps from raw answers at run start; freeze into the run snapshot.
- If
knoll_ai_settings.caps_enabled = true(default, per Settings mock): pass the active caps into the step 4 prompt ("Regelbasierte Obergrenzen werden vor der KI-Bewertung angewendet" — Settings AI tab) and clamp in code afterwards:final = min(llm_score, cap). Defense in depth — the prompt makes the Begründung coherent, the clamp guarantees the number. - If
caps_enabled = false: caps are still computed and displayed in the review gate ("Deckelung würde greifen: MarketPosition ≤ 1,5") but not enforced; the audit entry records that caps were disabled for this run. - Every clamp writes an
audit_logentry{rule_id, lever, llm_score, capped_score}.
6.3 Traffic-light derivation (code, never LLM)
Verified against expert-reports.ts:5 and all 22 mock lever scores:
traffic_light(score) = Red if score < 2.5
= Yellow if 2.5 ≤ score ≤ 3.5
= Green if score > 3.5
6.4 Overall score (code)
overall_score = round(mean(score_1..score_11), 1) — unweighted arithmetic mean of the 11 final (capped) lever scores, one decimal; overall traffic_light from the same thresholds. Verified against both mock expert reports (g-01: mean 3.2 ✓, g-02: mean 2.69 → 2.7 ✓). TODO-VERIFY: confirm against the Methodenhandbuch that the overall score is unweighted (the Bewertungsmatrix D.III.2 weighting 35/35/25 applies to service-provider ratings, not levers — but a lever weighting may exist in the source docs).
6.5 Settings consumption (knoll_ai_settings, plan 2.1/6.6)
| Setting | Values | Consumed by |
|---|---|---|
detail_level | Compact | Standard | Detailed (Settings AI tab) | Step 5 section lengths + max_tokens; step 6 summary length |
caps_enabled | bool, default true | §6.2 |
| model picker | LiteLLM id from GET /ai-gateway/api/v1/models | §3.2 ($KNOLL_PIPELINE_MODEL) |
| HITL approval | always on (QMS requirement, not configurable) | §2.2 review gates |
Settings are read once at run start into the snapshot (mid-run changes affect the next run).
7. Failure handling
7.1 Error taxonomy → step behavior
(Full taxonomy in 11-resilience-and-errors.md; grag-client raises typed errors, plan 2.5.)
| Failure | Detection | Behavior | ai_runs.error | User-visible |
|---|---|---|---|---|
| Provider error / timeout on completion | gateway 5xx / network timeout | gateway-side fallback chain first; if the request itself failed: retry once on connection-level errors only (a completed-but-slow call must not be re-sent — cost) | llm_error | Fehlgeschlagen + "Erneut ausführen" |
| Invalid JSON after repair retries | Zod | fail step (§3.3) | schema_validation | Fehlgeschlagen; raw output attached for dev |
| 402 budget exceeded (keyvault) | HTTP 402 | fail step immediately, no retry; notify Partner + operator (plan 2.7, 7.3 80%-alert should have fired earlier) | budget_exceeded | Banner "KI-Budget erschöpft — Analyse pausiert" |
| 429 rate limited | HTTP 429 (+ Retry-After) | backoff per Retry-After, max 3 attempts, then fail | rate_limited | Läuft (verzögert) → Fehlgeschlagen |
| 503 dark flag / cold start / admission gate | HTTP 503 | backoff 30 s → 60 s → 120 s (covers rerank/groundedness model loads of 60–120 s and ~30 s deploy churn), then fail | service_unavailable | Läuft (verzögert) |
Retrieval degraded (data.rerank.degraded: true or confidence unknown) | response inspection | proceed with plain hits; stamp warning into step output; review gate shows "Reranking ausgefallen" | — | Warning badge |
| Retrieval empty for a document | 0 hits over all queries | Lücke in findings, never invented content | — | Listed under Lücken |
| Groundedness unavailable | 503 / error | do not fail the step; groundedness_band = unknown + note | — | "nicht bewertet" |
segments/text:batch misses keys | found-only response | fall back to payload.text previews | — | — |
| Step timeout | wall clock (step budget: 1/3/4 = 10 min; 2 = 30 min; 5 = 45 min; 6 = 15 min) | janitor sweeps running → failed (§2.3) | timeout | Fehlgeschlagen |
| Knoll backend crash mid-step | janitor | as timeout | timeout | Fehlgeschlagen |
7.2 Idempotency & money safety
- Completions are not idempotent — every retry is new spend. The
attemptcounter and per-run spend (Knoll-side sum of ledgertotals?pipeline_id=over the run'sai_runs.pipeline_idvalues, §3.4) are surfaced in the pipeline panel; a run exceeding a configurable soft cost limit (default from14-cost-model.md) pauses before the next step with a Partner confirmation. - Retrieval,
segments/text:batch, groundedness and ledger reads are safe to retry freely. - All GRAG calls of a step attempt carry that attempt's
X-Pipeline-Id(assigned before the first call), so orphaned spend from crashed steps is still attributed — the id exists inai_runseven if the attempt never finished.
8. Partner review gates (plan 5.3 / 2.3 / 6.4)
8.1 What is presented per step
| Step | Gate content |
|---|---|
| 1 | Digest per lever (facts with question ids, gaps); contradictions (sorted by severity); follow-up questions list with "send follow-up questions to client" action; regelwerk_fakten vs. deterministic recomputation (mismatch flagged) |
| 2 | Findings per document (metrics, facts with click-through to evidence full text via stored (document_id, ordinal)), questionnaire cross-check (contradicting items highlighted), gaps, retrieval confidence |
| 3 | SWOT quadrants with provenance chips |
| 4 | Score table: LLM proposal → cap → final score → traffic_light; active caps listed; radar preview; overall_score |
| 5 | 11 sections with per-section groundedness band, recommendations list; per-section "Neu generieren" |
| 6 | Executive Summary, 3 key messages, full assembled preview, PDF preview |
Every gate additionally shows: model used, tokens, cost so far (ledger), groundedness band (+ red-band acknowledgement checkbox, §4), attempt number.
8.2 Actions
| Action | Who | Effect |
|---|---|---|
| Freigeben (approve) | Partner only (server-side role check, plan 2.3/7.7) | step → approved; next step queued; audit entry |
| Bearbeiten (edit) | Partner (Analyst may draft edits; Freigabe stays Partner) | inline edit of the output (re-validated against the step's Zod schema); edited=true on the run row; edit diff hash in audit; then Freigeben |
| Ablehnen (reject) | Partner | comment mandatory; step → rejected; new attempt queued with the comment injected as additional instruction: "Hinweis des Prüfers zur vorherigen Fassung: …" |
| Rückfragen senden (step 1 only) | Partner or Analyst | emails via plan 2.7; logged |
| Lauf abbrechen | Partner | run cancelled (active step attempt → cancelled, §2.2); audit entry |
8.3 Audit entries (audit_log, plan 2.1)
One row per gate action: {actor_user_id, role, action ∈ step_approved|step_edited|step_rejected|run_started|run_cancelled|red_band_acknowledged|cap_applied|caps_disabled_run, analysis_id, pipeline_id, step, attempt, payload (comment/diff-hash/rule_id), created_at}. Final approval of the expert report (step 6 approve) additionally writes the expert_reports version row (§9) and is the QMS-relevant "durch einen Partner freigegeben" record.
9. Final assembly, PDF, ingest (step 6 code part)
After step 6 is approved:
- Assemble the
expert_reportsrow (versioned, Knoll DB = source of truth):{analysis_id, status: "Provisional", created_at, overall_score, traffic_light, key_messages[3], lever_scores[11] (score, traffic_light, comment), swot{…}, recommendations[], pipeline: derived from ai_runs}— matchinglib/types.ts:195-213.statusflipsProvisional → Finalonly via the separate expert-report approval on the expert-report route (plan 6.4), which also triggers the deterministic KG projection (plan 3.6:SCORES {score, traffic_light, analysis_id},ADDRESSES,RECOMMENDSedges — see07-kg-schema-knoll-advisory.md; requires platform gap 1.11). - Render PDF in the Knoll backend (renderer is a Knoll implementation choice — e.g. headless-Chromium print of the report route; GRAG plays no part) and store it in the Knoll file store (plan 2.4) with a
filesrow{expert_report_id, sha256, mime: application/pdf}. GRAG keeps no binaries — the file store copy is what the PDF-Export button (plan 6.4) and GDPR Auskunft (7.5) serve. - Ingest into the analysis-KB so future KB-Chat ("Frag die Akte",
09-chat-integration.md) can retrieve the expert report (⚠ blocked for real data by plan 1.8 — the BFF is unauthenticated today; theAuthorizationheader below is the post-1.8 contract):
# 3a. upload (multipart) → docfold conversion job
curl -sS -X POST "$GRAG_URL/next/api/upload/start" \
-H "Authorization: Bearer $GRAG_API_KEY" \
-H "X-Tenant-ID: $GRAG_TENANT" \
-F "kb_id=kb-analyse-7f3k" \
-F "files=@gutachten-a02-v1.pdf;type=application/pdf"
# → {"jobs":[{"filename":"gutachten-a02-v1.pdf","docfoldJobId":"…","documentId":"…"}]}
# 3b. index
curl -sS -X POST "$GRAG_URL/next/api/ingest/start" \
-H "Authorization: Bearer $GRAG_API_KEY" \
-H "X-Tenant-ID: $GRAG_TENANT" \
-H "Content-Type: application/json" \
-d '{"kb_id": "kb-analyse-7f3k", "document_id": "<documentId>",
"docfold_job_id": "<docfoldJobId>", "filename": "gutachten-a02-v1.pdf"}'
# → {"jobId":"…"} — progress via SSE GET /next/api/orchestrator/events/{jobId}
Record documentId in grag_refs linked to the expert_reports version. A newer expert-report version supersedes the old document (POST /workspaces/api/v1/documents/{id}/supersede, plan 4.4) so retrieval never mixes versions.
4. Notify "expert report ready for approval" (plan 2.7) and update ai_agents run stats (last_run, total_runs derive from ai_runs, plan 6.1).
10. Preconditions checklist (before the first real run)
| Precondition | Plan ref |
|---|---|
| BFF auth deployed (else step 6 ingest + all chat lanes are publicly abusable) | 1.8 (blocker) |
Analysis-KB provisioned + documents indexed; kb-methodology ingested | 3.1, 3.3, Phase 4 |
TENANT_SETTINGS_ENABLED, preloads (RERANK_FUSION_PRELOAD, GROUNDEDNESS_PRELOAD), KG_INGEST_ENABLED live | 0.9, 1.4 |
KG_RELATION_EXTRACTION_ENABLED=false or knoll-advisory v1 without relationships (else auto-edges pollute the graph) | 0.7, 1.4, 3.4 |
| Keyvault keys + budget + 80% alert | 1.6, 7.3 |
| Partner role enforcement server-side | 2.3, 7.7 |
| German PII posture decided (firewall is OFF and EN-only — questionnaire answers and document text go to the LLM provider un-redacted) | 1.10 |
| Groundedness calibration status known (bands advisory until 7.1) | 7.1 |
Open items (TODO-VERIFY)
- TODO-VERIFY (§6.1): complete cap-rule catalog from
Knoll-Analyzer-Docs/Methodenhandbuch/Anlagen — only R-01 (no business plan → MarketPosition ≤ 1,5) and R-02 (no written strategy → Strategy ≤ 2,5) are verified from the prototype sources; ag-08's "z. B." wording implies more rules exist. - TODO-VERIFY (§6.4): overall-score aggregation — unweighted mean matches both mock expert reports, but confirm no lever weighting exists in the Methodenhandbuch.
- TODO-VERIFY (§4): live capability check
GET $GRAG_URL/groundedness/api/v1/capabilitieson app.grag.ai —per_sentence_supportedmust be true on the deployed backend (schema docs note per-sentence is "local backend only in v1"; if the deployment uses a gateway backend, per-sentence returns null +per_sentence_unavailablenote and the review UI must degrade gracefully). - TODO-VERIFY (§5.2): whether the analysis-KB collections are created as
engine: denseby the BFF ingest lane in the live deployment (free-textquery_textsearch requires a dense collection;shard/ColBERT collections would need caller-side query embeddings).