# Design Document: Capacity Details Enrichment ## Overview This feature enriches the existing `get_capacity_details` MCP tool to display additional information about a capacity: its description, references (partner + projects), and certifications. The data is already available in the database and exposed through existing `DBClient` methods (`get_capacity_description`, `get_capacity_references`, `get_capacity_certificates`). The change is purely in the tool's output formatting layer. The tool currently returns a Markdown table with basic capacity fields (ID, Owner, Role, Competences, Availability) followed by a "Next steps" section. After this enhancement, it will include three additional sections between the table and the next steps: Beschreibung, Referenzen, and Zertifizierungen. ## Architecture The change is localized to the `get_capacity_details` tool function inside `build_server()` in `src/teamlandkarte_mcp/mcp_server.py`. No new modules, classes, or external dependencies are needed. ```mermaid sequenceDiagram participant Client as MCP Client participant Tool as get_capacity_details participant DB as DBClient Client->>Tool: call(capacity_id) Tool->>DB: get_capacity_by_id(capacity_id) DB-->>Tool: Capacity | None Tool->>DB: get_capacity_description(capacity_id) DB-->>Tool: str | None Tool->>DB: get_capacity_references(capacity_id) DB-->>Tool: list[CapacityReferenceRow] Tool->>DB: get_capacity_certificates(capacity_id) DB-->>Tool: list[str] Tool-->>Client: Formatted Markdown string ``` ### Design Decisions 1. **Sequential DB calls** – The three additional queries are simple key lookups on indexed views. Parallelizing them would add complexity (async conversion of the tool) for negligible latency gain. Keep the tool synchronous. 2. **Formatting inline** – The formatting logic is simple string concatenation. No need for a separate formatter class. 3. **Empty-state handling** – Each section shows a "(keine)" placeholder when data is absent, keeping the output structure predictable for LLM consumers. ## Components and Interfaces ### Modified Component: `get_capacity_details` tool **Current signature** (unchanged): ```python def get_capacity_details(capacity_id: int | str) -> str: ``` **New internal calls added:** ```python description: str | None = db_client.get_capacity_description(capacity_id) references: list[CapacityReferenceRow] = db_client.get_capacity_references(capacity_id) certificates: list[str] = db_client.get_capacity_certificates(capacity_id) ``` **Output format** (Markdown string): ``` | ID | Owner | Role | Competences | Availability | | ... | ## Beschreibung ## Referenzen - **Partner A**: Projekt X, Projekt Y - Projekt Z (no partner) *or* Referenzen: (keine) ## Zertifizierungen - Zertifikat 1 - Zertifikat 2 *or* Zertifizierungen: (keine) ## Next steps Call find_matching_tasks(capacity_id=...) to see matching open tasks. ``` ### Existing Interfaces Used (no changes) | Method | Returns | Source | |--------|---------|--------| | `DBClient.get_capacity_description(capacity_id)` | `str \| None` | `teamlandkarte_v_capacities_latest.description` | | `DBClient.get_capacity_references(capacity_id)` | `list[CapacityReferenceRow]` | `teamlandkarte_v_capacity_references_latest` joined with partners | | `DBClient.get_capacity_certificates(capacity_id)` | `list[str]` | `teamlandkarte_v_capacity_certificates_latest.description` | ## Data Models ### CapacityReferenceRow (existing, unchanged) ```python class CapacityReferenceRow(TypedDict): partner_name: str # May be empty string when partner_id is NULL projects: str # Project text from the references view ``` No new data models are introduced. ## Correctness Properties *A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.* ### Property 1: All enrichment data is fetched for any capacity *For any* valid capacity ID that resolves to an existing capacity, the tool SHALL invoke `get_capacity_description`, `get_capacity_references`, and `get_capacity_certificates` with that capacity ID. **Validates: Requirements 1.1, 2.1, 3.1** ### Property 2: Non-empty data appears in output *For any* capacity with a non-empty description, a non-empty list of references, and a non-empty list of certificates, the formatted output SHALL contain the description text, every reference's projects text, and every certificate string. **Validates: Requirements 1.2, 2.2, 3.2** ### Property 3: Section ordering is fixed *For any* capacity (regardless of which fields are empty or populated), the output string SHALL contain the section markers in the order: capacity table first, then "Beschreibung", then "Referenzen", then "Zertifizierungen", then "Next steps" — and each section SHALL be separated by at least one blank line. **Validates: Requirements 4.1, 4.2** ## Error Handling | Scenario | Behaviour | |----------|-----------| | `get_capacity_by_id` returns `None` | Return `"Capacity not found: {capacity_id}"` (existing behaviour, unchanged) | | `get_capacity_description` returns `None` | Display `"Beschreibung: (keine)"` | | `get_capacity_references` returns `[]` | Display `"Referenzen: (keine)"` | | `get_capacity_certificates` returns `[]` | Display `"Zertifizierungen: (keine)"` | | `get_capacity_references` returns a row with empty `partner_name` | Display only the `projects` text (no bold partner prefix) | | Any DB call raises an exception | Let it propagate (existing error handling in the MCP framework catches and reports it) | ## Testing Strategy ### Unit Tests - Test `get_capacity_details` with a mock DB client returning known data for all three new fields → verify output contains expected sections and content. - Test empty-state: description=None, references=[], certificates=[] → verify "(keine)" placeholders appear. - Test edge case: reference with empty `partner_name` → verify only projects text is shown. - Test that capacity-not-found still returns the error message unchanged. ### Property-Based Tests Library: **Hypothesis** (Python) Each property test runs a minimum of 100 iterations. - **Property 1 test**: Generate random capacity IDs and mock DB responses. Verify all three DB methods are called with the correct ID. - Tag: `Feature: capacity-details-enrichment, Property 1: All enrichment data is fetched for any capacity` - **Property 2 test**: Generate random non-empty descriptions (text strategy), random lists of `CapacityReferenceRow` dicts with non-empty `projects` and `partner_name`, and random lists of non-empty certificate strings. Call the formatting logic and assert all generated data appears in the output. - Tag: `Feature: capacity-details-enrichment, Property 2: Non-empty data appears in output` - **Property 3 test**: Generate random combinations of present/absent data (description: str|None, references: list of 0-5 items, certificates: list of 0-5 items). Call the formatting logic and assert section headers appear in the correct order with blank-line separation. - Tag: `Feature: capacity-details-enrichment, Property 3: Section ordering is fixed` ### Test Configuration - Property-based testing library: `hypothesis` (already available in the project's test dependencies) - Minimum iterations: 100 per property (`@settings(max_examples=100)`) - Each test tagged with a comment referencing the design property