# solution-wizard

Adopt this skill: https://proskills.md/skills/other/gaik-project-gaik-toolkit-implementation-layer-solution-wizard

Page: https://proskills.md/skills/other/gaik-project-gaik-toolkit-implementation-layer-solution-wizard
This document is the public skill bundle for that page. It contains SKILL.md and every helper file the catalog carries. No login is required.

## File: implementation_layer/solution_wizard/SKILL.md
size: 76682
sha: 4777d8381f5ae508179e5c17c3c983b0a072df74
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/SKILL.md
truncated: true

````text
---
name: solution-wizard
description: GAIK Solution Configuration Wizard. Guides the user from a natural-language use-case description to a validated executable blueprint (JSON), a Mermaid workflow diagram, a standards-based BPMN 2.0 visual blueprint, a runnable PoC, and a use-case documentation suite. Collects the complete requirement set (with a completeness gate), reasons about each component's behaviour-changing options, selects GAIK components, generates and validates the blueprint, and saves all outputs to a user-chosen directory.
---

# GAIK Solution Configuration Wizard

You are the GAIK Solution Configuration Wizard. Your job is to help users design GenAI use cases using the GAIK toolkit. You guide the user through a structured conversation, then generate and validate an executable blueprint (JSON) and two derived visual views: a Mermaid workflow diagram and a standards-based BPMN 2.0 visual blueprint.

**What you output:**
- `use_case.blueprint.json` -- the executable blueprint saved to the user's chosen directory (the single source of truth)
- `workflow.mmd` -- a Mermaid diagram of the selected workflow (quick technical view)
- `workflow.bpmn` -- a BPMN 2.0 business-process model (the visual blueprint; derived from the JSON, linked by `visualizations.bpmn_mapping`)
- `poc/` -- a minimal runnable proof of concept
- A concise specification summary and handoff message shown in the conversation

**Your implementation scripts** (relative to this SKILL.md):
- `scripts/check_requirements.py` -- checks Section-9 requirement completeness (Gate 1)
- `scripts/validate_blueprint.py` -- validates a blueprint JSON against all rules
- `scripts/generate_mermaid.py` -- generates workflow.mmd from a blueprint
- `scripts/generate_bpmn.py` -- generates workflow.bpmn (BPMN 2.0 visual blueprint) from a blueprint
- `scripts/generate_schema.py` -- calls GAIK SchemaGenerator once to generate the extraction schema (Phase 4)
- `scripts/scaffold_poc.py` -- scaffolds the poc/ folder from a validated blueprint
- `scripts/generate_docs.py` -- generates the documentation suite from the validated blueprint (Phase 12)
- `scripts/promote_template.py` -- generalize-then-save a validated hybrid PoC into the template library (optional)
- `scripts/run_wizard.py` -- CLI entry point (`--show-registry`, `--export-schema`)

**Constraints:**
- Never write any files inside the GAIK repo (`implementation_layer/`). All outputs go to the user-chosen directory.
- Never proceed to blueprint generation until Gate 1 (spec confirmation) is passed.
- Never finalize a blueprint with validation errors. Fix them first.
- Keep the conversation natural. Ask one or two questions at a time, not a long list.
- Record every assumption you make when information is missing.
- **Both diagrams are derived views, never the source of truth.** When the user asks to
  change anything in the BPMN or the Mermaid diagram — a step, a swimlane, a gateway, a
  participant, a hand-off, an exception path, the order of steps, or any other visual
  element — identify the corresponding field(s) in `use_case.blueprint.json`
  (`workflow.steps`, `artifacts`, `business_process`, or `technical_spec`), update those
  fields first, re-validate the blueprint, then regenerate both `workflow.bpmn` and
  `workflow.mmd`. **Never edit `workflow.bpmn` or `workflow.mmd` directly** — any changes
  made to the diagram files are silently overwritten the next time the diagrams are
  regenerated. This rule applies at every phase of the conversation, not only at Gate 2 or
  Gate 3.

---

## Phase 1: Session Start

Begin every wizard session with exactly these two questions, in this order.

**Step 1.1 -- Output directory:**

```
Where would you like to save the generated files?
(Enter a folder path, e.g. ~/projects/my-use-case or C:\work\my-use-case)
```

Store the path. Confirm it back to the user. All files will be written there.

**Step 1.2 -- Use-case description:**

```
What business or operational task should the GenAI solution support?
Please describe:
  - what the current process looks like (without AI)
  - what the input material is (audio recordings, PDFs, documents, etc.)
  - what output you want the system to produce
```

After receiving the description, classify it yourself (you own this decision -- no script does it) into one of these conventional patterns:
- `audio_to_structured` -- audio/video input, structured JSON output
- `document_to_structured` -- PDF/DOCX input, structured JSON output
- `rag` -- document collection + question answering
- `vision_extraction` -- images or scanned documents, structured JSON output
- `classification` -- documents to categories
- `transcript_only` -- audio to transcript only
- `multi_source_report` -- any mix of audio, documents, images, or text → narrative report
- `hybrid` -- combination of the above that does not fit any single pattern

These labels are conventions, not a fixed enum -- if a use case does not fit cleanly, pick the closest one (or `hybrid`) and proceed. Each label maps to a canonical transformation chain in `src/solution_wizard/selector.py` (`CHAINS`) that you can consult as a scaffold when building the workflow, and to the module-first map (`module_for_pattern`) that tells you whether a single GAIK module covers the pattern. Treat both as hints you may override.

State your classification to the user in one sentence before moving on.

---

## Phase 2: Complete Requirement Collection

Collect the **full Section-8 requirement model** — not just a fast path. Ask conversationally in **thematic rounds**, grouping related questions so the user never faces a long questionnaire. Carry over anything already stated in the use-case description; only ask what is still missing. If the user signals they want a quick PoC you may move faster, but still record every field — and where something is genuinely not known, mark it explicitly (do not silently skip).

**How to present a round.** The user has to be able to see, at a glance, exactly what they are being asked. So:

- At most **3 questions per turn**. If a round needs more, split it across turns.
- Lead with **one short sentence** of context, not a paragraph.
- Put the questions in a **numbered Markdown list**, one question per item, with any examples in parentheses inside the item. Do not scatter questions through prose.
- End the turn with a horizontal rule (`---`) and a single closing line telling the user how to answer (for example: "Answer what you can, and say *skip* for anything you are unsure of.").

The rule is a visual anchor: everything above it is context, everything the user must act on is the list directly above it.

**Round 1 — Business context (§8.1).** Three §8.1 fields are already recorded in Phase 1: `use_case_name` → `use_case.name`, `domain` → `use_case.domain`, and `knowledge_processes` → `use_case.knowledge_processes` (set during Step 1.2 description + pattern classification). Do not re-ask them. Collect the remaining §8.1 fields here: `current_process`, `pain_points`, `proposed_solution`, `intended_users`, `reviewers`, `stakeholders`, `input_artifacts` (business-level), `target_outputs` (business-level), `success_criteria`, `expected_value`, `risks`. Also capture `poc_goal` (what the first proof of concept should demonstrate) — this is a wizard addition not in §8.1 but required for completeness checklist point 13; store it in `business_spec.poc_goal`.

**Round 2 — Technical (§8.2).** `input_types`, `input_formats`, `output_types`, `language`, `domain_vocabulary` (terms / codes / controlled lists — record `"none"` if not needed), `data_sources`, `model_provider` (or `"configurable"`), `model_preferences` (model names, temperature, reasoning_effort, embedding/transcription model), `security_constraints`, `integration_targets` (record `[]` if none), `human_review` (yes / no / conditional), `evaluation_requirements` (metrics, test data, thresholds), `runtime_interface`.

When discussing `output_types`, also ask
````

## File: implementation_layer/solution_wizard/README.md
size: 41560
sha: b43f027b951eefb07c4cdc87cfd7447dcada138b
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/README.md

````text
# GAIK Solution Configuration Wizard

The GAIK Solution Configuration Wizard guides users from a natural-language business problem to a validated, executable blueprint, a standards-based BPMN visual blueprint, a runnable proof of concept, and a documentation suite.

> **Full specification:** [`docs/solution_wizard.md`](docs/solution_wizard.md) — detailed requirements, design decisions, release plan, JSON blueprint schema, component registry model, validation rules, and implementation guidance for both V1 (current) and V2 (future).

**What the wizard delivers (V1 — current):**
- **Complete requirement collection** — Business, technical, and target-output requierments collected conversationally in thematic rounds, with a **completeness gate** that asks follow-up questions for anything missing instead of silently assuming
- **Component selection:** agent driven component selection from a registry 
- **Component-option awareness** — the wizard reasons about each component's behaviour-changing options (recorded in the reference cards) and infers them from the requirements or asks.
- **Two complementary blueprints** generated from the validated specification:
 - **JSON blueprint** the executable source of truth: components, artifacts, workflow steps, schemas, prompts, model settings, validation gates. Drives all downstream generation.
 - **BPMN visual blueprint** a standards-based BPMN 2.0 business-process model with swimlanes for each stakeholder role, typed tasks, gateways, data objects/stores, and message flows. Linked element-by-element to the JSON via; kept in sync by editing the JSON and regenerating.
- **Runnable PoC:** with schema files, extraction prompt, evaluation script
- **Refinement loop:** user runs PoC, shares output, wizard classifies the change (intent vs. implementation fix), updates the blueprint first for intent changes and re-validates before regenerating PoC files, or patches the PoC directly for code-only fixes
- **Documentation suite** — GenAI product canvas, technical specification, user guide, developer guide, evaluation plan

---

## Installation

Clone the repository first:

```bash
git clone https://github.com/GAIK-project/gaik-toolkit.git
```

The wizard itself requires:
- Python 3.11+
- `pydantic>=2`

```bash
pip install pydantic
```

For the interactive SDK runner (`run_wizard_interactive.py`), also install:

```bash
pip install "gaik[wizard]"   # installs claude-agent-sdk + python-dotenv
```

The PoC generated by the solution wizard will have its own `poc/requirements.txt` listing the exact GAIK extras for the selected components. Install them with:

```bash
pip install -r poc/requirements.txt
```

---

## Running the wizard

### Option 1 — Claude Code (CLI) or Claude Desktop (recommended)

Type `/solution-wizard` in a Claude Code session or Claude Desktop chat.

The wizard asks for an output directory and guides you through the full workflow. All generated files are saved to your chosen directory.

### Option 2 — Interactive wizard session via the Claude Agent SDK (CLI)

`run_wizard_interactive.py` starts a persistent, multi-turn conversation with the wizard
driven by the Claude Agent SDK (`ClaudeSDKClient`) on Azure Foundry. The wizard streams
responses token-by-token; you type answers and press Enter. The session continues until you
press Ctrl+D or Ctrl+C.

```bash
# Activate the venv that has claude-agent-sdk installed (pip install gaik[wizard])
# then, from the solution_wizard directory:

# Wizard asks for the output directory itself:
python run_wizard_interactive.py

# Pre-supply the output directory (skips that question):
python run_wizard_interactive.py --output-dir "C:\projects\my-use-case"
```

Requires Azure Foundry credentials in `../.env` (one level up from this directory) or the
current shell environment. See `.env.example` for the required variables
(`CLAUDE_CODE_USE_FOUNDRY`, `ANTHROPIC_FOUNDRY_API_KEY`, `ANTHROPIC_FOUNDRY_RESOURCE`,
`ANTHROPIC_DEFAULT_SONNET_MODEL`). Run `python run_wizard_interactive.py --preflight` to
confirm routing before a full session.

### Option 3 — Wizard as a web chat (demo website)

The wizard is also integrated as a streaming chat page in the demo website at
`implementation_layer/toolkit_demo_app`. The backend (`api/routers/solution_wizard.py`)
keeps a `ClaudeSDKClient` session alive per browser tab and streams tokens over SSE;
the frontend renders responses with full markdown + Mermaid support.

```bash
cd implementation_layer/toolkit_demo_app
uv run uvicorn api.main:app --port 8000 # backend (no --reload to avoid session drops)
bun dev # frontend at http://localhost:3000
```

Open **http://localhost:3000/solution-wizard**. Generated files (blueprint, diagrams, docs,
PoC) appear in the sidebar and can be downloaded individually or as a `.zip`. See
`implementation_layer/toolkit_demo_app/README.md` for the full setup.

---

## Wizard workflow overview

The diagram below shows the complete vision for the wizard across both releases.

![GAIK Solution Configuration Wizard — full workflow (V2 vision)](images/solution_wizard_v2_workflow.png)

**What is implemented in V1 (current):** phases 1–12 as shown — session start and use-case intake (1), guided requirement collection (2), specification generation (3), Gate 1 specification validation (4), component selection (5), executable JSON blueprint (7), BPMN visual blueprint (8), Gate 2 workflow validation, PoC creation (10), PoC validation and refinement / Gate 3 (11), documentation suite. The BPMN is regenerated from the JSON blueprint and never edited directly; during PoC validation the wizard updates the JSON based on user feedback and regenerates the BPMN accordingly.

**What is planned for V2:** a dedicated Solution Wizard web UI that hosts the full wizard workflow as a structured multi-pane workspace; interactive BPMN editing within that UI (live canvas, step configuration panel, wizard chat, synchronized JSON ↔ BPMN); full use-case package generation; Gate 4 runtime validation of the full package; and production-grade error handling and guardrails (scoped to GAIK toolkit use cases, safety constraints, graceful recovery, and session state safety).

---

## How the workflow works (complete walkthrough)

The **agent** (the LLM following `SKILL.md`) is the brain of the wizard. Everything in `scripts/` and `src/` is a deterministic helper it calls. Nothing in the Python layer talks to the user — only the agent does.

> **The agent produces meaning** (interpretation, classification, component selection, authoring the blueprint). **The Python scripts enforce correctness** (Pydantic type rules + the validator rules) and **render the derived views** (BPMN visual blueprint, PoC files, documentation). The wizard produces two complementary blueprints: the **JSON blueprint** (executable source of truth, drives all generation) and the **BPMN visual blueprint** (standards-based business-process view for stakeholder communication and review).

### Worked example

**The user's description:**

> *"We run a facilities maintenance team. Our field technicians call in equipment faults as Finnish voice messages — they describe what's broken, where it is, and how urgent it is. We'd like to turn these into structured maintenance tickets that a supervisor checks before they go into the system."*

---

**Phase 1 — Session start.** The agent asks for the output directory (`~/projects/maintenance-tickets`) and the use-case description, then classifies the pattern: audio input + structured JSON output → `audio_to_structured`. Consults `selector.py` as reference data.

**Phase 2 — Complete requirement collection .** The agent collects the full Section-8 model in thematic rounds (business, technical, target output). Key answers: audio input, **Finnish** language, structured JSON output, human review required, technician names are personal data, integration target = the maintenance system, evaluation = field-level accuracy. The agent (not a script) maps "Finnis
````

## File: implementation_layer/solution_wizard/scripts/check_requirements.py
size: 1451
sha: 4399a759551e08408476d34542d3e5948104c399
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/scripts/check_requirements.py

```text
#!/usr/bin/env python3
"""Check requirement completeness against the Section-9 checklist (V3 Gate 1).

Run before component selection. Reports which of the 13 checklist points are
answered vs. still missing, so the wizard knows exactly what follow-up
questions to ask. Exit code 0 when complete, 1 when points are still missing.

Usage:
    python check_requirements.py --blueprint path/to/use_case.blueprint.json
"""

import argparse
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from solution_wizard.blueprint import Blueprint
from solution_wizard.requirements import check_completeness, summary

def main() -> int:
    parser = argparse.ArgumentParser(
        description="Check Section-9 requirement completeness for a (draft) blueprint."
    )
    parser.add_argument("--blueprint", required=True, help="Path to use_case.blueprint.json (draft is fine)")
    args = parser.parse_args()

    blueprint_path = Path(args.blueprint)
    if not blueprint_path.exists():
        print(f"ERROR: Blueprint file not found: {blueprint_path}", file=sys.stderr)
        return 1

    try:
        blueprint = Blueprint.from_file(blueprint_path)
    except Exception as exc:
        print(f"ERROR: Failed to load blueprint: {exc}", file=sys.stderr)
        return 1

    print(summary(blueprint))
    return 0 if not check_completeness(blueprint) else 1

if __name__ == "__main__":
    sys.exit(main())
```

## File: implementation_layer/solution_wizard/scripts/generate_bpmn.py
size: 3027
sha: 96bf172f74bd77c70750e9fb2b8a96182e6c82aa
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/scripts/generate_bpmn.py

```text
#!/usr/bin/env python3
"""Generate a BPMN 2.0 diagram from a blueprint and write workflow.bpmn.

The BPMN is the *visual blueprint*: a standards-based, Level-2 business-process
model derived from the blueprint JSON. It is linked-by-derivation -- every
element id maps back to a blueprint object (the map is written to
visualizations.bpmn_mapping). Keep it in sync by editing the JSON and
regenerating; never hand-edit the diagram.

Validates the blueprint before rendering by default. Use --skip-validation to
render regardless (e.g. while iterating on a draft blueprint).

Usage:
    python generate_bpmn.py --blueprint path/to/blueprint.json --output-dir path/to/output/
    python generate_bpmn.py --blueprint path/to/blueprint.json --output-dir path/to/output/ --skip-validation
"""

import argparse
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from solution_wizard.blueprint import Blueprint
from solution_wizard.bpmn_generator import write_bpmn
from solution_wizard.validator import validate

def main() -> int:
    parser = argparse.ArgumentParser(description="Generate BPMN 2.0 diagram from blueprint.")
    parser.add_argument("--blueprint", required=True, help="Path to use_case.blueprint.json")
    parser.add_argument("--output-dir", required=True, help="Directory to write workflow.bpmn")
    parser.add_argument(
        "--skip-validation",
        action="store_true",
        help="Skip blueprint validation and render anyway (useful for draft blueprints)",
    )
    args = parser.parse_args()

    blueprint_path = Path(args.blueprint)
    if not blueprint_path.exists():
        print(f"ERROR: Blueprint not found: {blueprint_path}", file=sys.stderr)
        return 1

    try:
        blueprint = Blueprint.from_file(blueprint_path)
    except Exception as exc:
        print(f"ERROR: Failed to load blueprint: {exc}", file=sys.stderr)
        return 1

    if not args.skip_validation:
        result = validate(blueprint)
        if not result.ok:
            print(result.summary(), file=sys.stderr)
            print(
                "\nBlueprint has validation errors. Fix them first, or use "
                "--skip-validation to render anyway.",
                file=sys.stderr,
            )
            return 1
        for w in result.warnings:
            print(f"WARNING {w}")

    bpmn_path = write_bpmn(blueprint, args.output_dir)
    print(f"BPMN diagram written to: {bpmn_path}")

    # Persist the updated blueprint so visualizations.bpmn_mapping is saved.
    # write_bpmn() populates the mapping in-memory; without this the map is lost
    # when the process exits (run_wizard.py writes the blueprint, but the
    # standalone CLI must do it too to stay consistent with the documentation).
    out_blueprint = Path(args.output_dir) / "use_case.blueprint.json"
    blueprint.to_file(out_blueprint)
    print(f"Blueprint (with bpmn_mapping) saved to: {out_blueprint}")
    return 0

if __name__ == "__main__":
    sys.exit(main())
```

## File: implementation_layer/solution_wizard/scripts/generate_docs.py
size: 2331
sha: 02da1d0b283d010d4c4cb19517797db61a34e071
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/scripts/generate_docs.py

```text
#!/usr/bin/env python3
"""Generate the use-case documentation suite (spec §18) into <output_dir>/docs/.

Produces five documents from the validated blueprint: GenAI product canvas,
technical specification, user guide, developer guide, and evaluation plan. They
are deterministic skeletons pre-filled with blueprint facts; the wizard then
fills the `<!-- AGENT: ... -->` narrative markers.

Validates the blueprint first by default. Use --skip-validation for drafts.

Usage:
    python generate_docs.py --blueprint path/to/use_case.blueprint.json --output-dir path/to/output/
"""

import argparse
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from solution_wizard.blueprint import Blueprint
from solution_wizard.docs_generator import generate_docs
from solution_wizard.validator import validate

def main() -> int:
    parser = argparse.ArgumentParser(description="Generate the use-case documentation suite.")
    parser.add_argument("--blueprint", required=True, help="Path to use_case.blueprint.json")
    parser.add_argument("--output-dir", required=True, help="Directory; docs are written to <output-dir>/docs/")
    parser.add_argument("--skip-validation", action="store_true", help="Render even if validation fails (drafts)")
    args = parser.parse_args()

    blueprint_path = Path(args.blueprint)
    if not blueprint_path.exists():
        print(f"ERROR: Blueprint not found: {blueprint_path}", file=sys.stderr)
        return 1

    try:
        blueprint = Blueprint.from_file(blueprint_path)
    except Exception as exc:
        print(f"ERROR: Failed to load blueprint: {exc}", file=sys.stderr)
        return 1

    if not args.skip_validation:
        result = validate(blueprint)
        if not result.ok:
            print(result.summary(), file=sys.stderr)
            print("\nBlueprint has validation errors. Fix them first, or use --skip-validation.", file=sys.stderr)
            return 1
        for w in result.warnings:
            print(f"WARNING {w}")

    written = generate_docs(blueprint, args.output_dir)
    print("Documentation suite written:")
    for p in written:
        print(f"  - {p}")
    print("\nNext: fill the <!-- AGENT: ... --> narrative sections in each document.")
    return 0

if __name__ == "__main__":
    sys.exit(main())
```

## File: implementation_layer/solution_wizard/scripts/generate_mermaid.py
size: 2207
sha: 9835e0135f1a752505dc9833825a776aadefb9f9
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/scripts/generate_mermaid.py

```text
#!/usr/bin/env python3
"""Generate a Mermaid diagram from a blueprint and write workflow.mmd.

Validates the blueprint before rendering by default. Use --skip-validation
to render regardless (e.g. while iterating on a draft blueprint).

Usage:
    python generate_mermaid.py --blueprint path/to/blueprint.json --output-dir path/to/output/
    python generate_mermaid.py --blueprint path/to/blueprint.json --output-dir path/to/output/ --skip-validation
"""

import argparse
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from solution_wizard.blueprint import Blueprint
from solution_wizard.validator import validate
from solution_wizard.visualizer import write_mermaid

def main() -> int:
    parser = argparse.ArgumentParser(description="Generate Mermaid diagram from blueprint.")
    parser.add_argument("--blueprint", required=True, help="Path to use_case.blueprint.json")
    parser.add_argument("--output-dir", required=True, help="Directory to write workflow.mmd")
    parser.add_argument(
        "--skip-validation",
        action="store_true",
        help="Skip blueprint validation and render anyway (useful for draft blueprints)",
    )
    args = parser.parse_args()

    blueprint_path = Path(args.blueprint)
    if not blueprint_path.exists():
        print(f"ERROR: Blueprint not found: {blueprint_path}", file=sys.stderr)
        return 1

    try:
        blueprint = Blueprint.from_file(blueprint_path)
    except Exception as exc:
        print(f"ERROR: Failed to load blueprint: {exc}", file=sys.stderr)
        return 1

    if not args.skip_validation:
        result = validate(blueprint)
        if not result.ok:
            print(result.summary(), file=sys.stderr)
            print(
                "\nBlueprint has validation errors. Fix them first, or use "
                "--skip-validation to render anyway.",
                file=sys.stderr,
            )
            return 1
        for w in result.warnings:
            print(f"WARNING {w}")

    mmd_path = write_mermaid(blueprint, args.output_dir)
    print(f"Mermaid diagram written to: {mmd_path}")
    return 0

if __name__ == "__main__":
    sys.exit(main())
```

## File: implementation_layer/solution_wizard/scripts/generate_schema.py
size: 13974
sha: a0b476ae39eac154222232fb240a18c44c6bb8c2
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/scripts/generate_schema.py

```text
#!/usr/bin/env python3
"""Generate the extraction schema using the GAIK SchemaGenerator.

Called once during Phase 5 (Schema Design) of the wizard conversation.
The wizard presents the generated schema to the user for review; the user
approves or requests changes; approved changes are incorporated by the
wizard directly (no second SchemaGenerator call). The approved schema is
then saved and reused by run_poc.py via load_schema().

Flow:
    Phase 5 in SKILL.md:
        1. Call this script to generate the schema from extraction_requirements.md
        2. Present output_schema.py to the user for review
        3. User approves or requests changes (wizard edits directly, no re-generation)
        4. Confirmed schema is already saved -- scaffold_poc.py will use it as-is

Usage:
    python scripts/generate_schema.py \\
        --requirements poc/prompts/extraction_requirements.md \\
        --schema-name MaintenanceTicket \\
        --output-dir poc/
        [--provider azure|openai|google|aitta|litellm|...]
        [--use-azure / --no-azure]
        [--model gpt-6-luna]
        [--base-url https://your-inference-server.example/v1]

An explicit --provider overrides the legacy Azure flags. Without --provider,
Azure remains the default and --no-azure selects OpenAI. Credentials come from
the selected provider's environment variables, never command-line arguments.
"""

from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path
from typing import Literal, Union, get_args, get_origin

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

# GAIK imports -- only needed at runtime, not at scaffold time
try:
    from gaik.software_components.extractor.schema import (
        DECIMAL_PERSISTED_HELPER_SOURCE,
        CompositeExtractionRequirements,
        ExtractionRequirements,
        SchemaGenerator,
        decimal_field_repr,
    )
    from gaik.software_components.llm import Provider, get_llm_config

    _GAIK_AVAILABLE = True
except ImportError as _import_err:
    _GAIK_AVAILABLE = False
    _IMPORT_ERR = _import_err

def _annotation_repr(annotation) -> str:
    """Return a Python source representation for common Pydantic field types.

    Routes Decimal fields to the safe DecimalField/OptionalDecimalField
    aliases instead of the plain type -- see decimal_field_repr's docstring
    (gaik.software_components.extractor.schema) for why field.annotation
    alone can no longer tell a "made safe" Decimal field apart from a plain
    one, and _DECIMAL_HELPER_SOURCE below for what those aliases need.
    """
    decimal_repr = decimal_field_repr(annotation)
    if decimal_repr is not None:
        return decimal_repr

    origin = get_origin(annotation)

    if origin is list:
        args = get_args(annotation)
        return f"list[{_annotation_repr(args[0])}]" if args else "list"

    if origin is Literal:
        args = get_args(annotation)
        return f"Literal[{', '.join(repr(arg) for arg in args)}]"

    if origin is Union:
        args = get_args(annotation)
        non_none = [arg for arg in args if arg is not type(None)]
        if len(non_none) == 1 and type(None) in args:
            return f"Optional[{_annotation_repr(non_none[0])}]"
        return f"Union[{', '.join(_annotation_repr(arg) for arg in args)}]"

    # Python 3.10+ union syntax, e.g. str | None.
    try:
        import types as _types

        if isinstance(annotation, _types.UnionType):
            args = get_args(annotation)
            non_none = [arg for arg in args if arg is not type(None)]
            if len(non_none) == 1 and type(None) in args:
                return f"Optional[{_annotation_repr(non_none[0])}]"
            return f"Union[{', '.join(_annotation_repr(arg) for arg in args)}]"
    except AttributeError:
        pass

    if annotation is type(None):
        return "None"

    if hasattr(annotation, "__name__"):
        return annotation.__name__

    return repr(annotation)

def _collect_models(model: type) -> list[type]:
    """Collect nested Pydantic models before the parent model."""
    seen: set[type] = set()
    ordered: list[type] = []

    def collect(current: type) -> None:
        if current in seen:
            return
        seen.add(current)
        for field in current.model_fields.values():
            if get_origin(field.annotation) is list:
                args = get_args(field.annotation)
                if args and isinstance(args[0], type) and hasattr(args[0], "model_fields"):
                    collect(args[0])
        ordered.append(current)

    collect(model)
    return ordered

def _schema_to_py(schema_class: type, schema_name: str) -> str:
    """Render the generated Pydantic model (and any nested child models) to a
    .py file string.

    Builds source directly from model_fields rather than via
    print_pydantic_schema(): that helper reads field.annotation, which
    strips the Annotated/WithJsonSchema/BeforeValidator wrapping
    create_extraction_model() applies to Decimal fields (see
    decimal_field_repr's docstring) -- regenerating from the bare
    annotation would silently drop the safe Decimal representation,
    restoring both the regex-lookaround schema-rejection failure and the
    "12.40 EUR"-crashes-Decimal-parsing failure on the next PoC run.
    """
    class_blocks: list[str] = []
    needs_decimal_helper = False

    for current_model in _collect_models(schema_class):
        lines = [f"class {current_model.__name__}(BaseModel):"]

        docstring = (current_model.__doc__ or "").strip()
        if docstring:
            lines.append(f'    """{docstring}"""')

        lines.append("    model_config = ConfigDict(extra='forbid')")
        lines.append("")

        for field_name, field in current_model.model_fields.items():
            field_args: list[str] = []

            if field.description:
                field_args.append(f"description={field.description!r}")

            if field.default_factory is not None:
                factory_name = getattr(field.default_factory, "__name__", None)
                if factory_name in {"list", "dict", "set"}:
                    field_args.append(f"default_factory={factory_name}")
                else:
                    field_args.append(f"default={field.default_factory()!r}")
            elif not field.is_required():
                field_args.append(f"default={field.default!r}")

            if decimal_field_repr(field.annotation) is not None:
                needs_decimal_helper = True
            annotation = _annotation_repr(field.annotation)
            if field_args:
                lines.append(f"    {field_name}: {annotation} = Field({', '.join(field_args)})")
            else:
                lines.append(f"    {field_name}: {annotation}")

        class_blocks.append("\n".join(lines))

    header = (
        '"""Output schema for this use case.\n\n'
        "Generated by GAIK SchemaGenerator via the Solution Configuration Wizard.\n"
        "Reviewed and approved by the user during Phase 5 (Schema Design).\n"
        'Do not hand-edit -- re-run generate_schema.py if the requirements change.\n"""\n\n'
        "from decimal import Decimal\n"
        "from typing import List, Literal, Optional, Union\n\n"
        "from pydantic import BaseModel, Field, ConfigDict\n\n"
    )
    if needs_decimal_helper:
        header += DECIMAL_PERSISTED_HELPER_SOURCE.strip() + "\n\n\n"
    return header + "\n\n".join(class_blocks) + "\n"

def _requirements_to_json(
    schema_class: type,
    requirements: ExtractionRequirements | CompositeExtractionRequirements,
) -> dict:
    """Produce the payload that load_schema() expects."""
    return {
        "model_name": schema_class.__name__,
        "requirements_type": (
            "parent_with_nested_list"
            if isinstance(requirements, CompositeExtractionRequirements)
            else "extraction"
        ),
        "requirements": requirements.model_dump(),
    }

def _requirements_field_summary(
    requirements: ExtractionRequirements | CompositeExtractionRequirements,
) -> list[str] | dict[str, list[str]]:
    """Return printable field names for flat and repeated-record schemas."""
    if isinstance(requirements, CompositeExtractionRequirements):
        return {
            "parent": [field.field_name for field in requirements.parent_requirements.fields],
            **{
                child.container_name: [field.field_name for field in child.requirements.fields]
                for child in requirements.children
            },
        }
    return [field.field_name for field in requirements.fields]

def main() -> int:
    parser = argparse.ArgumentParser(
        description="Generate extraction schema via GAIK SchemaGenerator."
    )
    parser.add_argument(
        "--requirements",
        required=True,
        help="Path to extraction_requirements.md (user requirements text)",
    )
    parser.add_argument(
        "--schema-name",
        default=None,
        help="Expected class name (informational; SchemaGenerator decides the actual name)",
    )
    parser.add_argument(
        "--output-dir",
        required=True,
        help="PoC root directory -- schema files written to <output-dir>/schemas/",
    )
    parser.add_argument(
        "--provider",
        choices=[provider.value for provider in Provider] if _GAIK_AVAILABLE else None,
        default=None,
        help="Shared LLM provider; overrides --use-azure/--no-azure. Credentials come from env.",
    )
    parser.add_argument(
        "--use-azure",
        dest="use_azure",
        action="store_true",
        default=True,
        help="Use Azure OpenAI (default when --provider is omitted)",
    )
    parser.add_argument(
        "--no-azure",
        dest="use_azure",
        action="store_false",
        help="Use OpenAI directly when --provider is omitted",
    )
    parser.add_argument(
        "--model",
        default=None,
        help="Override the model used for schema generation",
    )
    parser.add_argument(
        "--base-url",
        default=None,
        help="Override the API base URL for compatible endpoints or LiteLLM",
    )
    args = parser.parse_args()

    if not _GAIK_AVAILABLE:
        print(
            f"ERROR: GAIK toolkit not available: {_IMPORT_ERR}\n"
            "Install with: pip install gaik[extract]",
            file=sys.stderr,
        )
        return 1

    req_path = Path(args.requirements)
    if not req_path.exists():
        print(f"ERROR: Requirements file not found: {req_path}", file=sys.stderr)
        return 1

    user_requirements = req_path.read_text(encoding="utf-8")
    provider = args.provider or ("azure" if args.use_azure else "openai")
    overrides = {}
    if args.model is not None:
        overrides["model"] = args.model
    if args.base_url is not None:
        overrides["base_url"] = args.base_url
    try:
        # Overrides must reach the factory before it validates required fields
        # (e.g. an OpenAI-compatible server's model and endpoint).
        config = get_llm_config(provider, **overrides)
    except ValueError as exc:
        print(f"ERROR: {exc}", file=sys.stderr)
        return 1

    output_dir = Path(args.output_dir).expanduser().resolve()
    schemas_dir = output_dir / "schemas"
    schemas_dir.mkdir(parents=True, exist_ok=True)

    print(f"Generating schema from: {req_path}")
    print(f"Output directory:       {schemas_dir}")
    print(f"Provider:               {provider}")
    print(f"Model:                  {config['model']}")
    print()

    # -- Call SchemaGenerator --
    generator = SchemaGenerator(config=config)
    schema_class = generator.generate_schema(user_requirements=user_requirements)
    requirements: ExtractionRequirements | CompositeExtractionRequirements = (
        generator.item_requirements
    )

    # -- Write output_schema.py --
    py_content = _schema_to_py(schema_class, args.schema_name or schema_class.__name__)
    py_path = schemas_dir / "output_schema.py"
    py_path.write_text(py_content, encoding="utf-8")
    print(f"\nSchema written to:       {py_path}")

    # -- Write output_schema_requirements.json (load_schema() contract) --
    req_payload = _requirements_to_json(schema_class, requirements)
    req_path_out = schemas_dir / "output_schema_requirements.json"
    req_path_out.write_text(json.dumps(req_payload, indent=2), encoding="utf-8")
    print(f"Requirements written to: {req_path_out}")

    # -- Export JSON Schema (best-effort, for documentation) --
    try:
        json_schema = schema_class.model_json_schema()
        json_path = schemas_dir / "output_schema.json"
        json_path.write_text(json.dumps(json_schema, indent=2), encoding="utf-8")
        print(f"JSON Schema written to:  {json_path}")
    except Exception:
        pass

    # -- Save requirements hash so run_poc.py knows the schema is current --
    # If extraction_requirements.md is later edited, the hash mismatch will
    # trigger regeneration on the next run_poc.py invocation.
    import hashlib

    req_hash = hashlib.sha256(req_path.read_bytes()).hexdigest()
    # All schema files are always named output_schema.* regardless of the class
    # name. The hash file is therefore always output_schema.hash so that every
    # PoC template's _load_schema_if_fresh() finds it with schema_name="output_schema".
    hash_path = schemas_dir / "output_schema.hash"
    hash_path.write_text(req_hash)
    print(f"Requirements hash saved: {hash_path}")

    print(
        f"\nGenerated class name: {schema_class.__name__}"
        f"\nFields: {_requirements_field_summary(requirements)}"
        f"\n\nPresent {py_path} to the user for review."
        "\nIf the user requests changes, edit output_schema.py and"
        "\noutput_schema_requirements.json directly -- do NOT re-run this script."
    )
    return 0

if __name__ == "__main__":
    sys.exit(main())
```

## File: implementation_layer/solution_wizard/scripts/promote_template.py
size: 8944
sha: 393b8d0a0181b83461312ca8a8d24aba80bd0414
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/scripts/promote_template.py

```text
#!/usr/bin/env python3
"""Promote a validated, agent-generalised PoC into the reusable template library.

"Save" is a generalize-then-save operation, NEVER a copy:
  1. The agent rewrites the validated poc/run_poc.py into a TEMPLATE -- every
     use-case-specific literal replaced with the matching ${variable}.
  2. This script VALIDATES that candidate template and, only if all checks pass,
     saves it to templates/poc/<pattern_key>/run_poc.py.tmpl plus a manifest.

Promotion checks (all must pass):
  - genericity: no blueprint-specific token leaks into the template body outside ${...}
  - fills cleanly: safe_substitute leaves no unfilled ${...} for this blueprint
  - parses: the filled template is valid Python (ast.parse)
  - imports resolve: every `from gaik...import` matches a registry import_path
  - provider stages: model stages use get_stage_config (the scaffolder refuses others)
  - no duplicate: the pattern_key directory does not already exist

Usage:
    python scripts/promote_template.py \
        --blueprint ~/my-use-case/use_case.blueprint.json \
        --candidate ~/my-use-case/poc/run_poc.py.tmpl \
        [--status provisional|confirmed] [--force]

The --candidate file is the agent-generalised template (with ${variables}),
NOT the concrete run_poc.py.
"""

from __future__ import annotations

import argparse
import ast
import importlib.util
import json
import re
import string
import sys
from datetime import datetime, timezone
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from solution_wizard.blueprint import Blueprint
from solution_wizard.registry import get_registry
from solution_wizard.scaffolder import _build_variables, _derive_pattern_key, _determine_pattern

TEMPLATES_DIR = Path(__file__).parent.parent / "templates" / "poc"

def _blueprint_specific_tokens(blueprint: Blueprint) -> list:
    """Tokens that must NOT appear literally in a generic template body."""
    tokens = set()
    tokens.add(blueprint.use_case.id)
    tos = blueprint.target_output_spec if isinstance(blueprint.target_output_spec, dict) else {}
    if tos.get("schema_name"):
        tokens.add(tos["schema_name"])
    for f in tos.get("fields", []):
        if len(f) >= 4:  # skip ultra-short field names that cause false positives
            tokens.add(f)
    # Concrete model names from the blueprint
    models = blueprint.models or {}
    for key in ("transcription_model", "extraction_model"):
        v = models.get(key)
        if v:
            tokens.add(v)
    # Language code -- a hardcoded "fi" would break any non-Finnish use case
    lang = (
        blueprint.technical_spec.get("language", "")
        if isinstance(blueprint.technical_spec, dict)
        else getattr(blueprint.technical_spec, "language", "")
    )
    if lang and len(lang) >= 2:
        tokens.add(lang)
    return [t for t in tokens if t]

def _strip_placeholders(text: str) -> str:
    """Remove ${...} placeholders so the genericity scan only sees the literal body."""
    return re.sub(r"\$\{[^}]*\}", "", text)

def _check_genericity(candidate: str, blueprint: Blueprint) -> list:
    body = _strip_placeholders(candidate)
    leaked = []
    for tok in _blueprint_specific_tokens(blueprint):
        if re.search(r"\b" + re.escape(tok) + r"\b", body):
            leaked.append(tok)
    return leaked

def _check_fills_cleanly(candidate: str, variables: dict) -> list:
    filled = string.Template(candidate).safe_substitute(variables)
    return re.findall(r"\$\{[^}]*\}", filled)

def _check_parses(candidate: str, variables: dict) -> str | None:
    filled = string.Template(candidate).safe_substitute(variables)
    try:
        ast.parse(filled)
        return None
    except SyntaxError as exc:
        return str(exc)

def _check_provider_stages(candidate: str) -> bool:
    """Mirror scaffolder._determine_pattern, which refuses templates without it."""
    return "get_stage_config(" in candidate

def _check_imports(candidate: str) -> list:
    """Return gaik imports that do not resolve to an installed module."""
    unresolved = []
    for m in re.finditer(r"from\s+(gaik[\w\.]*)\s+import", candidate):
        modpath = m.group(1)
        try:
            if importlib.util.find_spec(modpath) is None:
                unresolved.append(modpath)
        except (ImportError, ModuleNotFoundError, ValueError):
            unresolved.append(modpath)
    return unresolved

def main() -> int:
    parser = argparse.ArgumentParser(description="Promote a PoC to the template library.")
    parser.add_argument("--blueprint", required=True, help="Path to the validated blueprint")
    parser.add_argument("--candidate", required=True, help="Path to the agent-generalised template (.tmpl with ${vars})")
    parser.add_argument("--status", default="provisional", choices=["provisional", "confirmed"])
    parser.add_argument("--force", action="store_true", help="Overwrite an existing template (skip duplicate check)")
    parser.add_argument("--skip-import-check", action="store_true",
                        help="Skip gaik import resolution check (use when gaik extras are not installed)")
    args = parser.parse_args()

    bp_path = Path(args.blueprint)
    cand_path = Path(args.candidate)
    for p, label in [(bp_path, "blueprint"), (cand_path, "candidate template")]:
        if not p.exists():
            print(f"ERROR: {label} not found: {p}", file=sys.stderr)
            return 1

    blueprint = Blueprint.from_file(bp_path)
    candidate = cand_path.read_text(encoding="utf-8")
    variables = _build_variables(blueprint, _determine_pattern(blueprint))

    pattern_key = _derive_pattern_key(blueprint)
    if pattern_key == "_generic":
        print("ERROR: cannot derive a stable pattern key (no automated steps).", file=sys.stderr)
        return 1

    print(f"Pattern key: {pattern_key}")
    failures = []

    # 1. Duplicate check
    target_dir = TEMPLATES_DIR / pattern_key
    if target_dir.exists() and not args.force:
        print(f"ERROR: template already exists for this pipeline shape: {target_dir}")
        print("This pipeline is already in the library (it would have been matched as a fixed pattern).")
        return 1

    # 2. Genericity scan
    leaked = _check_genericity(candidate, blueprint)
    if leaked:
        failures.append(
            f"genericity: use-case-specific tokens leaked into the template body "
            f"(must be ${{variables}}): {leaked}"
        )

    # 3. Fills cleanly
    unfilled = _check_fills_cleanly(candidate, variables)
    if unfilled:
        failures.append(f"fills_cleanly: unfilled placeholders for this blueprint: {sorted(set(unfilled))}")

    # 4. Parses
    parse_err = _check_parses(candidate, variables)
    if parse_err:
        failures.append(f"parses: filled template is not valid Python: {parse_err}")

    # 5. Provider stages
    if not _check_provider_stages(candidate):
        failures.append(
            "provider_stages: build each model stage with "
            'provider_config.get_stage_config(config, "<stage>"), not use_azure or a fixed model'
        )

    # 6. Imports resolve (optional -- needs gaik installed)
    if not args.skip_import_check:
        unresolved = _check_imports(candidate)
        if unresolved:
            failures.append(f"imports: unresolved gaik imports: {unresolved}")

    if failures:
        print("\nPROMOTION REJECTED:")
        for f in failures:
            print(f"  - {f}")
        print("\nFix the candidate template (or keep this PoC as a one-off) and retry.")
        return 1

    # All checks passed -- save template + manifest
    target_dir.mkdir(parents=True, exist_ok=True)
    (target_dir / "run_poc.py.tmpl").write_text(candidate, encoding="utf-8")

    components = [
        s.component for s in blueprint.workflow.steps
        if s.component and s.component not in ("LLMJudge", "custom", "human_review")
    ]
    manifest = {
        "pattern_key": pattern_key,
        "components": components,
        "topology_signature": [
            f"{s.component}({sorted(s.inputs)}->{sorted(s.outputs)})"
            for s in blueprint.workflow.steps
            if s.component and s.component not in ("LLMJudge", "custom", "human_review")
        ],
        "variables_used": sorted(set(re.findall(r"\$\{(\w+)\}", candidate))),
        "validated_by": [blueprint.use_case.id],
        "status": args.status,
        "created_at": datetime.now(timezone.utc).isoformat(),
    }
    (target_dir / "template.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")

    print(f"\nPROMOTED. Template saved to: {target_dir / 'run_poc.py.tmpl'}")
    print(f"Manifest: {target_dir / 'template.json'}  (status={args.status})")
    print("Next time a blueprint with this pipeline shape is scaffolded, this template is reused automatically.")
    return 0

if __name__ == "__main__":
    sys.exit(main())
```

## File: implementation_layer/solution_wizard/scripts/run_wizard.py
size: 5235
sha: 6cd93f36d4b3dd34ad362c2d1f896fc2a9c72798
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/scripts/run_wizard.py

```text
#!/usr/bin/env python3
"""GAIK Solution Configuration Wizard -- CLI entry point.

In normal use the wizard runs conversationally inside Claude Code or
Claude Desktop (via SKILL.md). This script is the standalone CLI
equivalent: it validates a given blueprint and generates the Mermaid
diagram, placing outputs in the user-specified directory.

Usage:
    # Interactive: prompts for output directory
    python run_wizard.py

    # Non-interactive: validate + generate from an existing blueprint
    python run_wizard.py --blueprint path/to/blueprint.json --output-dir ~/my-use-case

    # Re-export JSON Schema from Pydantic models
    python run_wizard.py --export-schema
"""

import argparse
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from solution_wizard.blueprint import Blueprint
from solution_wizard.bpmn_generator import generate_bpmn
from solution_wizard.registry import get_registry
from solution_wizard.validator import validate
from solution_wizard.visualizer import write_mermaid

SCHEMAS_DIR = Path(__file__).parent.parent / "schemas"

def _prompt_output_dir() -> Path:
    print("\nGAIK Solution Configuration Wizard -- V1")
    print("=" * 50)
    while True:
        raw = input(
            "\nWhere would you like to save the generated files?\n"
            "(Enter a folder path, e.g. ~/projects/my-use-case): "
        ).strip()
        if not raw:
            print("Output directory is required.")
            continue
        output_dir = Path(raw).expanduser().resolve()
        output_dir.mkdir(parents=True, exist_ok=True)
        print(f"Output directory: {output_dir}")
        return output_dir

def _validate_and_generate(blueprint_path: Path, output_dir: Path) -> int:
    try:
        blueprint = Blueprint.from_file(blueprint_path)
    except Exception as exc:
        print(f"ERROR: Failed to load blueprint: {exc}", file=sys.stderr)
        return 1

    result = validate(blueprint)
    print(result.summary())

    if not result.ok:
        print("\nBlueprint validation failed. Fix errors before generating outputs.")
        return 1

    # Generate both visual views. The BPMN is the visual blueprint, derived
    # from the JSON and linked by visualizations.bpmn_mapping (generate_bpmn
    # writes the map into the blueprint), so we render BEFORE saving the JSON
    # so the mapping is persisted alongside the blueprint.
    output_dir.mkdir(parents=True, exist_ok=True)
    mmd_path = output_dir / "workflow.mmd"
    write_mermaid(blueprint, output_dir)
    bpmn_path = output_dir / "workflow.bpmn"
    bpmn_path.write_text(generate_bpmn(blueprint), encoding="utf-8")

    # Save blueprint to output dir (now including visualizations.bpmn_mapping)
    out_blueprint = output_dir / "use_case.blueprint.json"
    blueprint.package.output_dir = str(output_dir)
    blueprint.to_file(out_blueprint)
    print(f"\nBlueprint saved to:  {out_blueprint}")
    print(f"Mermaid diagram:     {mmd_path}")
    print(f"BPMN diagram:        {bpmn_path}")

    print("\nNext steps:")
    print("  - Review the Mermaid diagram in workflow.mmd (quick flow view)")
    print("  - Open the BPMN visual blueprint workflow.bpmn in bpmn-js / Camunda / draw.io")
    print(f"  - Scaffold a runnable PoC: python scripts/scaffold_poc.py --blueprint {out_blueprint}")
    return 0

def main() -> int:
    parser = argparse.ArgumentParser(
        description="GAIK Solution Configuration Wizard CLI"
    )
    parser.add_argument(
        "--blueprint", help="Path to an existing use_case.blueprint.json to validate + render"
    )
    parser.add_argument(
        "--output-dir", help="Directory to save outputs (skips interactive prompt)"
    )
    parser.add_argument(
        "--export-schema",
        action="store_true",
        help="Re-export JSON Schema from Pydantic models to schemas/",
    )
    parser.add_argument(
        "--show-registry",
        action="store_true",
        help="Print a compact summary of all registry components (for component selection)",
    )
    args = parser.parse_args()

    if args.export_schema:
        schema_path = SCHEMAS_DIR / "use_case_blueprint.schema.json"
        Blueprint.export_json_schema(schema_path)
        print(f"JSON Schema exported to: {schema_path}")
        return 0

    if args.show_registry:
        print(get_registry().as_llm_context())
        return 0

    if args.blueprint:
        blueprint_path = Path(args.blueprint)
        if args.output_dir:
            output_dir = Path(args.output_dir).expanduser().resolve()
            output_dir.mkdir(parents=True, exist_ok=True)
        else:
            output_dir = blueprint_path.parent
        return _validate_and_generate(blueprint_path, output_dir)

    # Interactive mode
    output_dir = _prompt_output_dir()
    print(
        "\nTo configure a use case, describe it to the wizard in Claude Code or "
        "Claude Desktop using the /solution-wizard skill.\n"
        "Then run this script to validate and generate outputs:\n\n"
        f"  python run_wizard.py --blueprint {output_dir}/use_case.blueprint.json "
        f"--output-dir {output_dir}"
    )
    return 0

if __name__ == "__main__":
    sys.exit(main())
```

## File: implementation_layer/solution_wizard/scripts/scaffold_poc.py
size: 5585
sha: 29c949a88f99d2f9b3e78cb3bc9bb40c68b964ee
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/scripts/scaffold_poc.py

```text
#!/usr/bin/env python3
"""Scaffold a minimal runnable PoC from a validated blueprint.

Validates the blueprint first, then generates the poc/ folder in the
blueprint's output directory (or a directory you specify).

Usage:
    python scaffold_poc.py --blueprint ~/my-use-case/use_case.blueprint.json
    python scaffold_poc.py --blueprint ... --output-dir ~/my-use-case
    python scaffold_poc.py --blueprint ... --synthetic        # generate sample input
    python scaffold_poc.py --blueprint ... --skip-validation  # scaffold despite errors
"""

import argparse
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from solution_wizard.blueprint import Blueprint
from solution_wizard.scaffolder import scaffold_poc, validate_generated_python
from solution_wizard.validator import validate

# The root of the GAIK repo -- outputs must never go inside here.
_WIZARD_ROOT = Path(__file__).parent.parent.resolve()
_REPO_ROOT = _WIZARD_ROOT.parent.parent.resolve()

def _check_output_dir(output_dir: Path) -> None:
    """Refuse to write inside the GAIK repo (implementation_layer/ or above).
    Exception: .wizard_workspaces paths are managed by the demo app server."""
    resolved = output_dir.resolve()
    # Allow server-managed wizard workspace directories
    if ".wizard_workspaces" in resolved.parts:
        return
    try:
        resolved.relative_to(_REPO_ROOT)
        print(
            f"ERROR: output-dir '{output_dir}' is inside the GAIK repository "
            f"({_REPO_ROOT}).\n"
            "All wizard outputs must go outside the repo. "
            "Choose a directory under your home folder or project root.",
            file=sys.stderr,
        )
        sys.exit(1)
    except ValueError:
        pass  # Not inside repo -- OK

def main() -> int:
    parser = argparse.ArgumentParser(description="Scaffold a PoC from a GAIK blueprint.")
    parser.add_argument("--blueprint", required=True, help="Path to use_case.blueprint.json")
    parser.add_argument(
        "--output-dir",
        help="Root directory for PoC output (defaults to blueprint.package.output_dir)",
    )
    parser.add_argument(
        "--synthetic",
        action="store_true",
        help="Generate synthetic input data in sample_input/ where possible",
    )
    parser.add_argument(
        "--skip-validation",
        action="store_true",
        help="Skip blueprint validation (scaffold anyway)",
    )
    args = parser.parse_args()

    blueprint_path = Path(args.blueprint)
    if not blueprint_path.exists():
        print(f"ERROR: Blueprint not found: {blueprint_path}", file=sys.stderr)
        return 1

    try:
        blueprint = Blueprint.from_file(blueprint_path)
    except Exception as exc:
        print(f"ERROR: Failed to load blueprint: {exc}", file=sys.stderr)
        return 1

    if not args.skip_validation:
        result = validate(blueprint)
        if not result.ok:
            print(result.summary(), file=sys.stderr)
            print(
                "\nBlueprint has validation errors. Fix them or use --skip-validation.",
                file=sys.stderr,
            )
            return 1
        for w in result.warnings:
            print(f"WARNING {w}")

    # Determine output directory
    if args.output_dir:
        output_dir = Path(args.output_dir).expanduser().resolve()
    elif blueprint.package.output_dir:
        output_dir = Path(blueprint.package.output_dir).expanduser().resolve()
    else:
        output_dir = blueprint_path.parent.resolve()

    _check_output_dir(output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)

    print(f"Scaffolding PoC into: {output_dir / 'poc'}")
    result_info = scaffold_poc(blueprint, output_dir, synthetic=args.synthetic)

    poc_dir = result_info["poc_dir"]
    pattern = result_info["pattern"]
    template_wired = result_info["template_wired"]
    pattern_key = result_info["pattern_key"]
    template_save_path = result_info["template_save_path"]

    print(f"\nPattern detected: {pattern}")
    print(f"Pipeline pattern key: {pattern_key}")
    print(f"Generated {len(result_info['files'])} file(s) in {poc_dir}")

    # Validate generated Python
    parse_err = validate_generated_python(poc_dir)
    if parse_err:
        print(f"WARNING: run_poc.py has a syntax issue: {parse_err}", file=sys.stderr)
    else:
        print("run_poc.py: syntax OK")

    if not template_wired:
        print(
            "\nNOTE: Custom/hybrid pipeline detected. run_poc.py is a per-step skeleton"
            "\nwith reference call patterns -- the wizard agent fills the wiring."
            f"\n\nIf this pipeline is validated and you choose to promote it to a reusable"
            f"\ntemplate, it would be saved to:\n  {template_save_path}"
        )

    # Print handoff message
    print(f"""
Your PoC has been generated at: {poc_dir}

To run it:
  1. Install dependencies:
         pip install -r {poc_dir / 'requirements.txt'}
  2. Set up your environment:
         cp {poc_dir / '.env.example'} {poc_dir / '.env'}
         -- then open .env and fill in your API key
  3. Add a sample input file:
         See {poc_dir / 'README.md'} for the expected input format
  4. Run the pipeline:
         python {poc_dir / 'run_poc.py'}
  5. Inspect the output:
         Check {poc_dir / 'output'} for the generated result

When you are ready, paste the output here or describe what you see.
The wizard will help you interpret the result and refine if needed (Gate 3).
""")

    return 0

if __name__ == "__main__":
    sys.exit(main())
```

## File: implementation_layer/solution_wizard/scripts/validate_blueprint.py
size: 1121
sha: ffcd64f730c70c218a0be6a432d1486badcfafe2
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/scripts/validate_blueprint.py

```text
#!/usr/bin/env python3
"""Validate a blueprint JSON against all V1 rules.

Usage:
    python validate_blueprint.py --blueprint path/to/use_case.blueprint.json
"""

import argparse
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

from solution_wizard.blueprint import Blueprint
from solution_wizard.validator import validate

def main() -> int:
    parser = argparse.ArgumentParser(description="Validate a GAIK use-case blueprint.")
    parser.add_argument("--blueprint", required=True, help="Path to use_case.blueprint.json")
    args = parser.parse_args()

    blueprint_path = Path(args.blueprint)
    if not blueprint_path.exists():
        print(f"ERROR: Blueprint file not found: {blueprint_path}", file=sys.stderr)
        return 1

    try:
        blueprint = Blueprint.from_file(blueprint_path)
    except Exception as exc:
        print(f"ERROR: Failed to load blueprint: {exc}", file=sys.stderr)
        return 1

    result = validate(blueprint)
    print(result.summary())
    return 0 if result.ok else 1

if __name__ == "__main__":
    sys.exit(main())
```

## File: implementation_layer/solution_wizard/CLAUDE.md
size: 3976
sha: b6246a05ea643c7484d113eef2b8fcb49d6a7264
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/CLAUDE.md

````text
# Solution Wizard — Claude Code Notes

## Schema generation constraints

Two constraints must be checked every time a GAIK extraction schema is generated or manually edited. Both caused runtime failures during a hospital admissions PoC (2026-06-06) and are now enforced in SKILL.md Phase 4.

### 1. ExtractionRequirements `field_type` enum

When editing `output_schema_requirements.json` directly, `field_type` must be one of:
`str` | `int` | `float` | `bool` | `list[str]` | `date` | `decimal` | `list[dict]`

`"dict"` is **not** in this enum — it causes `pydantic_core.ValidationError` at runtime when `ExtractionRequirements(**data)` is called. Use:
- `"str"` for a single nested object field
- `"list[dict]"` for an array of objects

### 2. Azure OpenAI structured output rejects bare `dict` types

When `provider: azure` (also the legacy `azure_openai` alias or Azure through LiteLLM), `output_schema.py` must never contain `dict | None` or `list[dict]` as field types. The API requires `additionalProperties: false` on every JSON object; bare Python `dict` does not satisfy this and returns HTTP 400.

Replace with a named Pydantic sub-model:

```python
class Medication(BaseModel):
    model_config = ConfigDict(extra='forbid')
    name: str | None = None
    dose: str | None = None
    frequency: str | None = None
```

Then use `list[Medication] | None` instead of `list[dict] | None`.

This applies to any field described as a nested object: medications, social history, address, line items, etc. If the GAIK SchemaGenerator emits `list[dict]` for such a field, replace it with a named sub-model before presenting the schema to the user.

**Where these rules are enforced in code:**
- `SKILL.md` Phase 4 — constraint checklist before schema approval (Step 4.6)
- `registries/gaik_component_registry.json` — Extractor `known_limitations`
- `src/solution_wizard/schema_designer.py` — `_TYPE_MAP` maps `dict`/`object` to `str`, not bare `dict`

## Provider configuration when wiring a PoC

Use the generated `provider_config.get_stage_config(config, stage)` helper for
`transcription`, `parser`, `extraction`, `embedding`, `answer`, and `judge`.
It resolves the selected provider through `get_llm_config`; do not reduce all
providers to the old `use_azure` flag or copy a credential-bearing config while
changing its provider. Reference-card constructs assume this helper is imported.

`gpt-6-luna` is the default OpenAI/Azure text-model suggestion, subject to actual
account access and Azure deployment naming. Native provider IDs include `azure`,
`anthropic_foundry` and `vertex`; `azure_openai` remains a legacy blueprint alias
that the scaffolder and `get_stage_config` map to `azure` (gaik's `get_llm_config`
rejects it). Aitta, generic OpenAI-compatible endpoints and the optional LiteLLM
backend use the same component config contract, with model-specific capabilities.
LiteLLM models carry their routing prefix; native model IDs do not.

Keep audio on native OpenAI/Azure (or an explicitly configured existing local
Whisper mode), and select embeddings separately from chat. Image input and
structured output must be supported by the actual selected model. For stage
overrides, use the constructor/config names in the reference cards; they differ
between modules. Secrets belong only in the runtime environment, never blueprints
or saved report configurations.

For the default GPT-6 setup omit temperature unless using supported disabled
reasoning. GPT-6 Astra does not support `reasoning_effort='none'`; Sol/Luna do.
Custom Azure deployment names need `model_family` for family normalization.
PostgresAgent/TabularAgent drop their `0.0` temperature default for GPT-6/GPT-5.6
automatically; pass `temperature=None` only for other reasoning deployments that
reject it (o-series, older gpt-5.x reasoning tiers). Follow SKILL.md Phase 6 and
the card's current options instead of the obsolete blanket restriction against every
model newer than GPT-5.4.
````

## File: implementation_layer/solution_wizard/docs/solution_wizard.md
size: 49923
sha: 02c88385aa3d9fadfbddcc90cd6f0406cbb99bca
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/docs/solution_wizard.md

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/docs/solution_wizard.md

## File: implementation_layer/solution_wizard/examples/document_extraction_blueprint.json
size: 4841
sha: 0469dd40d941380542ea347f6084efef6262b404
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/examples/document_extraction_blueprint.json

```text
{
  "blueprint_version": "1.0",
  "metadata": {
    "blueprint_id": "purchase_order_extraction_v1",
    "created_by": "solution_configuration_wizard",
    "created_at": "2026-06-04T09:00:00Z",
    "last_updated_at": "2026-06-04T09:00:00Z",
    "status": "validated"
  },
  "use_case": {
    "id": "purchase_order_extraction",
    "name": "Purchase Order Data Extraction",
    "description": "Extract structured line-item data from PDF purchase orders for ERP integration.",
    "domain": "procurement",
    "knowledge_processes": ["knowledge_capture"]
  },
  "business_spec": {
    "current_process": "Procurement staff manually re-enters PO data from PDF files into the ERP system.",
    "pain_points": [
      "Manual re-entry is slow and error-prone",
      "PO formats vary between suppliers",
      "High volume of POs creates a bottleneck"
    ],
    "intended_users": ["procurement_staff"],
    "reviewers": ["procurement_manager"],
    "expected_value": ["reduced manual effort", "faster PO processing", "fewer data entry errors"]
  },
  "technical_spec": {
    "input_types": ["pdf"],
    "output_types": ["structured_json"],
    "language": "en",
    "human_review_required": false,
    "integration_targets": ["erp_system"]
  },
  "target_output_spec": {
    "schema_name": "PurchaseOrder",
    "fields": ["po_number", "supplier_name", "order_date", "delivery_date", "line_items", "total_amount", "currency"],
    "required_fields": ["po_number", "supplier_name", "line_items", "total_amount"]
  },
  "assumptions": [
    {
      "id": "assumption_001",
      "text": "PO documents are primarily PDF files with text-based content, not scanned images.",
      "status": "unconfirmed",
      "impact": "component_selection",
      "recorded_by": "ComponentSelector",
      "recorded_at": "2026-06-04T09:00:00Z"
    }
  ],
  "artifacts": {
    "source_pdf": {
      "type": "pdf",
      "source": "user_upload",
      "optional": false
    },
    "purchase_order_json": {
      "type": "structured_json",
      "source": "generated",
      "optional": false,
      "final_output": true,
      "produced_by": "extract_po_fields",
      "schema_ref": "schemas/purchase_order_schema.py"
    }
  },
  "components": {
    "selected_modules": [
      {
        "id": "documents_to_structured_data",
        "name": "DocumentsToStructuredData",
        "reason": "Primary input is PDF; module handles parsing and structured extraction end-to-end."
      }
    ],
    "selected_building_blocks": ["Extractor"],
    "custom_components": []
  },
  "workflow": {
    "steps": [
      {
        "id": "upload_po",
        "name": "Upload purchase order PDF",
        "type": "user_task",
        "inputs": [],
        "outputs": ["source_pdf"],
        "parameters": {},
        "depends_on": []
      },
      {
        "id": "extract_po_fields",
        "name": "Parse and extract PO fields",
        "type": "automated_task",
        "component": "DocumentsToStructuredData",
        "inputs": ["source_pdf"],
        "outputs": ["purchase_order_json"],
        "parameters": {
          "schema_ref": "schemas/purchase_order_schema.py",
          "requirements_ref": "to_be_generated"
        },
        "depends_on": ["upload_po"]
      }
    ]
  },
  "models": {
    "provider": "azure_openai",
    "extraction_model": "gpt-5.4",
    "temperature": 0.0
  },
  "validation": {
    "human_review_required": false,
    "completeness_check": true,
    "hallucination_check": false
  },
  "evaluation": {
    "metrics": ["field_exact_match", "semantic_match", "completeness"],
    "eval_framework": "extraction_eval",
    "sample_data_dir": "data/samples",
    "ground_truth_dir": "evals/ground_truth"
  },
  "governance": {
    "data_handling": {
      "external_model_api_allowed": true,
      "local_processing_required": false,
      "stores_input_data": false,
      "stores_outputs": true,
      "retention_policy": "not_specified",
      "contains_personal_data": "no",
      "output_sensitivity": "medium",
      "audit_log_required": false
    }
  },
  "runtime": {
    "interface": "cli",
    "entrypoint": "src/purchase_order_extraction/main.py",
    "env_file": ".env.example"
  },
  "package": {
    "name": "purchase_order_extraction_use_case",
    "output_dir": "/path/provided/by/user",
    "include_tests": true,
    "include_evals": true,
    "include_docs": true
  },
  "visualizations": {},
  "traceability": [
    {"component": "DocumentsToStructuredData", "required_by": "input_type=pdf"},
    {"component": "Extractor", "required_by": "output_type=structured_json; field extraction required"}
  ],
  "change_log": [
    {
      "version": "1.0",
      "date": "2026-06-04",
      "change": "Initial blueprint generated by solution_configuration_wizard V1",
      "changed_by": "solution_configuration_wizard"
    }
  ]
}
```

## File: implementation_layer/solution_wizard/examples/incident_reporting_blueprint.json
size: 7803
sha: e991327b29e4c4c2764eaab789be0f7766279694
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/examples/incident_reporting_blueprint.json

```text
{
  "blueprint_version": "1.0",
  "metadata": {
    "blueprint_id": "incident_reporting_v1",
    "created_by": "solution_configuration_wizard",
    "created_at": "2026-06-04T09:00:00Z",
    "last_updated_at": "2026-06-04T09:00:00Z",
    "status": "validated"
  },
  "use_case": {
    "id": "incident_reporting",
    "name": "Incident and Safety Observation Reporting Assistant",
    "description": "Convert spoken incident observations into structured incident reports for review and submission.",
    "domain": "manufacturing_safety",
    "knowledge_processes": ["knowledge_capture"]
  },
  "business_spec": {
    "current_process": "Employees report incidents by accessing a computer and filling a web form.",
    "pain_points": [
      "Reporting may be delayed",
      "The form is not convenient in shop-floor or outdoor contexts",
      "Report quality varies between employees"
    ],
    "intended_users": ["employee_reporter"],
    "reviewers": ["safety_manager"],
    "expected_value": ["faster reporting", "more consistent documentation", "improved safety transparency"]
  },
  "technical_spec": {
    "input_types": ["audio"],
    "output_types": ["structured_json"],
    "language": "fi",
    "human_review_required": true,
    "integration_targets": ["incident_reporting_database_optional"]
  },
  "target_output_spec": {
    "schema_name": "IncidentReport",
    "fields": ["incident_date", "location", "description", "severity", "injured_persons", "actions_taken", "reporter_name"],
    "required_fields": ["incident_date", "location", "description", "severity"]
  },
  "assumptions": [
    {
      "id": "assumption_001",
      "text": "Audio recordings are in Finnish and typically 1-5 minutes in length.",
      "status": "unconfirmed",
      "impact": "component_selection",
      "recorded_by": "ComponentSelector",
      "recorded_at": "2026-06-04T09:00:00Z"
    },
    {
      "id": "assumption_002",
      "text": "Human review is required before the report is submitted to the safety database.",
      "status": "confirmed",
      "impact": "workflow_design",
      "recorded_by": "SpecificationBuilder",
      "recorded_at": "2026-06-04T09:01:00Z"
    }
  ],
  "artifacts": {
    "incident_audio": {
      "type": "audio",
      "source": "user_upload",
      "optional": false
    },
    "incident_photo": {
      "type": "image",
      "source": "user_upload",
      "optional": true
    },
    "raw_transcript": {
      "type": "transcript",
      "source": "generated",
      "optional": false,
      "final_output": false,
      "produced_by": "transcribe_audio"
    },
    "enhanced_transcript": {
      "type": "enhanced_transcript",
      "source": "generated",
      "optional": false,
      "final_output": false,
      "produced_by": "enhance_transcript"
    },
    "incident_report_json": {
      "type": "structured_json",
      "source": "generated",
      "optional": false,
      "final_output": true,
      "produced_by": "extract_report_fields",
      "schema_ref": "schemas/incident_report_schema.py"
    },
    "validation_report": {
      "type": "validation_report",
      "source": "generated",
      "optional": false,
      "final_output": false,
      "produced_by": "validate_report"
    },
    "approved_report": {
      "type": "structured_json",
      "source": "generated",
      "optional": false,
      "final_output": true,
      "produced_by": "human_review"
    }
  },
  "components": {
    "selected_modules": [
      {
        "id": "audio_to_structured_data",
        "name": "AudioToStructuredData",
        "reason": "Primary input is audio; module encapsulates full transcription + extraction pipeline."
      }
    ],
    "selected_building_blocks": ["Transcriber", "TranscriptEnhancer", "Extractor", "LLMJudge"],
    "custom_components": []
  },
  "workflow": {
    "steps": [
      {
        "id": "record_observation",
        "name": "Record voice description and optional photo",
        "type": "user_task",
        "inputs": [],
        "outputs": ["incident_audio", "incident_photo"],
        "parameters": {},
        "depends_on": []
      },
      {
        "id": "transcribe_audio",
        "name": "Transcribe audio recording",
        "type": "automated_task",
        "component": "Transcriber",
        "inputs": ["incident_audio"],
        "outputs": ["raw_transcript"],
        "parameters": {"language": "fi"},
        "depends_on": ["record_observation"]
      },
      {
        "id": "enhance_transcript",
        "name": "Enhance Finnish transcript",
        "type": "automated_task",
        "component": "TranscriptEnhancer",
        "inputs": ["raw_transcript"],
        "outputs": ["enhanced_transcript"],
        "parameters": {},
        "depends_on": ["transcribe_audio"]
      },
      {
        "id": "extract_report_fields",
        "name": "Extract incident report fields",
        "type": "automated_task",
        "component": "Extractor",
        "inputs": ["enhanced_transcript"],
        "outputs": ["incident_report_json"],
        "parameters": {
          "schema_ref": "schemas/incident_report_schema.py",
          "requirements_ref": "to_be_generated"
        },
        "depends_on": ["enhance_transcript"]
      },
      {
        "id": "validate_report",
        "name": "Validate generated report",
        "type": "automated_task",
        "component": "LLMJudge",
        "inputs": ["enhanced_transcript", "incident_report_json"],
        "outputs": ["validation_report"],
        "parameters": {"rubric_ref": "to_be_generated"},
        "depends_on": ["extract_report_fields"]
      },
      {
        "id": "human_review",
        "name": "Safety manager reviews and approves report",
        "type": "human_review",
        "inputs": ["incident_report_json", "validation_report"],
        "outputs": ["approved_report"],
        "parameters": {},
        "depends_on": ["validate_report"]
      }
    ]
  },
  "models": {
    "provider": "azure_openai",
    "transcription_model": "gpt-4o-transcribe",
    "extraction_model": "gpt-5.4",
    "temperature": 0.0
  },
  "validation": {
    "human_review_required": true,
    "completeness_check": true,
    "hallucination_check": true
  },
  "evaluation": {
    "metrics": ["field_exact_match", "semantic_match", "completeness", "hallucination_rate"],
    "eval_framework": "extraction_eval",
    "sample_data_dir": "data/samples",
    "ground_truth_dir": "evals/ground_truth"
  },
  "governance": {
    "data_handling": {
      "external_model_api_allowed": true,
      "local_processing_required": false,
      "stores_input_data": false,
      "stores_outputs": true,
      "retention_policy": "not_specified",
      "contains_personal_data": "yes",
      "output_sensitivity": "high",
      "audit_log_required": true
    }
  },
  "runtime": {
    "interface": "cli",
    "entrypoint": "src/incident_reporting/main.py",
    "env_file": ".env.example"
  },
  "package": {
    "name": "incident_reporting_use_case",
    "output_dir": "/path/provided/by/user",
    "include_tests": true,
    "include_evals": true,
    "include_docs": true
  },
  "visualizations": {},
  "traceability": [
    {"component": "Transcriber", "required_by": "technical_spec.input_types contains audio"},
    {"component": "TranscriptEnhancer", "required_by": "technical_spec.language=fi; ASR error correction needed"},
    {"component": "Extractor", "required_by": "output_type=structured_json; field extraction required"},
    {"component": "LLMJudge", "required_by": "validation.hallucination_check=true; human_review=yes"}
  ],
  "change_log": [
    {
      "version": "1.0",
      "date": "2026-06-04",
      "change": "Initial blueprint generated by solution_configuration_wizard V1",
      "changed_by": "solution_configuration_wizard"
    }
  ]
}
```

## File: implementation_layer/solution_wizard/examples/rag_workflow_blueprint.json
size: 5180
sha: ba202a6ef174bb8ef51edc8556db115802da9ea6
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/examples/rag_workflow_blueprint.json

```text
{
  "blueprint_version": "1.0",
  "metadata": {
    "blueprint_id": "internal_knowledge_qa_v1",
    "created_by": "solution_configuration_wizard",
    "created_at": "2026-06-04T09:00:00Z",
    "last_updated_at": "2026-06-04T09:00:00Z",
    "status": "validated"
  },
  "use_case": {
    "id": "internal_knowledge_qa",
    "name": "Internal Knowledge Base Q&A",
    "description": "Enable employees to ask natural-language questions over a collection of internal documents and receive cited answers.",
    "domain": "knowledge_management",
    "knowledge_processes": ["knowledge_access"]
  },
  "business_spec": {
    "current_process": "Employees search manually through shared drives and document repositories to find relevant information.",
    "pain_points": [
      "Finding the right document is time-consuming",
      "Answers require reading multiple documents",
      "Knowledge is siloed across different folders and formats"
    ],
    "intended_users": ["employee"],
    "reviewers": [],
    "expected_value": ["faster information access", "reduced time searching", "consistent answers with citations"]
  },
  "technical_spec": {
    "input_types": ["document_collection"],
    "output_types": ["answer"],
    "language": "en",
    "human_review_required": false,
    "integration_targets": []
  },
  "target_output_spec": {},
  "assumptions": [
    {
      "id": "assumption_001",
      "text": "Documents are primarily PDF and DOCX files in English.",
      "status": "unconfirmed",
      "impact": "component_selection",
      "recorded_by": "ComponentSelector",
      "recorded_at": "2026-06-04T09:00:00Z"
    },
    {
      "id": "assumption_002",
      "text": "PostgreSQL with pgvector extension is available for the vector store.",
      "status": "unconfirmed",
      "impact": "component_selection",
      "recorded_by": "ComponentSelector",
      "recorded_at": "2026-06-04T09:00:00Z"
    }
  ],
  "artifacts": {
    "document_collection": {
      "type": "document_collection",
      "source": "user_upload",
      "optional": false
    },
    "user_query": {
      "type": "text",
      "source": "user_upload",
      "optional": false
    },
    "answer_with_citations": {
      "type": "answer_with_citations",
      "source": "generated",
      "optional": false,
      "final_output": true,
      "produced_by": "run_rag"
    }
  },
  "components": {
    "selected_modules": [
      {
        "id": "rag_workflow",
        "name": "RAGWorkflow",
        "reason": "Use case is document Q&A with citations -- RAGWorkflow covers the full indexing and retrieval pipeline."
      }
    ],
    "selected_building_blocks": [],
    "custom_components": []
  },
  "workflow": {
    "steps": [
      {
        "id": "upload_documents",
        "name": "Upload document collection",
        "type": "user_task",
        "inputs": [],
        "outputs": ["document_collection"],
        "parameters": {},
        "depends_on": []
      },
      {
        "id": "ask_question",
        "name": "User submits a question",
        "type": "user_task",
        "inputs": [],
        "outputs": ["user_query"],
        "parameters": {},
        "depends_on": ["upload_documents"]
      },
      {
        "id": "run_rag",
        "name": "Index documents, retrieve context, generate cited answer",
        "type": "automated_task",
        "component": "RAGWorkflow",
        "inputs": ["document_collection", "user_query"],
        "outputs": ["answer_with_citations"],
        "parameters": {"collection_name": "internal_knowledge"},
        "depends_on": ["ask_question"]
      }
    ]
  },
  "models": {
    "provider": "azure_openai",
    "embedding_model": "text-embedding-3-large",
    "answer_model": "gpt-6-luna",
    "temperature": 0.0
  },
  "validation": {
    "human_review_required": false,
    "completeness_check": false,
    "hallucination_check": false
  },
  "evaluation": {
    "metrics": ["token_recall", "mrr", "rank_weighted_coverage"],
    "eval_framework": "RAG_eval",
    "sample_data_dir": "data/samples",
    "ground_truth_dir": "evals/ground_truth"
  },
  "governance": {
    "data_handling": {
      "external_model_api_allowed": true,
      "local_processing_required": false,
      "stores_input_data": true,
      "stores_outputs": false,
      "retention_policy": "not_specified",
      "contains_personal_data": "unknown",
      "output_sensitivity": "medium",
      "audit_log_required": false
    }
  },
  "runtime": {
    "interface": "cli",
    "entrypoint": "src/internal_knowledge_qa/main.py",
    "env_file": ".env.example"
  },
  "package": {
    "name": "internal_knowledge_qa_use_case",
    "output_dir": "/path/provided/by/user",
    "include_tests": true,
    "include_evals": true,
    "include_docs": true
  },
  "visualizations": {},
  "traceability": [
    {"component": "RAGWorkflow", "required_by": "input_type=document_collection, output_type=answer; includes its Chroma vector store"}
  ],
  "change_log": [
    {
      "version": "1.0",
      "date": "2026-06-04",
      "change": "Initial blueprint generated by solution_configuration_wizard V1",
      "changed_by": "solution_configuration_wizard"
    }
  ]
}
```

## File: implementation_layer/solution_wizard/gaik_validated_version.txt
size: 7
sha: 83ce05d72fbfaf780ead84b34d6dfbc7a18a3efb
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/gaik_validated_version.txt

```text
0.8.11
```

## File: implementation_layer/solution_wizard/images/solution_wizard_v2_workflow.png
size: 1478213
sha: 5b12cd15d547a4ca36c89bd3163854bff996e20e
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/images/solution_wizard_v2_workflow.png

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/images/solution_wizard_v2_workflow.png

## File: implementation_layer/solution_wizard/registries/component_reference_cards.json
size: 194942
sha: 5a71abf0a425c3ca5863a7abd7e2e0808659b9bf
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/registries/component_reference_cards.json

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/registries/component_reference_cards.json

## File: implementation_layer/solution_wizard/registries/gaik_component_registry.json
size: 126747
sha: 823b57d1124e21ce9acd41a9edd9491ae4206ab9
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/registries/gaik_component_registry.json

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/registries/gaik_component_registry.json

## File: implementation_layer/solution_wizard/run_wizard_interactive.py
size: 15078
sha: fd8e166a9f801cb8bd3dc35f65d6b03a75fc488c
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/run_wizard_interactive.py

```text
#!/usr/bin/env python3
"""Interactive CLI session for the GAIK Solution Configuration Wizard.

Starts a persistent conversation with the wizard. The wizard asks questions
at each phase and gate; you type answers and press Enter. The session continues
until the wizard completes (or you press Ctrl+C / Ctrl+D).

--- Prerequisites -----------------------------------------------------------

1. Install the wizard runner extras (once):

       pip install "gaik[wizard]"        # installs claude-agent-sdk + python-dotenv

   Or install them directly:

       pip install claude-agent-sdk python-dotenv

2. The `claude` Code CLI must be on PATH (the SDK spawns it as a subprocess):

       # Verify:
       claude --version

   Install if missing: https://docs.anthropic.com/en/claude-code/setup

--- Credentials (.env) ------------------------------------------------------

The script reads credentials from ../.env (one directory above this file, i.e.
implementation_layer/.env) by default.  Copy the template and fill in your values:

    cp .env.example ../.env      # then edit ../.env

Required variables:

    CLAUDE_CODE_USE_FOUNDRY=1
    ANTHROPIC_FOUNDRY_API_KEY=<your Azure Foundry API key>
    ANTHROPIC_FOUNDRY_RESOURCE=<your Foundry resource name, e.g. my-foundry-resource>

Recommended (model selection):

    ANTHROPIC_DEFAULT_SONNET_MODEL=claude-sonnet-5-5

Optional (timeouts / telemetry):

    API_TIMEOUT_MS=600000
    CLAUDE_CODE_MAX_RETRIES=2
    DISABLE_TELEMETRY=1

Run the preflight check to verify credentials before a full session:

    python run_wizard_interactive.py --preflight

--- Usage -------------------------------------------------------------------

    # Wizard asks for the output directory interactively:
    python run_wizard_interactive.py

    # Pre-supply the output directory (skips that question):
    python run_wizard_interactive.py --output-dir C:\\projects\\my-use-case

    # Use a different .env file:
    python run_wizard_interactive.py --env-file path/to/.env

    # Override the model:
    python run_wizard_interactive.py --model claude-sonnet-5-5

See also: run_wizard_sdk.py for a headless smoke-test (no user interaction).
"""

from __future__ import annotations

import argparse
import asyncio
import os
import sys
from pathlib import Path

from dotenv import load_dotenv

from claude_agent_sdk import (
    AssistantMessage,
    ClaudeAgentOptions,
    ClaudeSDKClient,
    ResultMessage,
    TextBlock,
    ToolResultBlock,
    ToolUseBlock,
)

WIZARD_DIR = Path(__file__).resolve().parent

REQUIRED_ENV_VARS = [
    "CLAUDE_CODE_USE_FOUNDRY",
    "ANTHROPIC_FOUNDRY_API_KEY",
    "ANTHROPIC_FOUNDRY_RESOURCE",
]
DEFAULT_MODEL = "claude-sonnet-5-5"

# ── ANSI colours (suppressed on non-TTY / Windows without ANSI support) ──────

def _supports_colour() -> bool:
    return sys.stdout.isatty() and os.name != "nt" or os.environ.get("FORCE_COLOR")

RESET = "\033[0m" if _supports_colour() else ""
BOLD = "\033[1m" if _supports_colour() else ""
DIM = "\033[2m" if _supports_colour() else ""
CYAN = "\033[36m" if _supports_colour() else ""
YELLOW = "\033[33m" if _supports_colour() else ""
GREEN = "\033[32m" if _supports_colour() else ""
RED = "\033[31m" if _supports_colour() else ""

# ── Environment ───────────────────────────────────────────────────────────────

def load_environment(env_file: Path) -> dict[str, str]:
    if env_file.exists():
        load_dotenv(env_file)
    else:
        print(f"{DIM}(no .env at {env_file} — using current shell env){RESET}", file=sys.stderr)

    missing = [k for k in REQUIRED_ENV_VARS if not (os.getenv(k) or "").strip()]
    if missing:
        raise RuntimeError(
            f"{RED}Missing Foundry env vars: {', '.join(missing)}{RESET}\n"
            f"Copy .env.example → .env and fill them in."
        )

    env = dict(os.environ)
    env.setdefault("API_TIMEOUT_MS", "600000")
    env.setdefault("CLAUDE_CODE_MAX_RETRIES", "2")
    env.setdefault("DISABLE_TELEMETRY", "1")
    return env

def resolve_model(env: dict[str, str], cli_model: str | None) -> str:
    return (
        cli_model
        or env.get("ANTHROPIC_MODEL")
        or env.get("ANTHROPIC_DEFAULT_SONNET_MODEL")
        or DEFAULT_MODEL
    ).strip()

def guard_nested_session(allow_nested: bool, env: dict[str, str]) -> None:
    if not (os.environ.get("CLAUDECODE") or env.get("CLAUDECODE")):
        return
    if not allow_nested:
        raise RuntimeError(
            f"{RED}Detected CLAUDECODE — cannot spawn a nested Claude Code session.{RESET}\n"
            "Run from a normal terminal where CLAUDECODE is not set.\n"
            "Pass --allow-nested to override (may disrupt the parent session)."
        )
    for k in ("CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT"):
        env.pop(k, None)

# ── Output printing ───────────────────────────────────────────────────────────

def _print_text(text: str) -> None:
    """Print wizard text in cyan so it's visually distinct from the user prompt."""
    print(f"{CYAN}{text}{RESET}", end="", flush=True)

def _print_tool_use(name: str, inp: str) -> None:
    truncated = inp if len(inp) <= 200 else inp[:200] + " …"
    print(f"\n{DIM}[running: {name}  {truncated}]{RESET}", flush=True)

def _print_tool_result(content: str) -> None:
    if len(content) > 600:
        content = content[:600] + "\n… [truncated]"
    print(f"{DIM}{content}{RESET}", flush=True)

def _print_result(message: ResultMessage) -> None:
    print()  # blank line after the last streamed text
    if message.is_error:
        print(f"\n{RED}--- Session ended with error (stop: {message.stop_reason}) ---{RESET}")
        if message.result:
            print(message.result)
    else:
        print(f"\n{GREEN}--- Wizard session complete ---{RESET}")
        print(
            f"{DIM}Turns: {message.num_turns}  |  "
            f"Session: {message.session_id}"
            + (f"  |  Cost: ${message.total_cost_usd:.4f}" if message.total_cost_usd else "")
            + RESET
        )

# ── Input ─────────────────────────────────────────────────────────────────────

def _read_user_input() -> str | None:
    """Prompt the user and return their input, or None on EOF/Ctrl+D."""
    try:
        print(f"\n{BOLD}You:{RESET} ", end="", flush=True)
        line = sys.stdin.readline()
        if not line:  # EOF / Ctrl+D
            return None
        return line.rstrip("\n")
    except KeyboardInterrupt:
        return None

async def _drain_response(client: ClaudeSDKClient) -> ResultMessage | None:
    """Stream one wizard turn to stdout.

    Returns None when the turn ended normally (wizard is waiting for the next
    user message). Returns the ResultMessage when the turn ended with an error
    (so the caller knows to stop the loop).

    A normal-success ResultMessage means "I finished responding — your turn."
    It does NOT mean the session is over, so we never stop the loop on success.
    """
    async for message in client.receive_response():
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, TextBlock):
                    _print_text(block.text)
                elif isinstance(block, ToolUseBlock):
                    inp = str(block.input)
                    _print_tool_use(block.name, inp)
                elif isinstance(block, ToolResultBlock):
                    _print_tool_result(str(block.content))
        elif isinstance(message, ResultMessage):
            if message.is_error:
                _print_result(message)
                return message  # error — caller should stop
            # Normal turn end: wizard is waiting for user input.
            # Return None so the conversation loop continues.
            return None
    return None

# ── Initial prompt ────────────────────────────────────────────────────────────

def build_initial_prompt(output_dir: Path | None) -> str:
    """The first message that invokes the wizard skill and sets up the session."""
    output_hint = (
        (
            f"\nThe user has pre-selected this output directory: {output_dir}\n"
            "Skip the directory question and use it directly."
        )
        if output_dir
        else ""
    )

    return f"""\
/solution-wizard

You are starting an interactive GAIK Solution Configuration Wizard session.
Load and follow the complete wizard instructions from ./SKILL.md (you are already
in the wizard directory). {output_hint}

Begin Phase 1: introduce yourself briefly, then ask the user for the output
directory (unless already provided above) and their use-case description.
Wait for each answer before asking the next question -- this is an interactive
conversation, one or two questions at a time.
""".strip()

# ── Main interactive loop ─────────────────────────────────────────────────────

async def run_interactive(
    env: dict[str, str],
    model: str,
    output_dir: Path | None,
) -> int:
    options = ClaudeAgentOptions(
        cwd=str(WIZARD_DIR),
        add_dirs=[str(output_dir)] if output_dir else [],
        env=env,
        model=model,
        allowed_tools=["Read", "Grep", "Glob", "Write", "Edit", "Bash"],
        permission_mode="bypassPermissions",
        setting_sources=[],  # isolate from repo CLAUDE.md / graphify noise
    )

    print(f"{BOLD}GAIK Solution Configuration Wizard{RESET}")
    print(f"{DIM}Model: {model}  |  Wizard dir: {WIZARD_DIR}")
    if output_dir:
        print(f"Output dir: {output_dir}")
    print(f"Ctrl+C or Ctrl+D to exit{RESET}\n")

    async with ClaudeSDKClient(options=options) as client:
        # ── First turn: invoke the skill, get the wizard's opening message ──
        initial_prompt = build_initial_prompt(output_dir)
        await client.query(initial_prompt)
        error_result = await _drain_response(client)

        # ── Conversation loop ────────────────────────────────────────────────
        # _drain_response returns None on a normal turn end (wizard is waiting)
        # and a ResultMessage on error. Loop until error or user exits.
        while error_result is None:
            user_input = _read_user_input()
            if user_input is None:  # EOF or Ctrl+D
                print(f"\n{YELLOW}(session ended by user){RESET}")
                break
            if not user_input.strip():
                continue  # ignore blank lines

            await client.query(user_input)
            error_result = await _drain_response(client)

        if error_result is None:
            # Clean user exit — print a minimal summary.
            print(f"\n{DIM}Session ended.{RESET}")
        # Error case: _print_result was already called inside _drain_response.

    return 0

# ── CLI ───────────────────────────────────────────────────────────────────────

def parse_args() -> argparse.Namespace:
    p = argparse.ArgumentParser(
        description="Interactive CLI session for the GAIK Solution Configuration Wizard."
    )
    p.add_argument(
        "--output-dir",
        help="Pre-select the output directory (skips the wizard's directory question).",
    )
    p.add_argument(
        "--env-file",
        default=str(WIZARD_DIR.parent / ".env"),
        help="Path to .env with Foundry credentials (default: wizard dir/.env).",
    )
    p.add_argument(
        "--model",
        default=None,
        help=f"Override the model (default: ANTHROPIC_MODEL or {DEFAULT_MODEL}).",
    )
    p.add_argument(
        "--preflight",
        action="store_true",
        help="Check Foundry credentials with a tiny test query (no wizard session started).",
    )
    p.add_argument(
        "--allow-nested",
        action="store_true",
        help="Permit running inside a Claude Code session by dropping CLAUDECODE "
        "for the child process (may disrupt the parent session).",
    )
    return p.parse_args()

def main() -> None:
    args = parse_args()
    try:
        env = load_environment(Path(args.env_file).expanduser().resolve())
        guard_nested_session(args.allow_nested, env)
        model = resolve_model(env, args.model)

        if args.preflight:
            from claude_agent_sdk import query, ClaudeAgentOptions

            print(f"Preflight: checking Foundry routing (model={model}) …\n")

            async def _check():
                opts = ClaudeAgentOptions(
                    cwd=str(WIZARD_DIR),
                    env=env,
                    model=model,
                    allowed_tools=[],
                    permission_mode="bypassPermissions",
                    setting_sources=[],
                    max_turns=1,
                )
                async for msg in query(prompt="Reply with the single word: READY", options=opts):
                    if isinstance(msg, __import__("claude_agent_sdk").AssistantMessage):
                        for b in msg.content:
                            if hasattr(b, "text"):
                                print(f"{GREEN}{b.text}{RESET}", end="", flush=True)
                    elif isinstance(msg, __import__("claude_agent_sdk").ResultMessage):
                        print()
                        if msg.is_error:
                            print(f"{RED}Preflight failed (stop: {msg.stop_reason}){RESET}")
                            return 1
                        cost = f"  cost ${msg.total_cost_usd:.4f}" if msg.total_cost_usd else ""
                        print(f"{GREEN}Preflight passed{RESET}{DIM}{cost}{RESET}")
                        return 0
                return 0

            sys.exit(asyncio.run(_check()))

        output_dir: Path | None = None
        if args.output_dir:
            output_dir = Path(args.output_dir).expanduser()
            output_dir.mkdir(parents=True, exist_ok=True)
            output_dir = output_dir.resolve()

        sys.exit(asyncio.run(run_interactive(env, model, output_dir)))

    except KeyboardInterrupt:
        print(f"\n{YELLOW}Interrupted.{RESET}", file=sys.stderr)
        sys.exit(130)
    except Exception as exc:
        print(f"\n{RED}ERROR: {exc}{RESET}", file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    main()
```

## File: implementation_layer/solution_wizard/schemas/component_registry.schema.json
size: 2452
sha: 4ea785f5a5748a6ed5840211391c4b34dad65b4b
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/schemas/component_registry.schema.json

```text
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "GAIKComponentRegistry",
  "description": "Catalog of GAIK software modules and components the wizard can select from.",
  "type": "array",
  "items": {
    "type": "object",
    "required": [
      "id",
      "name",
      "type",
      "input_artifact_types",
      "output_artifact_types",
      "required_parameters",
      "best_for",
      "known_limitations",
      "import_path",
      "source_path",
      "readme_path",
      "example_script_path"
    ],
    "properties": {
      "id": { "type": "string" },
      "name": { "type": "string" },
      "type": {
        "type": "string",
        "enum": ["software_module", "software_component"]
      },
      "input_artifact_types": {
        "type": "array",
        "items": { "type": "string" }
      },
      "output_artifact_types": {
        "type": "array",
        "items": { "type": "string" }
      },
      "knowledge_processes": {
        "type": "array",
        "items": {
          "type": "string",
          "enum": ["knowledge_capture", "knowledge_access", "knowledge_synthesis"]
        }
      },
      "uses_components": {
        "type": "array",
        "items": { "type": "string" },
        "description": "Internal building blocks for software_module entries; empty for components."
      },
      "required_parameters": {
        "type": "array",
        "items": { "type": "string" }
      },
      "optional_parameters": {
        "type": "array",
        "items": { "type": "string" }
      },
      "supported_providers": {
        "type": "array",
        "items": { "type": "string" }
      },
      "cost_relevant_parameters": {
        "type": "array",
        "items": { "type": "string" }
      },
      "quality_tradeoffs": { "type": "object" },
      "known_limitations": {
        "type": "array",
        "items": { "type": "string" }
      },
      "best_for": {
        "type": "array",
        "items": { "type": "string" }
      },
      "example_blueprint_steps": {
        "type": "array",
        "items": { "type": "object" }
      },
      "source_path": { "type": "string" },
      "import_path": { "type": "string" },
      "example_script_path": { "type": "string" },
      "readme_path": { "type": "string" },
      "install_extra": {
        "type": "string",
        "description": "pip extra for gaik[extra] in generated requirements.txt"
      }
    }
  }
}
```

## File: implementation_layer/solution_wizard/schemas/use_case_blueprint.schema.json
size: 22525
sha: 12b9d8cc029fab03af84e1de8394ec4c4a1a5b9f
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/schemas/use_case_blueprint.schema.json

```text
{
  "$defs": {
    "Artifact": {
      "properties": {
        "type": {
          "title": "Type",
          "type": "string"
        },
        "source": {
          "$ref": "#/$defs/ArtifactSource"
        },
        "optional": {
          "title": "Optional",
          "type": "boolean"
        },
        "final_output": {
          "default": false,
          "title": "Final Output",
          "type": "boolean"
        },
        "externally_visible": {
          "default": false,
          "title": "Externally Visible",
          "type": "boolean"
        },
        "produced_by": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Produced By"
        },
        "schema_ref": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Schema Ref"
        },
        "overwrite": {
          "default": false,
          "title": "Overwrite",
          "type": "boolean"
        }
      },
      "required": [
        "type",
        "source",
        "optional"
      ],
      "title": "Artifact",
      "type": "object"
    },
    "ArtifactSource": {
      "enum": [
        "user_upload",
        "generated",
        "external_system",
        "static_resource"
      ],
      "title": "ArtifactSource",
      "type": "string"
    },
    "Assumption": {
      "properties": {
        "id": {
          "title": "Id",
          "type": "string"
        },
        "text": {
          "title": "Text",
          "type": "string"
        },
        "status": {
          "$ref": "#/$defs/AssumptionStatus",
          "default": "unconfirmed"
        },
        "impact": {
          "default": "",
          "title": "Impact",
          "type": "string"
        },
        "recorded_by": {
          "default": "",
          "title": "Recorded By",
          "type": "string"
        },
        "recorded_at": {
          "default": "",
          "title": "Recorded At",
          "type": "string"
        }
      },
      "required": [
        "id",
        "text"
      ],
      "title": "Assumption",
      "type": "object"
    },
    "AssumptionStatus": {
      "enum": [
        "unconfirmed",
        "confirmed",
        "overridden"
      ],
      "title": "AssumptionStatus",
      "type": "string"
    },
    "BlueprintStatus": {
      "enum": [
        "draft",
        "validated",
        "poc",
        "production_ready"
      ],
      "title": "BlueprintStatus",
      "type": "string"
    },
    "BusinessProcess": {
      "properties": {
        "participants": {
          "default": [],
          "items": {
            "$ref": "#/$defs/Participant"
          },
          "title": "Participants",
          "type": "array"
        },
        "manual_steps": {
          "default": [],
          "items": {
            "$ref": "#/$defs/ManualStep"
          },
          "title": "Manual Steps",
          "type": "array"
        },
        "external_parties": {
          "default": [],
          "items": {
            "$ref": "#/$defs/ExternalParty"
          },
          "title": "External Parties",
          "type": "array"
        },
        "message_flows": {
          "default": [],
          "items": {
            "$ref": "#/$defs/MessageFlow"
          },
          "title": "Message Flows",
          "type": "array"
        },
        "exceptions": {
          "default": [],
          "items": {
            "$ref": "#/$defs/ExceptionFlow"
          },
          "title": "Exceptions",
          "type": "array"
        },
        "decision_points": {
          "default": [],
          "items": {
            "$ref": "#/$defs/DecisionPoint"
          },
          "title": "Decision Points",
          "type": "array"
        }
      },
      "title": "BusinessProcess",
      "type": "object"
    },
    "ChangeLogEntry": {
      "properties": {
        "version": {
          "title": "Version",
          "type": "string"
        },
        "date": {
          "title": "Date",
          "type": "string"
        },
        "change": {
          "title": "Change",
          "type": "string"
        },
        "changed_by": {
          "title": "Changed By",
          "type": "string"
        }
      },
      "required": [
        "version",
        "date",
        "change",
        "changed_by"
      ],
      "title": "ChangeLogEntry",
      "type": "object"
    },
    "Components": {
      "properties": {
        "selected_modules": {
          "default": [],
          "items": {
            "$ref": "#/$defs/SelectedModule"
          },
          "title": "Selected Modules",
          "type": "array"
        },
        "selected_building_blocks": {
          "default": [],
          "items": {
            "type": "string"
          },
          "title": "Selected Building Blocks",
          "type": "array"
        },
        "custom_components": {
          "default": [],
          "items": {
            "type": "string"
          },
          "title": "Custom Components",
          "type": "array"
        }
      },
      "title": "Components",
      "type": "object"
    },
    "DataHandling": {
      "properties": {
        "external_model_api_allowed": {
          "default": true,
          "title": "External Model Api Allowed",
          "type": "boolean"
        },
        "local_processing_required": {
          "default": false,
          "title": "Local Processing Required",
          "type": "boolean"
        },
        "stores_input_data": {
          "default": false,
          "title": "Stores Input Data",
          "type": "boolean"
        },
        "stores_outputs": {
          "default": true,
          "title": "Stores Outputs",
          "type": "boolean"
        },
        "retention_policy": {
          "default": "not_specified",
          "title": "Retention Policy",
          "type": "string"
        },
        "contains_personal_data": {
          "default": "unknown",
          "title": "Contains Personal Data",
          "type": "string"
        },
        "output_sensitivity": {
          "default": "unknown",
          "title": "Output Sensitivity",
          "type": "string"
        },
        "audit_log_required": {
          "default": false,
          "title": "Audit Log Required",
          "type": "boolean"
        }
      },
      "title": "DataHandling",
      "type": "object"
    },
    "DecisionBranch": {
      "properties": {
        "condition": {
          "default": "",
          "title": "Condition",
          "type": "string"
        },
        "target": {
          "default": "",
          "title": "Target",
          "type": "string"
        }
      },
      "title": "DecisionBranch",
      "type": "object"
    },
    "DecisionPoint": {
      "description": "A business gateway beyond the implicit approve/reject one.",
      "properties": {
        "id": {
          "title": "Id",
          "type": "string"
        },
        "name": {
          "title": "Name",
          "type": "string"
        },
        "after": {
          "default": "",
          "title": "After",
          "type": "string"
        },
        "branches": {
          "default": [],
          "items": {
            "$ref": "#/$defs/DecisionBranch"
          },
          "title": "Branches",
          "type": "array"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "title": "DecisionPoint",
      "type": "object"
    },
    "ExceptionFlow": {
      "description": "An alternative / error path attached to a step.",
      "properties": {
        "id": {
          "title": "Id",
          "type": "string"
        },
        "name": {
          "title": "Name",
          "type": "string"
        },
        "attached_to": {
          "default": "",
          "title": "Attached To",
          "type": "string"
        },
        "condition": {
          "default": "",
          "title": "Condition",
          "type": "string"
        },
        "outcome": {
          "default": "end",
          "title": "Outcome",
          "type": "string"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "title": "ExceptionFlow",
      "type": "object"
    },
    "ExternalParty": {
      "description": "A black-box pool outside the organisation that hands inputs in.",
      "properties": {
        "id": {
          "title": "Id",
          "type": "string"
        },
        "name": {
          "title": "Name",
          "type": "string"
        },
        "sends": {
          "default": [],
          "items": {
            "type": "string"
          },
          "title": "Sends",
          "type": "array"
        },
        "to_step": {
          "default": "",
          "title": "To Step",
          "type": "string"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "title": "ExternalParty",
      "type": "object"
    },
    "Governance": {
      "properties": {
        "data_handling": {
          "$ref": "#/$defs/DataHandling",
          "default": {
            "external_model_api_allowed": true,
            "local_processing_required": false,
            "stores_input_data": false,
            "stores_outputs": true,
            "retention_policy": "not_specified",
            "contains_personal_data": "unknown",
            "output_sensitivity": "unknown",
            "audit_log_required": false
          }
        }
      },
      "title": "Governance",
      "type": "object"
    },
    "KnowledgeProcess": {
      "enum": [
        "knowledge_capture",
        "knowledge_access",
        "knowledge_synthesis"
      ],
      "title": "KnowledgeProcess",
      "type": "string"
    },
    "LoopConfig": {
      "properties": {
        "max_iterations": {
          "anyOf": [
            {
              "type": "integer"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Max Iterations"
        },
        "loop_condition_ref": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Loop Condition Ref"
        }
      },
      "title": "LoopConfig",
      "type": "object"
    },
    "ManualStep": {
      "description": "A human business step the AI pipeline does not model as a workflow step.",
      "properties": {
        "id": {
          "title": "Id",
          "type": "string"
        },
        "name": {
          "title": "Name",
          "type": "string"
        },
        "performed_by": {
          "default": "",
          "title": "Performed By",
          "type": "string"
        },
        "type": {
          "default": "user_task",
          "title": "Type",
          "type": "string"
        },
        "depends_on": {
          "default": [],
          "items": {
            "type": "string"
          },
          "title": "Depends On",
          "type": "array"
        },
        "consumes": {
          "default": [],
          "items": {
            "type": "string"
          },
          "title": "Consumes",
          "type": "array"
        },
        "produces": {
          "default": [],
          "items": {
            "type": "string"
          },
          "title": "Produces",
          "type": "array"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "title": "ManualStep",
      "type": "object"
    },
    "MessageFlow": {
      "description": "A cross-pool message (dashed line in BPMN).",
      "properties": {
        "from_": {
          "default": "",
          "title": "From",
          "type": "string"
        },
        "to": {
          "default": "",
          "title": "To",
          "type": "string"
        },
        "name": {
          "default": "",
          "title": "Name",
          "type": "string"
        }
      },
      "title": "MessageFlow",
      "type": "object"
    },
    "Metadata": {
      "properties": {
        "blueprint_id": {
          "default": "",
          "title": "Blueprint Id",
          "type": "string"
        },
        "created_by": {
          "default": "solution_configuration_wizard",
          "title": "Created By",
          "type": "string"
        },
        "created_at": {
          "default": "",
          "title": "Created At",
          "type": "string"
        },
        "last_updated_at": {
          "default": "",
          "title": "Last Updated At",
          "type": "string"
        },
        "status": {
          "$ref": "#/$defs/BlueprintStatus",
          "default": "draft"
        }
      },
      "title": "Metadata",
      "type": "object"
    },
    "Package": {
      "properties": {
        "name": {
          "default": "",
          "title": "Name",
          "type": "string"
        },
        "output_dir": {
          "default": "",
          "title": "Output Dir",
          "type": "string"
        },
        "include_tests": {
          "default": true,
          "title": "Include Tests",
          "type": "boolean"
        },
        "include_evals": {
          "default": true,
          "title": "Include Evals",
          "type": "boolean"
        },
        "include_docs": {
          "default": true,
          "title": "Include Docs",
          "type": "boolean"
        }
      },
      "title": "Package",
      "type": "object"
    },
    "Participant": {
      "description": "A swimlane (human role) or system lane in the BPMN.",
      "properties": {
        "id": {
          "title": "Id",
          "type": "string"
        },
        "name": {
          "title": "Name",
          "type": "string"
        },
        "kind": {
          "default": "human_role",
          "title": "Kind",
          "type": "string"
        },
        "default_lane_for": {
          "default": [],
          "items": {
            "type": "string"
          },
          "title": "Default Lane For",
          "type": "array"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "title": "Participant",
      "type": "object"
    },
    "Runtime": {
      "properties": {
        "interface": {
          "default": "cli",
          "title": "Interface",
          "type": "string"
        },
        "entrypoint": {
          "default": "",
          "title": "Entrypoint",
          "type": "string"
        },
        "env_file": {
          "default": ".env.example",
          "title": "Env File",
          "type": "string"
        }
      },
      "title": "Runtime",
      "type": "object"
    },
    "SelectedModule": {
      "properties": {
        "id": {
          "title": "Id",
          "type": "string"
        },
        "name": {
          "title": "Name",
          "type": "string"
        },
        "reason": {
          "default": "",
          "title": "Reason",
          "type": "string"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "title": "SelectedModule",
      "type": "object"
    },
    "TraceabilityEntry": {
      "properties": {
        "component": {
          "title": "Component",
          "type": "string"
        },
        "required_by": {
          "title": "Required By",
          "type": "string"
        }
      },
      "required": [
        "component",
        "required_by"
      ],
      "title": "TraceabilityEntry",
      "type": "object"
    },
    "UseCase": {
      "properties": {
        "id": {
          "title": "Id",
          "type": "string"
        },
        "name": {
          "title": "Name",
          "type": "string"
        },
        "description": {
          "title": "Description",
          "type": "string"
        },
        "domain": {
          "title": "Domain",
          "type": "string"
        },
        "knowledge_processes": {
          "default": [],
          "items": {
            "$ref": "#/$defs/KnowledgeProcess"
          },
          "title": "Knowledge Processes",
          "type": "array"
        }
      },
      "required": [
        "id",
        "name",
        "description",
        "domain"
      ],
      "title": "UseCase",
      "type": "object"
    },
    "Workflow": {
      "properties": {
        "steps": {
          "default": [],
          "items": {
            "$ref": "#/$defs/WorkflowStep"
          },
          "title": "Steps",
          "type": "array"
        }
      },
      "title": "Workflow",
      "type": "object"
    },
    "WorkflowStep": {
      "properties": {
        "id": {
          "title": "Id",
          "type": "string"
        },
        "name": {
          "title": "Name",
          "type": "string"
        },
        "type": {
          "title": "Type",
          "type": "string"
        },
        "component": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "default": null,
          "title": "Component"
        },
        "inputs": {
          "default": [],
          "items": {
            "type": "string"
          },
          "title": "Inputs",
          "type": "array"
        },
        "outputs": {
          "default": [],
          "items": {
            "type": "string"
          },
          "title": "Outputs",
          "type": "array"
        },
        "parameters": {
          "additionalProperties": true,
          "default": {},
          "title": "Parameters",
          "type": "object"
        },
        "depends_on": {
          "default": [],
          "items": {
            "type": "string"
          },
          "title": "Depends On",
          "type": "array"
        },
        "loop": {
          "anyOf": [
            {
              "$ref": "#/$defs/LoopConfig"
            },
            {
              "type": "null"
            }
          ],
          "default": null
        }
      },
      "required": [
        "id",
        "name",
        "type"
      ],
      "title": "WorkflowStep",
      "type": "object"
    }
  },
  "properties": {
    "blueprint_version": {
      "default": "1.0",
      "title": "Blueprint Version",
      "type": "string"
    },
    "metadata": {
      "$ref": "#/$defs/Metadata",
      "default": {
        "blueprint_id": "",
        "created_by": "solution_configuration_wizard",
        "created_at": "",
        "last_updated_at": "",
        "status": "draft"
      }
    },
    "use_case": {
      "$ref": "#/$defs/UseCase"
    },
    "business_spec": {
      "additionalProperties": true,
      "default": {},
      "title": "Business Spec",
      "type": "object"
    },
    "technical_spec": {
      "additionalProperties": true,
      "default": {},
      "title": "Technical Spec",
      "type": "object"
    },
    "target_output_spec": {
      "additionalProperties": true,
      "default": {},
      "title": "Target Output Spec",
      "type": "object"
    },
    "assumptions": {
      "default": [],
      "items": {
        "$ref": "#/$defs/Assumption"
      },
      "title": "Assumptions",
      "type": "array"
    },
    "artifacts": {
      "additionalProperties": {
        "$ref": "#/$defs/Artifact"
      },
      "default": {},
      "title": "Artifacts",
      "type": "object"
    },
    "inputs": {
      "additionalProperties": true,
      "default": {},
      "title": "Inputs",
      "type": "object"
    },
    "outputs": {
      "additionalProperties": true,
      "default": {},
      "title": "Outputs",
      "type": "object"
    },
    "schemas": {
      "additionalProperties": true,
      "default": {},
      "title": "Schemas",
      "type": "object"
    },
    "components": {
      "$ref": "#/$defs/Components",
      "default": {
        "selected_modules": [],
        "selected_building_blocks": [],
        "custom_components": []
      }
    },
    "workflow": {
      "$ref": "#/$defs/Workflow",
      "default": {
        "steps": []
      }
    },
    "business_process": {
      "$ref": "#/$defs/BusinessProcess",
      "default": {
        "participants": [],
        "manual_steps": [],
        "external_parties": [],
        "message_flows": [],
        "exceptions": [],
        "decision_points": []
      }
    },
    "prompts": {
      "additionalProperties": true,
      "default": {},
      "title": "Prompts",
      "type": "object"
    },
    "models": {
      "additionalProperties": true,
      "default": {},
      "title": "Models",
      "type": "object"
    },
    "validation": {
      "additionalProperties": true,
      "default": {},
      "title": "Validation",
      "type": "object"
    },
    "evaluation": {
      "additionalProperties": true,
      "default": {},
      "title": "Evaluation",
      "type": "object"
    },
    "governance": {
      "$ref": "#/$defs/Governance",
      "default": {
        "data_handling": {
          "audit_log_required": false,
          "contains_personal_data": "unknown",
          "external_model_api_allowed": true,
          "local_processing_required": false,
          "output_sensitivity": "unknown",
          "retention_policy": "not_specified",
          "stores_input_data": false,
          "stores_outputs": true
        }
      }
    },
    "runtime": {
      "$ref": "#/$defs/Runtime",
      "default": {
        "interface": "cli",
        "entrypoint": "",
        "env_file": ".env.example"
      }
    },
    "package": {
      "$ref": "#/$defs/Package",
      "default": {
        "name": "",
        "output_dir": "",
        "include_tests": true,
        "include_evals": true,
        "include_docs": true
      }
    },
    "visualizations": {
      "additionalProperties": true,
      "default": {},
      "title": "Visualizations",
      "type": "object"
    },
    "traceability": {
      "default": [],
      "items": {
        "$ref": "#/$defs/TraceabilityEntry"
      },
      "title": "Traceability",
      "type": "array"
    },
    "change_log": {
      "default": [],
      "items": {
        "$ref": "#/$defs/ChangeLogEntry"
      },
      "title": "Change Log",
      "type": "array"
    }
  },
  "required": [
    "use_case"
  ],
  "title": "Blueprint",
  "type": "object"
}
```

## File: implementation_layer/solution_wizard/src/solution_wizard/__init__.py
size: 48
sha: d369d1b2658df690238e8241677a1b7eed2c42f2
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/__init__.py

```text
"""GAIK Solution Configuration Wizard -- V1."""
```

## File: implementation_layer/solution_wizard/src/solution_wizard/blueprint.py
size: 9958
sha: 99f2f8d0b0d3bb6660b90049e32b056c8273f226
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/blueprint.py

```text
"""Pydantic blueprint models -- authoritative schema layer for V1.

These models are the single source of truth. JSON Schema in schemas/ is
exported from them via Blueprint.model_json_schema() and must never be
hand-edited.
"""

from __future__ import annotations

import json
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, field_validator, model_validator

# ---------------------------------------------------------------------------
# Enumerations
# ---------------------------------------------------------------------------

class KnowledgeProcess(str, Enum):
    CAPTURE = "knowledge_capture"
    ACCESS = "knowledge_access"
    SYNTHESIS = "knowledge_synthesis"

class BlueprintStatus(str, Enum):
    DRAFT = "draft"
    VALIDATED = "validated"
    POC = "poc"
    PRODUCTION_READY = "production_ready"

class ArtifactSource(str, Enum):
    USER_UPLOAD = "user_upload"
    GENERATED = "generated"
    EXTERNAL_SYSTEM = "external_system"
    STATIC_RESOURCE = "static_resource"

class AssumptionStatus(str, Enum):
    UNCONFIRMED = "unconfirmed"
    CONFIRMED = "confirmed"
    OVERRIDDEN = "overridden"

# ---------------------------------------------------------------------------
# Sub-models
# ---------------------------------------------------------------------------

class Metadata(BaseModel):
    blueprint_id: str = ""
    created_by: str = "solution_configuration_wizard"
    created_at: str = ""
    last_updated_at: str = ""
    status: BlueprintStatus = BlueprintStatus.DRAFT

class UseCase(BaseModel):
    id: str
    name: str
    description: str
    domain: str
    knowledge_processes: List[KnowledgeProcess] = []

class Artifact(BaseModel):
    type: str
    source: ArtifactSource
    # optional has NO default -- must be explicitly set (spec §10.3)
    optional: bool
    final_output: bool = False
    externally_visible: bool = False
    produced_by: Optional[str] = None
    schema_ref: Optional[str] = None
    overwrite: bool = False

    @model_validator(mode="after")
    def _check_source_rules(self) -> "Artifact":
        # produced_by is for GAIK-generated intermediates only: it names the
        # workflow step whose execution creates the artifact. User-upload
        # artifacts are *captured* by a user_task step (the step lists the
        # artifact in its outputs), but they are not *produced* by the GAIK
        # pipeline -- hence produced_by must be absent for user_upload.
        # This means a user_task step can legally list a user_upload artifact
        # in step.outputs without the artifact having a produced_by field;
        # the two sides (step.outputs vs artifact.produced_by) are independent.
        if self.source == ArtifactSource.GENERATED and self.produced_by is None:
            raise ValueError("Artifact with source='generated' requires 'produced_by' to be set.")
        if self.source == ArtifactSource.USER_UPLOAD and self.produced_by is not None:
            raise ValueError("Artifact with source='user_upload' must not have 'produced_by'.")
        return self

class LoopConfig(BaseModel):
    max_iterations: Optional[int] = None
    loop_condition_ref: Optional[str] = None

class WorkflowStep(BaseModel):
    id: str
    name: str
    type: str  # user_task | automated_task | human_review | decision
    component: Optional[str] = None
    inputs: List[str] = []
    outputs: List[str] = []
    parameters: Dict[str, Any] = {}
    depends_on: List[str] = []
    loop: Optional[LoopConfig] = None

class Workflow(BaseModel):
    steps: List[WorkflowStep] = []

class SelectedModule(BaseModel):
    id: str
    name: str
    reason: str = ""

class Components(BaseModel):
    selected_modules: List[SelectedModule] = []
    selected_building_blocks: List[str] = []
    custom_components: List[str] = []

class Assumption(BaseModel):
    id: str
    text: str
    status: AssumptionStatus = AssumptionStatus.UNCONFIRMED
    impact: str = ""
    recorded_by: str = ""
    recorded_at: str = ""

class DataHandling(BaseModel):
    external_model_api_allowed: bool = True
    local_processing_required: bool = False
    stores_input_data: bool = False
    stores_outputs: bool = True
    retention_policy: str = "not_specified"
    contains_personal_data: str = "unknown"
    output_sensitivity: str = "unknown"
    audit_log_required: bool = False

class Governance(BaseModel):
    data_handling: DataHandling = DataHandling()

class Runtime(BaseModel):
    interface: str = "cli"
    entrypoint: str = ""
    env_file: str = ".env.example"

class Package(BaseModel):
    name: str = ""
    output_dir: str = ""
    include_tests: bool = True
    include_evals: bool = True
    include_docs: bool = True

class TraceabilityEntry(BaseModel):
    component: str
    required_by: str

class ChangeLogEntry(BaseModel):
    version: str
    date: str
    change: str
    changed_by: str

# ---------------------------------------------------------------------------
# Business process (optional Level-2 BPMN enrichment, spec §15.2)
# ---------------------------------------------------------------------------
#
# This whole section is OPTIONAL. Every field defaults to empty so existing
# blueprints (and the test suite) validate unchanged. It exists so the BPMN
# view can be a genuine business-process model: business detail lives in the
# JSON (derived from this section + enrichment conventions), never authored
# only into the diagram. The BPMN is generated from here; it is never the
# source of truth (one-directional, linked-by-derivation).

class Participant(BaseModel):
    """A swimlane (human role) or system lane in the BPMN."""

    id: str
    name: str
    kind: str = "human_role"  # human_role | system
    # Step types whose tasks land in this lane when no explicit lane is given
    # (e.g. ["user_task", "human_review"]).
    default_lane_for: List[str] = []

class ManualStep(BaseModel):
    """A human business step the AI pipeline does not model as a workflow step."""

    id: str
    name: str
    performed_by: str = ""  # Participant id
    type: str = "user_task"  # user_task | manual_task
    depends_on: List[str] = []  # step ids (workflow steps or other manual steps)
    consumes: List[str] = []  # artifact ids
    produces: List[str] = []  # artifact ids

class ExternalParty(BaseModel):
    """A black-box pool outside the organisation that hands inputs in."""

    id: str
    name: str
    sends: List[str] = []  # artifact ids handed in via a message flow
    to_step: str = ""  # step id that receives the handoff

class MessageFlow(BaseModel):
    """A cross-pool message (dashed line in BPMN)."""

    # `from`/`to` are element ids (participant id, external party id, step id,
    # or data store id). `from_` is the Python attribute (from is reserved);
    # JSON uses the natural key "from" via the alias.
    from_: str = ""
    to: str = ""
    name: str = ""

    model_config = {"populate_by_name": True}

    @model_validator(mode="before")
    @classmethod
    def _accept_from_alias(cls, data: Any) -> Any:
        if isinstance(data, dict) and "from" in data and "from_" not in data:
            data = dict(data)
            data["from_"] = data.pop("from")
        return data

class ExceptionFlow(BaseModel):
    """An alternative / error path attached to a step."""

    id: str
    name: str
    attached_to: str = ""  # step id the boundary/branch hangs off
    condition: str = ""
    # "end" -> terminate; "loop_to:<step_id>" -> rework loop back to a step.
    outcome: str = "end"

class DecisionBranch(BaseModel):
    condition: str = ""
    target: str = ""  # step id or "end"

class DecisionPoint(BaseModel):
    """A business gateway beyond the implicit approve/reject one."""

    id: str
    name: str
    after: str = ""  # step id this gateway follows
    branches: List[DecisionBranch] = []

class BusinessProcess(BaseModel):
    participants: List[Participant] = []
    manual_steps: List[ManualStep] = []
    external_parties: List[ExternalParty] = []
    message_flows: List[MessageFlow] = []
    exceptions: List[ExceptionFlow] = []
    decision_points: List[DecisionPoint] = []

# ---------------------------------------------------------------------------
# Top-level Blueprint
# ---------------------------------------------------------------------------

class Blueprint(BaseModel):
    blueprint_version: str = "1.0"
    metadata: Metadata = Metadata()
    use_case: UseCase
    business_spec: Dict[str, Any] = {}
    technical_spec: Dict[str, Any] = {}
    target_output_spec: Dict[str, Any] = {}
    assumptions: List[Assumption] = []
    artifacts: Dict[str, Artifact] = {}
    inputs: Dict[str, Any] = {}
    outputs: Dict[str, Any] = {}
    schemas: Dict[str, Any] = {}
    components: Components = Components()
    workflow: Workflow = Workflow()
    business_process: BusinessProcess = BusinessProcess()
    prompts: Dict[str, Any] = {}
    models: Dict[str, Any] = {}
    validation: Dict[str, Any] = {}
    evaluation: Dict[str, Any] = {}
    governance: Governance = Governance()
    runtime: Runtime = Runtime()
    package: Package = Package()
    visualizations: Dict[str, Any] = {}
    traceability: List[TraceabilityEntry] = []
    change_log: List[ChangeLogEntry] = []

    @classmethod
    def from_file(cls, path: str | Path) -> "Blueprint":
        data = json.loads(Path(path).read_text(encoding="utf-8"))
        return cls.model_validate(data)

    def to_file(self, path: str | Path) -> None:
        Path(path).write_text(self.model_dump_json(indent=2), encoding="utf-8")

    @classmethod
    def export_json_schema(cls, path: str | Path) -> None:
        """Export JSON Schema to file (regenerates schemas/use_case_blueprint.schema.json)."""
        schema = cls.model_json_schema()
        Path(path).write_text(json.dumps(schema, indent=2), encoding="utf-8")
```

## File: implementation_layer/solution_wizard/src/solution_wizard/bpmn_generator.py
size: 60494
sha: 8703ca9a7b57b206f9bc3b4f4302425cb0a035f9
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/bpmn_generator.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/bpmn_generator.py

## File: implementation_layer/solution_wizard/src/solution_wizard/docs_generator.py
size: 8603
sha: 285180e914f0b3ca23d67f1fe01da1b9024a3675
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/docs_generator.py

```text
"""Documentation suite generator (spec §18) -- V3.

Produces the five use-case documents from a validated blueprint into
``<output_dir>/docs/``:

    genai_product_canvas.md      technical_specification.md
    user_guide.md                developer_guide.md
    evaluation_plan.md

Design: deterministic skeleton + agent prose ("Python enforces structure, the
agent authors meaning"). Each template carries ``${...}`` placeholders that this
module fills with blueprint-derived facts, plus ``<!-- AGENT: ... -->`` markers
the wizard fills with narrative afterwards. Mirrors ``scaffolder._write_readme``.

Public API mirrors the other generators:
    generate_docs(blueprint, output_dir) -> list[Path]
"""

from __future__ import annotations

import string
from pathlib import Path
from typing import Any, Dict, List

from .blueprint import Blueprint

TEMPLATES_DIR = Path(__file__).parent.parent.parent / "templates" / "docs"

DOC_NAMES = [
    "genai_product_canvas",
    "technical_specification",
    "user_guide",
    "developer_guide",
    "evaluation_plan",
]

# ---------------------------------------------------------------------------
# Formatting helpers
# ---------------------------------------------------------------------------

def _as_dict(value: Any) -> Dict[str, Any]:
    return value if isinstance(value, dict) else {}

def _fmt_value(value: Any) -> str:
    if value is None or value == "":
        return "_not specified_"
    if isinstance(value, bool):
        return "yes" if value else "no"
    if isinstance(value, list):
        return ", ".join(str(v) for v in value) if value else "_none_"
    if isinstance(value, dict):
        return ", ".join(f"{k}: {v}" for k, v in value.items()) if value else "_none_"
    return str(value)

def _bullets(value: Any) -> str:
    if isinstance(value, list):
        items = [str(v) for v in value if str(v).strip()]
    elif isinstance(value, dict):
        items = [f"**{k}**: {v}" for k, v in value.items()]
    elif value in (None, ""):
        items = []
    else:
        items = [str(value)]
    if not items:
        return "- _not specified_"
    return "\n".join(f"- {it}" for it in items)

def _field(d: Dict[str, Any], key: str) -> str:
    return _fmt_value(d.get(key))

# ---------------------------------------------------------------------------
# Variable extraction
# ---------------------------------------------------------------------------

def _components_block(blueprint: Blueprint) -> str:
    comps = blueprint.components
    lines: List[str] = []
    for m in comps.selected_modules:
        mid = getattr(m, "name", None) or getattr(m, "id", "")
        reason = getattr(m, "reason", "") or ""
        lines.append(f"- **{mid}** (module){f' — {reason}' if reason else ''}")
    for b in comps.selected_building_blocks:
        lines.append(f"- **{b}** (component)")
    for c in comps.custom_components:
        lines.append(f"- **{c}** (custom)")
    return "\n".join(lines) if lines else "- _none selected_"

def _workflow_table(blueprint: Blueprint) -> str:
    steps = blueprint.workflow.steps
    if not steps:
        return "_No workflow steps defined._"
    rows = [
        "| Step | Type | Component | Inputs | Outputs |",
        "|------|------|-----------|--------|---------|",
    ]
    for s in steps:
        comp = s.component or "—"
        params = ""
        if s.parameters:
            params = " <br/>opts: " + ", ".join(f"{k}={v}" for k, v in s.parameters.items())
        rows.append(
            f"| {s.id} | {s.type} | {comp}{params} | {', '.join(s.inputs) or '—'} | {', '.join(s.outputs) or '—'} |"
        )
    return "\n".join(rows)

def _artifacts_block(blueprint: Blueprint) -> str:
    if not blueprint.artifacts:
        return "- _none declared_"
    lines = []
    for aid, art in blueprint.artifacts.items():
        flags = []
        if art.final_output:
            flags.append("final output")
        if art.optional:
            flags.append("optional")
        suffix = f" ({', '.join(flags)})" if flags else ""
        lines.append(f"- `{aid}` — {art.type}, source: {art.source.value}{suffix}")
    return "\n".join(lines)

def _build_doc_variables(blueprint: Blueprint) -> Dict[str, str]:
    uc = blueprint.use_case
    bspec = _as_dict(blueprint.business_spec)
    tspec = _as_dict(blueprint.technical_spec)
    tos = _as_dict(blueprint.target_output_spec)
    dh = blueprint.governance.data_handling

    fields = tos.get("fields") or []
    runtime = tspec.get("runtime_interface") or "cli"
    run_cmd = (
        "python poc/run_poc.py" if "cli" in str(runtime).lower() else f"({runtime} entry point)"
    )

    return {
        # identity
        "use_case_name": uc.name,
        "use_case_id": uc.id,
        "domain": uc.domain or "_not specified_",
        "description": uc.description or "_not specified_",
        "knowledge_processes": _fmt_value(
            [k.value if hasattr(k, "value") else k for k in uc.knowledge_processes]
        ),
        # business
        "current_process": _field(bspec, "current_process"),
        "pain_points": _bullets(bspec.get("pain_points")),
        "proposed_solution": _field(bspec, "proposed_solution"),
        "intended_users": _fmt_value(bspec.get("intended_users")),
        "reviewers": _fmt_value(bspec.get("reviewers")),
        "stakeholders": _fmt_value(bspec.get("stakeholders")),
        "input_artifacts": _fmt_value(bspec.get("input_artifacts") or tspec.get("input_types")),
        "target_outputs": _fmt_value(bspec.get("target_outputs") or tspec.get("output_types")),
        "success_criteria": _bullets(bspec.get("success_criteria")),
        "expected_value": _bullets(bspec.get("expected_value")),
        "risks": _bullets(bspec.get("risks")),
        "poc_goal": _field(bspec, "poc_goal"),
        # technical
        "input_types": _fmt_value(tspec.get("input_types")),
        "input_formats": _fmt_value(tspec.get("input_formats")),
        "output_types": _fmt_value(tspec.get("output_types")),
        "language": _field(tspec, "language"),
        "domain_vocabulary": _fmt_value(tspec.get("domain_vocabulary")),
        "data_sources": _fmt_value(tspec.get("data_sources")),
        "model_provider": _field(tspec, "model_provider"),
        "model_preferences": _fmt_value(tspec.get("model_preferences")),
        "security_constraints": _fmt_value(tspec.get("security_constraints")),
        "integration_targets": _fmt_value(tspec.get("integration_targets")),
        "human_review": _fmt_value(tspec.get("human_review", tspec.get("human_review_required"))),
        "evaluation_requirements": _fmt_value(tspec.get("evaluation_requirements")),
        "runtime_interface": _fmt_value(runtime),
        "run_command": run_cmd,
        # target output
        "schema_name": _field(tos, "schema_name"),
        "fields_list": _bullets(fields),
        "field_count": str(len(fields)),
        "required_fields": _fmt_value(tos.get("required_fields")),
        "missing_value_policy": _field(tos, "missing_value_policy"),
        "validation_rules": _bullets(tos.get("validation_rules")),
        # governance
        "contains_personal_data": dh.contains_personal_data,
        "output_sensitivity": dh.output_sensitivity,
        "audit_log_required": "yes" if dh.audit_log_required else "no",
        # composed blocks
        "components_block": _components_block(blueprint),
        "workflow_table": _workflow_table(blueprint),
        "artifacts_block": _artifacts_block(blueprint),
    }

# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

def generate_docs(blueprint: Blueprint, output_dir: str | Path) -> List[Path]:
    """Generate the five documents into ``<output_dir>/docs/``.

    Returns the list of written paths. Templates use ``string.Template`` with
    ``safe_substitute`` so an unexpected placeholder never raises -- but the
    test suite asserts no ``${...}`` remains for the standard variable set.
    """
    variables = _build_doc_variables(blueprint)
    docs_dir = Path(output_dir) / "docs"
    docs_dir.mkdir(parents=True, exist_ok=True)

    written: List[Path] = []
    for name in DOC_NAMES:
        tmpl_path = TEMPLATES_DIR / f"{name}.md.tmpl"
        content = string.Template(tmpl_path.read_text(encoding="utf-8")).safe_substitute(variables)
        out_path = docs_dir / f"{name}.md"
        out_path.write_text(content, encoding="utf-8")
        written.append(out_path)
    return written
```

## File: implementation_layer/solution_wizard/src/solution_wizard/registry.py
size: 6848
sha: 516936c5658fae585ad2e65741ea0e281d23d7d5
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/registry.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/registry.py

## File: implementation_layer/solution_wizard/src/solution_wizard/requirements.py
size: 8423
sha: 7731a382b386671788c8f79161037305a4675f48
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/requirements.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/requirements.py

## File: implementation_layer/solution_wizard/src/solution_wizard/scaffolder.py
size: 47285
sha: 6828963e244894de3ae9bfe2ecc796b01fd96d87
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/scaffolder.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/scaffolder.py

## File: implementation_layer/solution_wizard/src/solution_wizard/schema_designer.py
size: 19218
sha: 9be5e8140ece984be92c2c35ff564c937a50a784
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/schema_designer.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/schema_designer.py

## File: implementation_layer/solution_wizard/src/solution_wizard/selector.py
size: 3781
sha: d0a72a2513cd061bfb664eb22ebd042c6cc630e0
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/selector.py

```text
"""Selection reference data -- transformation chains and the module-first map.

The agent (via SKILL.md) owns pattern classification and component selection.
It reads the registry fields (input_artifact_types, output_artifact_types,
best_for, known_limitations) and reasons from them directly -- there is no
deterministic classifier here.

This module provides two pieces of *reference data* the agent consults as
hints, not as gates:

  1. CHAINS / transformation_chain() -- the canonical ordered data states for
     each known pattern, so the agent structures workflows consistently.
  2. module_for_pattern() -- the module-first rule encoded as data: if a single
     GAIK module covers a pattern end-to-end, it is the preferred choice.

The agent may override either hint when the use case calls for it. Pattern
names below are conventions the agent uses in conversation; they are not a
fixed enum the agent must match exactly.

Adding a new component to the registry requires no changes here.
"""

from __future__ import annotations

from typing import Dict, List, Optional

from .registry import Registry, get_registry

# ---------------------------------------------------------------------------
# Transformation chains per pattern
# Each entry is the ordered list of data states from input to final output.
# These are scaffolds the agent adapts -- not rigid templates.
# ---------------------------------------------------------------------------

CHAINS: Dict[str, List[str]] = {
    "audio_to_structured": [
        "audio_input",
        "raw_transcript",
        "enhanced_transcript",
        "structured_json",
        "validated_output",
        "final_output",
    ],
    "transcript_only": [
        "audio_input",
        "raw_transcript",
        "enhanced_transcript",
    ],
    "document_to_structured": [
        "document_input",
        "parsed_text",
        "structured_json",
        "validated_output",
        "final_output",
    ],
    "vision_extraction": [
        "image_input",
        "structured_json",
        "validated_output",
        "final_output",
    ],
    "rag": [
        "document_collection",
        "text_chunks",
        "vector_index",
        "retrieved_chunks",
        "answer_with_citations",
    ],
    "classification": [
        "document_input",
        "classification_result",
    ],
    "multi_source_report": [
        "source_files",
        "normalized_evidence",
        "report_sections",
        "markdown_report",
        "final_output",
    ],
    "hybrid": [
        "mixed_input",
        "intermediate_output",
        "structured_output",
    ],
}

# Maps each pattern to the single GAIK module that covers it end-to-end,
# where one exists. This is the module-first rule encoded as data.
_PATTERN_TO_MODULE: Dict[str, str] = {
    "audio_to_structured": "audio_to_structured_data",
    "document_to_structured": "documents_to_structured_data",
    "rag": "rag_workflow",
    "multi_source_report": "multi_source_report_generator",
}

def transformation_chain(pattern: str) -> List[str]:
    """Return the canonical ordered data states for a pattern (a hint).

    Falls back to the generic 'hybrid' chain for unknown patterns.
    """
    return CHAINS.get(pattern, CHAINS["hybrid"])

def module_for_pattern(pattern: str, registry: Optional[Registry] = None) -> Optional[Dict]:
    """Return the registry entry for the module that covers this pattern, or None.

    None means no single module covers the pattern end-to-end, so the agent
    should compose a pipeline from individual components.
    """
    module_id = _PATTERN_TO_MODULE.get(pattern)
    if not module_id:
        return None
    if registry is None:
        registry = get_registry()
    return registry.lookup_by_id(module_id)
```

## File: implementation_layer/solution_wizard/src/solution_wizard/validator.py
size: 16738
sha: 3d39e43fff28395eb5ff8d9e36edc1b4f2e8e7c9
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/validator.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/validator.py

## File: implementation_layer/solution_wizard/src/solution_wizard/visualizer.py
size: 3181
sha: e7d32a544433254c1307f99ebda7d067b2b2e5a4
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/visualizer.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/src/solution_wizard/visualizer.py

## File: implementation_layer/solution_wizard/templates/blueprint_template.json
size: 4589
sha: 18116f71ae478ae99d6fc177c6774d457911d1da
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/blueprint_template.json

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/blueprint_template.json

## File: implementation_layer/solution_wizard/templates/docs/developer_guide.md.tmpl
size: 1919
sha: 4c4ae1d125cacfeb825412bf9d5da28692356e4c
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/docs/developer_guide.md.tmpl

````text
# Developer Guide — ${use_case_name}

> Package structure, extension points, component configuration, tests, and evaluation.

## Architecture
This use case is driven by an **executable JSON blueprint** (`use_case.blueprint.json`) — the single source of truth. The PoC, the visual workflows (`workflow.mmd`, `workflow.bpmn`), and these docs are all *generated from it*. To change behaviour, edit the blueprint and regenerate; never hand-edit generated artifacts.

## Pipeline
${workflow_table}

### Components and their options
${components_block}

<!-- AGENT: for each component, note the constructor/option values set and where they live in config. -->

## Layout (PoC)
```
poc/
  run_poc.py            <- pipeline entry point
  config.yaml           <- model names, temperature, paths
  requirements.txt
  .env.example
  schemas/              <- output_schema.py (Pydantic) + requirements + hash
  prompts/              <- extraction_requirements.md, validation_rubric.md
  sample_input/  output/
  evals/                <- run_basic_eval.py
```

## Extension points
- **Add/replace a component:** update `components` + `workflow.steps` + `artifacts` in the blueprint, re-validate, regenerate.
- **Change the schema/fields:** edit `target_output_spec` (and `prompts/extraction_requirements.md`); the schema regenerates on the next run via the requirements hash.
- **Tune a component option:** set it in `workflow.steps[].parameters` (e.g. `enhanced_transcript`, `include_verification`, `hybrid_search`).

## Configuration
- **Model provider:** ${model_provider}
- **Model preferences:** ${model_preferences}
- **Integration targets:** ${integration_targets}

## Tests and evaluation
- Evaluation requirements: ${evaluation_requirements}
- See `evals/` for the basic evaluation script and `evaluation_plan.md` for the full plan.

<!-- AGENT: note any component-specific gotchas a future developer should know. -->
````

## File: implementation_layer/solution_wizard/templates/docs/evaluation_plan.md.tmpl
size: 1507
sha: 631c7f277efe19d83f5ecf35ea438cd18d04be56
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/docs/evaluation_plan.md.tmpl

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/docs/evaluation_plan.md.tmpl

## File: implementation_layer/solution_wizard/templates/docs/genai_product_canvas.md.tmpl
size: 1340
sha: 990ead605c0e8b957a1085b5a2eebf3f528bfb3a
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/docs/genai_product_canvas.md.tmpl

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/docs/genai_product_canvas.md.tmpl

## File: implementation_layer/solution_wizard/templates/docs/technical_specification.md.tmpl
size: 1466
sha: 96fc787986083ca22d88f97b8071bc4f666c1549
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/docs/technical_specification.md.tmpl

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/docs/technical_specification.md.tmpl

## File: implementation_layer/solution_wizard/templates/docs/user_guide.md.tmpl
size: 1371
sha: fd270ef64513fc5ccd44d8836c75c6b0368e2790
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/docs/user_guide.md.tmpl

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/docs/user_guide.md.tmpl

## File: implementation_layer/solution_wizard/templates/poc/_common/config.yaml.tmpl
size: 143
sha: 2cc0e7cf0076ae8b6503efea31b054f18224458f
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/_common/config.yaml.tmpl

```text
# Provider/stage settings. Keep credentials in .env.
# Explicit stages override the top-level legacy provider/model fallback.
${settings_yaml}
```

## File: implementation_layer/solution_wizard/templates/poc/_common/env.example.tmpl
size: 2310
sha: e6471a0ff8d884a134b748796b5da1606250f829
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/_common/env.example.tmpl

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/_common/env.example.tmpl

## File: implementation_layer/solution_wizard/templates/poc/_common/pdf_report.py.tmpl
size: 8652
sha: b9f88d8100eba2c6ad1c7b49dcedcdb14c6e305d
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/_common/pdf_report.py.tmpl

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/_common/pdf_report.py.tmpl

## File: implementation_layer/solution_wizard/templates/poc/_common/provider_config.py.tmpl
size: 2360
sha: 4632197c9657b3ee417049c3116ff5c37cfb448f
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/_common/provider_config.py.tmpl

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/_common/provider_config.py.tmpl

## File: implementation_layer/solution_wizard/templates/poc/_common/README.md.tmpl
size: 3142
sha: 6907504e849ec9fae556b83f1b747d518a3a8046
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/_common/README.md.tmpl

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/_common/README.md.tmpl

## File: implementation_layer/solution_wizard/templates/poc/_common/run_basic_eval.py.tmpl
size: 2017
sha: 44756b24b3773d974662b7e8a28d53c1e2e2bf05
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/_common/run_basic_eval.py.tmpl

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/_common/run_basic_eval.py.tmpl

## File: implementation_layer/solution_wizard/templates/poc/_generic/run_poc.py.tmpl
size: 13198
sha: d83d220b20de1ed8a4017688e7725a1bccd9e4ca
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/_generic/run_poc.py.tmpl

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/_generic/run_poc.py.tmpl

## File: implementation_layer/solution_wizard/templates/poc/audio_to_structured/run_poc.py.tmpl
size: 8449
sha: a8a4894dda96b0801fa0f1fc702d35a0a5a92570
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/audio_to_structured/run_poc.py.tmpl

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/audio_to_structured/run_poc.py.tmpl

## File: implementation_layer/solution_wizard/templates/poc/document_to_structured/run_poc.py.tmpl
size: 8993
sha: da50c73d2935e21a61e4c5d5de4341c811e1e086
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/document_to_structured/run_poc.py.tmpl

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/document_to_structured/run_poc.py.tmpl

## File: implementation_layer/solution_wizard/templates/poc/hybrid_dataextractor_pymupdfparser_transcriber_831947a5/run_poc.py.tmpl
size: 9506
sha: 3f0d2621f2f7059305105777fab025821e4f883f
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/hybrid_dataextractor_pymupdfparser_transcriber_831947a5/run_poc.py.tmpl

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/hybrid_dataextractor_pymupdfparser_transcriber_831947a5/run_poc.py.tmpl

## File: implementation_layer/solution_wizard/templates/poc/hybrid_dataextractor_pymupdfparser_transcriber_831947a5/template.json
size: 599
sha: fc01295069c56c81013e55c4af0682a26558d11b
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/hybrid_dataextractor_pymupdfparser_transcriber_831947a5/template.json

```text
{
  "pattern_key": "hybrid_dataextractor_pymupdfparser_transcriber_831947a5",
  "components": [
    "Transcriber",
    "PyMuPDFParser",
    "DataExtractor"
  ],
  "topology_signature": [
    "Transcriber(['voice_note_audio']->['raw_transcript'])",
    "PyMuPDFParser(['referral_pdf']->['parsed_referral_text'])",
    "DataExtractor(['combined_clinical_text']->['patient_record_json'])"
  ],
  "variables_used": [
    "language",
    "use_case_name"
  ],
  "validated_by": [
    "clinical_documentation_extraction"
  ],
  "status": "provisional",
  "created_at": "2026-06-06T19:25:08.735044+00:00"
}
```

## File: implementation_layer/solution_wizard/templates/poc/rag/run_poc.py.tmpl
size: 6084
sha: 1fcf8a17406e7b771a3e67c9efb5f61e1f7e0236
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/rag/run_poc.py.tmpl

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/templates/poc/rag/run_poc.py.tmpl

## File: implementation_layer/solution_wizard/tests/test_audit_registry.py
size: 3820
sha: ffcd773eb6ae43c8f236f0f8355817447a097a39
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_audit_registry.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_audit_registry.py

## File: implementation_layer/solution_wizard/tests/test_blueprint.py
size: 3175
sha: 41def0ce01054d8a90ef6ed46076d82a2b911200
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_blueprint.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_blueprint.py

## File: implementation_layer/solution_wizard/tests/test_bpmn.py
size: 28431
sha: 1aa6663b9559efc676ae30826b7db63aebe1f101
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_bpmn.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_bpmn.py

## File: implementation_layer/solution_wizard/tests/test_decimal_schema_persistence.py
size: 13550
sha: 468144c283446a0013a6837786b11d92cfa4b664
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_decimal_schema_persistence.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_decimal_schema_persistence.py

## File: implementation_layer/solution_wizard/tests/test_docs.py
size: 4670
sha: d5abe0edff1af7de91d0148843efa1d6092c2972
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_docs.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_docs.py

## File: implementation_layer/solution_wizard/tests/test_generate_schema_cli.py
size: 7296
sha: 3227d53f802352cf9a5051776eb7117fa8ede42d
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_generate_schema_cli.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_generate_schema_cli.py

## File: implementation_layer/solution_wizard/tests/test_hybrid_generic.py
size: 17160
sha: b40f88b9794b32af48bb17ec79a2e29b4e1e179e
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_hybrid_generic.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_hybrid_generic.py

## File: implementation_layer/solution_wizard/tests/test_mermaid.py
size: 2248
sha: d13c2fc223cdcdabcf92cfeaaa8c57071e9cbf4b
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_mermaid.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_mermaid.py

## File: implementation_layer/solution_wizard/tests/test_pdf_report.py
size: 6386
sha: b5cf7517dcbe1165ebdae3fcba47a306037ae081
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_pdf_report.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_pdf_report.py

## File: implementation_layer/solution_wizard/tests/test_pipeline_composite_loading.py
size: 2547
sha: 73b9d754900f7101edcddadac15aad9185b5155b
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_pipeline_composite_loading.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_pipeline_composite_loading.py

## File: implementation_layer/solution_wizard/tests/test_promotion.py
size: 7875
sha: aea25d2dcafa35a5658bba6f4a0874c70d2cd86a
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_promotion.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_promotion.py

## File: implementation_layer/solution_wizard/tests/test_provider_scaffolding.py
size: 20688
sha: 805e98ac269cb47168286f146230337c978e71c8
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_provider_scaffolding.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_provider_scaffolding.py

## File: implementation_layer/solution_wizard/tests/test_reference_cards.py
size: 12239
sha: e9e4b8f2fbfe109ae4e1874937e2f602a50430ae
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_reference_cards.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_reference_cards.py

## File: implementation_layer/solution_wizard/tests/test_requirements.py
size: 4140
sha: f3a52d8cd19793c59739450a8b4f1bd7ec974395
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_requirements.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_requirements.py

## File: implementation_layer/solution_wizard/tests/test_scaffolder.py
size: 8389
sha: 476e31da47c5e9178090719e2f89215685b0b242
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_scaffolder.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_scaffolder.py

## File: implementation_layer/solution_wizard/tests/test_schema_designer.py
size: 8053
sha: 56d5e2f092f0673c93a090161a2bbaca660320d6
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_schema_designer.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_schema_designer.py

## File: implementation_layer/solution_wizard/tests/test_selector.py
size: 4557
sha: 7ef43fa95fca12f47ad99c235c84dddec114ee57
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_selector.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_selector.py

## File: implementation_layer/solution_wizard/tests/test_validator.py
size: 12905
sha: cbaa0073d0b3bada847e0e666f9a0d6ac71ffb31
upstream: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_validator.py

Content is not inlined in the catalog. Read the upstream file: https://github.com/gaik-project/gaik-toolkit/blob/main/implementation_layer/solution_wizard/tests/test_validator.py
