From 2255b65ef6002071bedec882e26a396b79971844 Mon Sep 17 00:00:00 2001 From: Bill Ballou Date: Tue, 6 Jan 2026 22:42:10 -0500 Subject: [PATCH] feat: add data_date to project summary response Include the schedule data date (last_recalc_date from XER) in the get_project_summary tool response. This shows when the schedule was last calculated in P6. Changes: - Add last_recalc_date column to projects table schema - Parse last_recalc_date in PROJECT table handler - Include last_recalc_date in db loader - Return data_date field in get_project_summary query - Update contract test to verify data_date presence --- specs/001-schedule-tools/spec.md | 5 +++-- src/xer_mcp/db/loader.py | 5 +++-- src/xer_mcp/db/queries.py | 3 ++- src/xer_mcp/db/schema.py | 1 + src/xer_mcp/parser/table_handlers/project.py | 1 + tests/contract/test_get_project_summary.py | 1 + 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/specs/001-schedule-tools/spec.md b/specs/001-schedule-tools/spec.md index d337d44..5b6c249 100644 --- a/specs/001-schedule-tools/spec.md +++ b/specs/001-schedule-tools/spec.md @@ -74,7 +74,7 @@ As an AI assistant user, I want to get a high-level summary of the project sched **Acceptance Scenarios**: -1. **Given** an XER file is loaded, **When** I request the project summary, **Then** I receive project name, start date, finish date, and total activity count +1. **Given** an XER file is loaded, **When** I request the project summary, **Then** I receive project name, data date, start date, finish date, and total activity count 2. **Given** an XER file with milestones, **When** I request milestones, **Then** I receive a list of milestone activities with their target dates 3. **Given** an XER file is loaded, **When** I request the critical path, **Then** I receive the sequence of activities that determine the project end date 4. **Given** no XER file is loaded, **When** I request project summary, milestones, or critical path, **Then** I receive a clear error message indicating no file is loaded @@ -98,7 +98,7 @@ As an AI assistant user, I want to get a high-level summary of the project sched - **FR-003**: System MUST expose an MCP tool to list all activities with filtering options (by date range, by WBS, by activity type) - **FR-004**: System MUST expose an MCP tool to retrieve detailed information for a specific activity by ID - **FR-005**: System MUST expose an MCP tool to query predecessor and successor relationships for any activity -- **FR-006**: System MUST expose an MCP tool to retrieve project summary information (name, dates, activity count) +- **FR-006**: System MUST expose an MCP tool to retrieve project summary information (name, data date, plan dates, activity count) - **FR-007**: System MUST expose an MCP tool to list milestone activities - **FR-008**: System MUST expose an MCP tool to identify the critical path - **FR-009**: System MUST return structured data that AI assistants can process and present to users @@ -139,6 +139,7 @@ As an AI assistant user, I want to get a high-level summary of the project sched - Q: How should multi-project XER files be handled? → A: Require explicit project selection if multiple exist - Q: Should calendar data be exposed as queryable? → A: Internal use only (not exposed as queryable) - Q: What happens when any query tool is called without a file loaded? → A: Return informative error indicating no XER file is loaded; applies to all tools except load_xer +- Q: Should project summary include the data date? → A: Yes, include the data date (schedule "as-of" date) in project summary response ## Assumptions diff --git a/src/xer_mcp/db/loader.py b/src/xer_mcp/db/loader.py index 4388ba2..a89b902 100644 --- a/src/xer_mcp/db/loader.py +++ b/src/xer_mcp/db/loader.py @@ -25,14 +25,15 @@ def load_parsed_data(parsed: ParsedXer, project_id: str) -> None: # Insert project cur.execute( """ - INSERT INTO projects (proj_id, proj_short_name, plan_start_date, plan_end_date) - VALUES (?, ?, ?, ?) + INSERT INTO projects (proj_id, proj_short_name, plan_start_date, plan_end_date, last_recalc_date) + VALUES (?, ?, ?, ?, ?) """, ( project["proj_id"], project["proj_short_name"], project["plan_start_date"], project["plan_end_date"], + project.get("last_recalc_date"), ), ) diff --git a/src/xer_mcp/db/queries.py b/src/xer_mcp/db/queries.py index bfc7a76..61a2790 100644 --- a/src/xer_mcp/db/queries.py +++ b/src/xer_mcp/db/queries.py @@ -290,7 +290,7 @@ def get_project_summary(project_id: str) -> dict | None: with db.cursor() as cur: cur.execute( """ - SELECT proj_id, proj_short_name, plan_start_date, plan_end_date + SELECT proj_id, proj_short_name, plan_start_date, plan_end_date, last_recalc_date FROM projects WHERE proj_id = ? """, @@ -319,6 +319,7 @@ def get_project_summary(project_id: str) -> dict | None: return { "project_id": project_row[0], "project_name": project_row[1], + "data_date": project_row[4], "plan_start_date": project_row[2], "plan_end_date": project_row[3], "activity_count": activity_count, diff --git a/src/xer_mcp/db/schema.py b/src/xer_mcp/db/schema.py index 892c17e..c4b9c02 100644 --- a/src/xer_mcp/db/schema.py +++ b/src/xer_mcp/db/schema.py @@ -7,6 +7,7 @@ CREATE TABLE IF NOT EXISTS projects ( proj_short_name TEXT NOT NULL, plan_start_date TEXT, plan_end_date TEXT, + last_recalc_date TEXT, loaded_at TEXT NOT NULL DEFAULT (datetime('now')) ); diff --git a/src/xer_mcp/parser/table_handlers/project.py b/src/xer_mcp/parser/table_handlers/project.py index eb32fd8..2a7aa22 100644 --- a/src/xer_mcp/parser/table_handlers/project.py +++ b/src/xer_mcp/parser/table_handlers/project.py @@ -35,4 +35,5 @@ class ProjectHandler(TableHandler): "proj_short_name": data.get("proj_short_name", ""), "plan_start_date": convert_date(data.get("plan_start_date")), "plan_end_date": convert_date(data.get("plan_end_date")), + "last_recalc_date": convert_date(data.get("last_recalc_date")), } diff --git a/tests/contract/test_get_project_summary.py b/tests/contract/test_get_project_summary.py index d448c38..4163af7 100644 --- a/tests/contract/test_get_project_summary.py +++ b/tests/contract/test_get_project_summary.py @@ -30,6 +30,7 @@ class TestGetProjectSummaryContract: result = await get_project_summary() assert "project_name" in result + assert "data_date" in result assert "plan_start_date" in result assert "plan_end_date" in result assert "activity_count" in result