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.
8.1 KiB
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:
- Model – Add an optional
namefield to theTaskdataclass. - Database – Modify SQL queries in
TrinoClientto SELECTname__c; add a newget_task_by_namemethod. - Protocol – Extend the
DBClientprotocol withget_task_by_name. - 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– addt.name__cto both the unlimited and CTE-limited queries.get_task_by_id– addt.name__cto 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
Taskdataclass construction withname=Noneandname="ABC-123". - Verify
list_open_tasksoutput table includes a "Name" column. - Verify
get_task_detailsoutput table includes the name value. - Verify
get_task_detailsfalls back toget_task_by_namewhen ID lookup returns None. - Verify
get_task_by_namereturns 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)