Files
Orchestrator/bahn/teamlandkarte-mcp/.kiro/specs/task-name-column/design.md
T
ankn a5f8fb49ab Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
2026-06-30 20:39:52 +02:00

8.1 KiB
Raw Blame History

Design Document: Task Name Column

Overview

This feature adds the name__c database column to the Task model and all task-related queries, enabling users to see and look up tasks by their short human-readable name. The changes span four layers:

  1. Model Add an optional name field to the Task dataclass.
  2. Database Modify SQL queries in TrinoClient to SELECT name__c; add a new get_task_by_name method.
  3. Protocol Extend the DBClient protocol with get_task_by_name.
  4. MCP Tools Include the name in list/detail output; resolve name-based lookups in get_task_details.

Architecture

flowchart TD
    A[MCP Tool: list_open_tasks] -->|calls| B[TrinoClient.get_open_tasks]
    C[MCP Tool: get_task_details] -->|calls| D[TrinoClient.get_task_by_id]
    C -->|calls| E[TrinoClient.get_task_by_name]
    B --> F[(Trino DB: name__c)]
    D --> F
    E --> F
    B --> G[Task dataclass with name field]
    D --> G
    E --> G

The identifier resolution logic in get_task_details will attempt get_task_by_id first. If that returns None, it falls back to get_task_by_name. This keeps backward compatibility for callers passing IDs while enabling name-based lookup without a separate tool.

Components and Interfaces

1. Task Dataclass (models.py)

Add a single field:

@dataclass(frozen=True)
class Task:
    id: str
    name: Optional[str]  # NEW  maps to name__c
    title: str
    description: str
    start_date: Optional[date]
    end_date: Optional[date]
    created_date: datetime
    skills: list[str] = field(default_factory=list)

The field is Optional[str] because the database column can be NULL.

2. DBClient Protocol (database/types.py)

Add one method:

def get_task_by_name(self, name: str) -> Task | None:
    """Fetch one published task by its short name (name__c)."""
    raise NotImplementedError

3. TrinoClient (database/trino_client.py)

SQL changes Every SELECT that builds a Task must include t.name__c. Affected methods:

  • get_open_tasks add t.name__c to both the unlimited and CTE-limited queries.
  • get_task_by_id add t.name__c to the SELECT.

New method get_task_by_name:

def get_task_by_name(self, name: str) -> Optional[Task]:
    """Fetch a single published task by name__c (case-sensitive)."""
    query = """
    SELECT
        t.id,
        t.name__c,
        t.title__c,
        t.description__c,
        t.startdate__c,
        t.enddate__c,
        t.createddate,
        s.skillname__c
    FROM beschaffungstool_kmp_task_latest t
    LEFT JOIN beschaffungstool_kmp_skill_latest s
        ON t.id = s.task__c
    WHERE t.name__c = ? AND t.status__c = ?
    """
    # Same row-assembly logic as get_task_by_id

4. MCP Server (mcp_server.py)

list_open_tasks Add a "Name" column to the output table (between task_id and Title).

get_task_details Change the resolution logic:

@mcp.tool()
async def get_task_details(task_id: str) -> str:
    # 1. Try by ID
    task = db_client.get_task_by_id(task_id)
    # 2. Fallback: try by name
    if task is None:
        task = db_client.get_task_by_name(task_id)
    # 3. Not found
    if task is None:
        return f"Task not found or not published: {task_id}"
    # ... rest unchanged, but include task.name in the summary table

Add "Name" to the summary table header and row.

Data Models

Task Table Schema (relevant columns)

Column Type Nullable Maps to
id VARCHAR No Task.id
name__c VARCHAR Yes Task.name
title__c VARCHAR Yes Task.title
description__c VARCHAR Yes Task.description
startdate__c DATE Yes Task.start_date
enddate__c DATE Yes Task.end_date
createddate TIMESTAMP No Task.created_date

Task Dataclass (after change)

@dataclass(frozen=True)
class Task:
    id: str
    name: Optional[str]
    title: str
    description: str
    start_date: Optional[date]
    end_date: Optional[date]
    created_date: datetime
    skills: list[str] = field(default_factory=list)

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: Task name field preserves database value

For any database row with a name__c value (including NULL), when the row is mapped to a Task object, Task.name must equal the original name__c value (with NULL mapped to None).

Validates: Requirements 1.1, 1.2, 1.3, 1.4

Property 2: Task output contains name

For any Task with a non-None name field, the formatted output string (from both list_open_tasks and get_task_details) must contain that name value as a substring.

Validates: Requirements 2.1, 2.2

Property 3: Name lookup returns correct task

For any task that exists in the database with a given name__c value, calling get_task_by_name with that exact name must return a Task whose name field equals the queried name.

Validates: Requirements 3.2

Property 4: Name resolution equivalence

For any published task with a non-None name, calling get_task_details with the task's name must produce the same task data as calling get_task_details with the task's ID.

Validates: Requirements 3.4

Property 5: Unknown identifier returns not-found

For any string that is neither a valid published task ID nor a valid published task name, get_task_details must return a not-found message.

Validates: Requirements 3.3, 3.5

Error Handling

Scenario Behavior
name__c is NULL in DB Task.name is set to None; output displays empty or omits the name cell
get_task_by_name receives empty string Returns None (no match)
get_task_details identifier matches neither ID nor name Returns "Task not found or not published: {identifier}"
Multiple tasks share the same name__c (data quality issue) get_task_by_name returns the first match (ORDER BY createddate DESC)

The resolution order in get_task_details (ID first, then name) ensures that if a name happens to look like an ID, the ID lookup takes precedence. This avoids ambiguity.

Testing Strategy

Unit Tests

  • Verify Task dataclass construction with name=None and name="ABC-123".
  • Verify list_open_tasks output table includes a "Name" column.
  • Verify get_task_details output table includes the name value.
  • Verify get_task_details falls back to get_task_by_name when ID lookup returns None.
  • Verify get_task_by_name returns None for empty string input.

Property-Based Tests

Use Hypothesis (Python PBT library) with a minimum of 100 iterations per property.

Each test must be tagged with a comment referencing the design property:

# Feature: task-name-column, Property 1: Task name field preserves database value

Property 1 Generate random (name__c_value | None) values, construct Task objects via the row-mapping helper, assert task.name == input_value.

Property 2 Generate random Task objects with non-None names (using st.text(min_size=1)), format them through the output functions, assert the name string appears in the output.

Property 3 Generate a random set of tasks with unique names, mock the DB cursor to return those rows, call get_task_by_name(name), assert the returned task's name matches.

Property 4 Generate a task with a non-None name, mock both get_task_by_id and get_task_by_name to return the same task, call get_task_details with the name and with the ID, assert both outputs are identical.

Property 5 Generate random strings, filter out any that match known task IDs or names in the mock data, call get_task_details, assert the output contains "not found".

Test Configuration

from hypothesis import given, settings, strategies as st

@settings(max_examples=100)