feat: add milestone_type field to distinguish start/finish milestones
Add milestone_type field to milestone queries that indicates whether
a milestone is a start milestone ('start') or finish milestone ('finish').
Changes:
- Add milestone_type column to activities table schema
- Parse milestone_type from XER TASK table (MS_Start/MS_Finish)
- Include milestone_type in list_milestones response
- Update contract tests for milestone_type field
- Update specs, contracts, and documentation
The milestone_type is determined by:
1. Explicit milestone_type field in XER (MS_Start -> 'start', MS_Finish -> 'finish')
2. Derived from task_type (TT_Mile -> 'start', TT_FinMile -> 'finish')
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -38,9 +38,12 @@ htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
|
||||
# XER files (may contain sensitive project data)
|
||||
*.xer
|
||||
# Schedule files
|
||||
schedules/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
|
||||
.claude/skills/
|
||||
|
||||
@@ -4,6 +4,8 @@ Auto-generated from all feature plans. Last updated: 2026-01-06
|
||||
|
||||
## Active Technologies
|
||||
- SQLite in-memory database (001-schedule-tools)
|
||||
- Python 3.14 + mcp>=1.0.0 (MCP SDK), sqlite3 (stdlib) (001-schedule-tools)
|
||||
- In-memory SQLite database (populated from XER files at runtime) (001-schedule-tools)
|
||||
|
||||
- Python 3.14 + mcp (MCP SDK), sqlite3 (stdlib) (001-schedule-tools)
|
||||
|
||||
@@ -23,6 +25,7 @@ cd src [ONLY COMMANDS FOR ACTIVE TECHNOLOGIES][ONLY COMMANDS FOR ACTIVE TECHNOLO
|
||||
Python 3.14: Follow standard conventions
|
||||
|
||||
## Recent Changes
|
||||
- 001-schedule-tools: Added Python 3.14 + mcp>=1.0.0 (MCP SDK), sqlite3 (stdlib)
|
||||
- 001-schedule-tools: Added Python 3.14 + mcp (MCP SDK), sqlite3 (stdlib)
|
||||
|
||||
- 001-schedule-tools: Added Python 3.14 + mcp (MCP SDK), sqlite3 (stdlib)
|
||||
|
||||
@@ -301,7 +301,8 @@
|
||||
"target_start_date": { "type": "string", "format": "date-time" },
|
||||
"target_end_date": { "type": "string", "format": "date-time" },
|
||||
"status_code": { "type": "string" },
|
||||
"driving_path_flag": { "type": "boolean" }
|
||||
"driving_path_flag": { "type": "boolean" },
|
||||
"milestone_type": { "type": "string", "enum": ["start", "finish", null], "description": "Type of milestone: 'start' for start milestones, 'finish' for finish milestones, null for non-milestones" }
|
||||
}
|
||||
},
|
||||
"ActivityDetail": {
|
||||
|
||||
@@ -54,6 +54,7 @@ A unit of work in the schedule.
|
||||
| task_code | string | Yes | User-visible activity code |
|
||||
| task_name | string | Yes | Activity description |
|
||||
| task_type | enum | Yes | TT_Task, TT_Mile, TT_LOE, TT_WBS, TT_Rsrc |
|
||||
| milestone_type | enum | No | 'start' for start milestones, 'finish' for finish milestones, null for non-milestones |
|
||||
| target_start_date | datetime | No | Planned start |
|
||||
| target_end_date | datetime | No | Planned finish |
|
||||
| early_start_date | datetime | No | Calculated early start (for driving computation) |
|
||||
|
||||
@@ -1,47 +1,38 @@
|
||||
# Implementation Plan: Add Driving Flag to Relationships
|
||||
# Implementation Plan: Project Schedule Tools
|
||||
|
||||
**Branch**: `001-schedule-tools` | **Date**: 2026-01-06 | **Spec**: [spec.md](./spec.md)
|
||||
**Branch**: `001-schedule-tools` | **Date**: 2026-01-08 | **Spec**: [spec.md](./spec.md)
|
||||
**Input**: Feature specification from `/specs/001-schedule-tools/spec.md`
|
||||
|
||||
## Summary
|
||||
|
||||
Extend the existing XER MCP Server implementation to include the "driving" flag in all relationship query responses. This requires updating the database schema, XER parser, and query functions to extract and return the driving relationship indicator from Primavera P6 schedule data.
|
||||
Build an MCP server that provides 9 tools for querying Primavera P6 XER schedule data. The server parses XER files into an in-memory SQLite database and exposes tools for loading files, querying activities, relationships, milestones, critical path, and project summary. Uses Python 3.14 with the MCP SDK and follows TDD practices per the project constitution.
|
||||
|
||||
## Technical Context
|
||||
|
||||
**Language/Version**: Python 3.14
|
||||
**Primary Dependencies**: mcp (MCP SDK), sqlite3 (stdlib)
|
||||
**Storage**: SQLite in-memory database
|
||||
**Testing**: pytest, pytest-asyncio
|
||||
**Target Platform**: Linux server (local MCP server)
|
||||
**Project Type**: single
|
||||
**Performance Goals**: Query response < 1 second, load < 5 seconds for 10k activities
|
||||
**Constraints**: Single-user operation, in-memory storage for loaded XER data
|
||||
**Scale/Scope**: Files up to 50,000 activities
|
||||
**Primary Dependencies**: mcp>=1.0.0 (MCP SDK), sqlite3 (stdlib)
|
||||
**Storage**: In-memory SQLite database (populated from XER files at runtime)
|
||||
**Testing**: pytest>=8.0.0, pytest-asyncio>=0.24.0
|
||||
**Target Platform**: Local server (Linux/macOS/Windows with file system access)
|
||||
**Project Type**: Single project
|
||||
**Performance Goals**: Load + query XER files ≤5 seconds for 10,000 activities; query response <1 second
|
||||
**Constraints**: Memory sufficient for 50,000 activities; single-user operation
|
||||
**Scale/Scope**: MCP server with 9 tools; handles typical P6 project files (up to 50,000 activities)
|
||||
|
||||
## Constitution Check
|
||||
|
||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||
|
||||
| Principle | Status | Evidence/Notes |
|
||||
|-----------|--------|----------------|
|
||||
| I. Test-First Development | ✅ PASS | TDD required - write failing tests first for driving flag in relationship responses |
|
||||
| II. Extensibility Architecture | ✅ PASS | Current design already separates parser → db → query → tool layers |
|
||||
| III. MCP Protocol Compliance | ✅ PASS | Tool schemas already JSON Schema compliant; no schema changes needed |
|
||||
| IV. XER Format Fidelity | ✅ PASS | Must parse `driving_flag` from TASKPRED table in XER files |
|
||||
| V. Semantic Versioning | ✅ PASS | Adding new field to response is backward-compatible (MINOR) |
|
||||
| Principle | Requirement | Status | Notes |
|
||||
|-----------|-------------|--------|-------|
|
||||
| **I. Test-First Development** | TDD mandatory; tests fail before implementation | ✅ PASS | Contract tests for all 9 MCP tools; integration tests for XER parsing |
|
||||
| **II. Extensibility Architecture** | Core parsing separate from MCP transport; pluggable handlers | ✅ PASS | Table handlers are pluggable (`parser/table_handlers/`); tools are modular (`tools/`) |
|
||||
| **III. MCP Protocol Compliance** | Complete JSON schemas; MCP error format; compliant transport | ✅ PASS | Using official MCP SDK; tool definitions include JSON schemas |
|
||||
| **IV. XER Format Fidelity** | No data loss; preserve precision; handle all standard tables | ✅ PASS | Parsing TASK, TASKPRED, PROJECT, PROJWBS, CALENDAR; dates preserve precision |
|
||||
| **V. Semantic Versioning** | SemVer for releases; breaking changes documented | ✅ PASS | Version 0.1.0; initial development phase |
|
||||
| **Technical Standards** | Python 3.14; type hints; ruff formatting | ✅ PASS | pyproject.toml configured for Python 3.14, ruff, pytest |
|
||||
|
||||
**Pre-design Status**: All gates PASS
|
||||
|
||||
**Post-design Status** (re-evaluated after Phase 1):
|
||||
|
||||
| Principle | Status | Evidence/Notes |
|
||||
|-----------|--------|----------------|
|
||||
| I. Test-First Development | ✅ PASS | Tests will verify: (1) early dates parsed from TASK, (2) driving computed correctly, (3) driving included in all relationship responses |
|
||||
| II. Extensibility Architecture | ✅ PASS | Driving computation is isolated in query layer; no changes to parser or model structure |
|
||||
| III. MCP Protocol Compliance | ✅ PASS | Response schema updated in contracts/mcp-tools.json |
|
||||
| IV. XER Format Fidelity | ✅ PASS | Research confirmed: driving is computed from schedule dates (matching P6 behavior), not stored in XER |
|
||||
| V. Semantic Versioning | ✅ PASS | Adding `driving` field is backward-compatible (MINOR version bump)
|
||||
**Gate Result**: PASS - All constitution principles satisfied. Proceed to Phase 0.
|
||||
|
||||
## Project Structure
|
||||
|
||||
@@ -49,53 +40,116 @@ Extend the existing XER MCP Server implementation to include the "driving" flag
|
||||
|
||||
```text
|
||||
specs/001-schedule-tools/
|
||||
├── plan.md # This file
|
||||
├── research.md # Phase 0 output
|
||||
├── data-model.md # Phase 1 output
|
||||
├── quickstart.md # Phase 1 output
|
||||
├── contracts/ # Phase 1 output
|
||||
└── tasks.md # Phase 2 output (/speckit.tasks)
|
||||
├── spec.md # Feature specification
|
||||
├── plan.md # This file (/speckit.plan command output)
|
||||
├── research.md # Phase 0 output (/speckit.plan command)
|
||||
├── data-model.md # Phase 1 output (/speckit.plan command)
|
||||
├── quickstart.md # Phase 1 output (/speckit.plan command)
|
||||
├── contracts/ # Phase 1 output (/speckit.plan command)
|
||||
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
|
||||
```
|
||||
|
||||
### Source Code (repository root)
|
||||
|
||||
```text
|
||||
src/
|
||||
├── xer_mcp/
|
||||
│ ├── models/
|
||||
│ │ └── relationship.py # Add driving field to dataclass
|
||||
│ ├── parser/
|
||||
│ │ └── table_handlers/
|
||||
│ │ └── taskpred.py # Parse driving_flag from XER
|
||||
│ ├── db/
|
||||
│ │ ├── schema.py # Add driving column to relationships table
|
||||
│ │ ├── loader.py # Store driving flag when loading
|
||||
│ │ └── queries.py # Return driving in relationship queries
|
||||
│ └── tools/
|
||||
│ ├── list_relationships.py # Response already uses query result
|
||||
│ ├── get_predecessors.py # Response already uses query result
|
||||
│ └── get_successors.py # Response already uses query result
|
||||
src/xer_mcp/
|
||||
├── __init__.py
|
||||
├── __main__.py # Entry point
|
||||
├── server.py # MCP server setup and tool registration
|
||||
├── errors.py # Error types
|
||||
├── models/ # Data models (dataclasses)
|
||||
│ ├── __init__.py
|
||||
│ ├── project.py
|
||||
│ ├── activity.py
|
||||
│ ├── relationship.py
|
||||
│ ├── wbs.py
|
||||
│ ├── calendar.py
|
||||
│ └── pagination.py
|
||||
├── parser/ # XER file parsing
|
||||
│ ├── __init__.py
|
||||
│ ├── xer_parser.py # Main parser
|
||||
│ └── table_handlers/ # Pluggable table handlers
|
||||
│ ├── __init__.py
|
||||
│ ├── base.py
|
||||
│ ├── project.py
|
||||
│ ├── task.py
|
||||
│ ├── taskpred.py
|
||||
│ ├── projwbs.py
|
||||
│ └── calendar.py
|
||||
├── db/ # SQLite database layer
|
||||
│ ├── __init__.py
|
||||
│ ├── schema.py # Table definitions
|
||||
│ ├── loader.py # Data loading
|
||||
│ └── queries.py # Query functions
|
||||
└── tools/ # MCP tool implementations
|
||||
├── __init__.py
|
||||
├── load_xer.py
|
||||
├── list_activities.py
|
||||
├── get_activity.py
|
||||
├── list_relationships.py
|
||||
├── get_predecessors.py
|
||||
├── get_successors.py
|
||||
├── get_project_summary.py
|
||||
├── list_milestones.py
|
||||
└── get_critical_path.py
|
||||
|
||||
tests/
|
||||
├── contract/
|
||||
│ ├── test_list_relationships.py # Verify driving flag in response
|
||||
│ ├── test_get_predecessors.py # Verify driving flag in response
|
||||
│ └── test_get_successors.py # Verify driving flag in response
|
||||
├── integration/
|
||||
│ └── test_xer_parsing.py # Verify driving flag parsed from XER
|
||||
└── unit/
|
||||
├── test_table_handlers.py # Test TASKPRED handler parses driving
|
||||
└── test_db_queries.py # Test queries return driving flag
|
||||
├── __init__.py
|
||||
├── conftest.py # Shared fixtures
|
||||
├── contract/ # MCP tool contract tests
|
||||
│ ├── __init__.py
|
||||
│ ├── test_load_xer.py
|
||||
│ ├── test_list_activities.py
|
||||
│ ├── test_get_activity.py
|
||||
│ ├── test_list_relationships.py
|
||||
│ ├── test_get_predecessors.py
|
||||
│ ├── test_get_successors.py
|
||||
│ ├── test_get_project_summary.py
|
||||
│ ├── test_list_milestones.py
|
||||
│ └── test_get_critical_path.py
|
||||
├── integration/ # XER parsing integration tests
|
||||
│ ├── __init__.py
|
||||
│ └── test_xer_parsing.py
|
||||
└── unit/ # Unit tests
|
||||
├── __init__.py
|
||||
├── test_parser.py
|
||||
├── test_table_handlers.py
|
||||
└── test_db_queries.py
|
||||
```
|
||||
|
||||
**Structure Decision**: Single project structure (existing). No new directories needed.
|
||||
**Structure Decision**: Single project structure with domain-specific organization: `models/` for data structures, `parser/` for XER file handling with pluggable table handlers, `db/` for SQLite storage layer, and `tools/` for MCP tool implementations. Tests follow contract/integration/unit hierarchy per constitution requirements.
|
||||
|
||||
## Complexity Tracking
|
||||
|
||||
> No constitution violations. Implementation is a focused enhancement to existing architecture.
|
||||
> **Fill ONLY if Constitution Check has violations that must be justified**
|
||||
|
||||
| Item | Assessment |
|
||||
|------|------------|
|
||||
| Scope | Small - single field addition across 4 layers |
|
||||
| Risk | Low - additive change, no behavior modification |
|
||||
| Testing | Contract tests + unit tests for each layer |
|
||||
No violations. All constitution principles satisfied.
|
||||
|
||||
## Post-Design Constitution Re-Check
|
||||
|
||||
*Re-evaluation after Phase 1 design artifacts are complete.*
|
||||
|
||||
| Principle | Status | Verification |
|
||||
|-----------|--------|--------------|
|
||||
| **I. Test-First Development** | ✅ PASS | Contract tests defined in `tests/contract/` for all 9 tools; integration tests in `tests/integration/` |
|
||||
| **II. Extensibility Architecture** | ✅ PASS | Table handlers pluggable via registry pattern; tools are modular functions; schema separates concerns |
|
||||
| **III. MCP Protocol Compliance** | ✅ PASS | `contracts/mcp-tools.json` defines complete JSON schemas for all tools with input/output schemas |
|
||||
| **IV. XER Format Fidelity** | ✅ PASS | Data model preserves all date precision; handles all 5 standard tables; driving flag computed accurately |
|
||||
| **V. Semantic Versioning** | ✅ PASS | Version 0.1.0 in contract schema; following SemVer |
|
||||
| **Technical Standards** | ✅ PASS | Type hints throughout models; ruff configured; pytest async mode enabled |
|
||||
|
||||
**Post-Design Gate Result**: PASS - Design artifacts align with constitution. Ready for task generation.
|
||||
|
||||
## Generated Artifacts
|
||||
|
||||
| Artifact | Path | Description |
|
||||
|----------|------|-------------|
|
||||
| Research | `specs/001-schedule-tools/research.md` | Technology decisions, XER format analysis, driving flag computation |
|
||||
| Data Model | `specs/001-schedule-tools/data-model.md` | Entity definitions, SQLite schema, validation rules |
|
||||
| Contracts | `specs/001-schedule-tools/contracts/mcp-tools.json` | MCP tool JSON schemas (input/output) |
|
||||
| Quickstart | `specs/001-schedule-tools/quickstart.md` | Usage guide with examples |
|
||||
| Agent Context | `CLAUDE.md` | Updated with Python 3.14, MCP SDK, SQLite |
|
||||
|
||||
## Next Steps
|
||||
|
||||
Run `/speckit.tasks` to generate implementation tasks from this plan.
|
||||
|
||||
@@ -226,6 +226,34 @@ Use the get_critical_path tool
|
||||
Use the list_milestones tool
|
||||
```
|
||||
|
||||
**Response**:
|
||||
```json
|
||||
{
|
||||
"milestones": [
|
||||
{
|
||||
"task_id": "M001",
|
||||
"task_code": "MS-START",
|
||||
"task_name": "Project Start",
|
||||
"target_start_date": "2026-01-15T08:00:00",
|
||||
"target_end_date": "2026-01-15T08:00:00",
|
||||
"status_code": "TK_Complete",
|
||||
"milestone_type": "start"
|
||||
},
|
||||
{
|
||||
"task_id": "M025",
|
||||
"task_code": "MS-END",
|
||||
"task_name": "Project Complete",
|
||||
"target_start_date": "2026-06-30T17:00:00",
|
||||
"target_end_date": "2026-06-30T17:00:00",
|
||||
"status_code": "TK_NotStart",
|
||||
"milestone_type": "finish"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The `milestone_type` field indicates whether the milestone is a Start Milestone (`"start"`) or a Finish Milestone (`"finish"`).
|
||||
|
||||
## Pagination
|
||||
|
||||
All list operations support pagination:
|
||||
|
||||
@@ -75,7 +75,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, 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
|
||||
2. **Given** an XER file with milestones, **When** I request milestones, **Then** I receive a list of all milestone activities (both Start Milestones and Finish Milestones) with their target dates and milestone type
|
||||
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
|
||||
|
||||
@@ -99,7 +99,7 @@ As an AI assistant user, I want to get a high-level summary of the project sched
|
||||
- **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, including relationship type, lag value, and driving flag
|
||||
- **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-007**: System MUST expose an MCP tool to list milestone activities, including both Start Milestones (TT_Mile with task_type='TT_Mile' and milestone_type='start') and Finish Milestones (task_type='TT_Mile' and milestone_type='finish')
|
||||
- **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
|
||||
- **FR-010**: System MUST provide clear, actionable error messages when operations fail
|
||||
@@ -112,7 +112,7 @@ As an AI assistant user, I want to get a high-level summary of the project sched
|
||||
### Key Entities
|
||||
|
||||
- **Project**: The top-level container representing a P6 project with name, ID, start/finish dates, and calendar assignments
|
||||
- **Activity**: A unit of work with ID, name, type (task/milestone/LOE), planned dates, actual dates, duration, and status
|
||||
- **Activity**: A unit of work with ID, name, type (task/milestone/LOE), milestone_type (start/finish for milestones, null otherwise), planned dates, actual dates, duration, and status
|
||||
- **Relationship**: A dependency link between two activities with type (FS/SS/FF/SF), lag value, and driving flag
|
||||
- **WBS (Work Breakdown Structure)**: Hierarchical organization of activities with ID, name, parent reference, and level
|
||||
- **Calendar**: Work schedule definition that determines working days and hours for activities (internal use only; not exposed as queryable entity)
|
||||
@@ -141,6 +141,7 @@ As an AI assistant user, I want to get a high-level summary of the project sched
|
||||
- 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
|
||||
- Q: Should relationship queries return the driving flag? → A: Yes, include the driving property in all relationship responses (predecessors, successors, list relationships)
|
||||
- Q: Which milestone types should be included in milestone queries? → A: Include both Start Milestones and Finish Milestones
|
||||
|
||||
## Assumptions
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
# Tasks: Add Driving Flag to Relationships
|
||||
# Tasks: Add Milestone Type to List Milestones Tool
|
||||
|
||||
**Input**: Design documents from `/specs/001-schedule-tools/`
|
||||
**Prerequisites**: plan.md (required), spec.md (required), research.md, data-model.md, contracts/
|
||||
|
||||
**Tests**: TDD is mandated by constitution - tests MUST be written and fail before implementation.
|
||||
|
||||
**Scope**: Enhancement to existing implementation - add computed `driving` flag to relationship query responses.
|
||||
**Scope**: Enhancement to existing implementation - add `milestone_type` (start/finish) to milestone query responses per spec clarification.
|
||||
|
||||
**Change Summary**: The spec was updated to require that the `list_milestones` tool return both Start Milestones and Finish Milestones with their `milestone_type` field (start/finish for milestones, null otherwise).
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
|
||||
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||
- **[Story]**: Which user story this task belongs to (US3 = Query Activity Relationships)
|
||||
- **[Story]**: Which user story this task belongs to (US4 = Query Project Summary)
|
||||
- Include exact file paths in descriptions
|
||||
|
||||
## Path Conventions
|
||||
@@ -23,65 +25,70 @@
|
||||
|
||||
## Phase 1: Setup (Schema Enhancement)
|
||||
|
||||
**Purpose**: Add early date columns needed for driving flag computation
|
||||
**Purpose**: Add milestone_type column needed for distinguishing start vs finish milestones
|
||||
|
||||
- [x] T001 Update activities table schema to add early_start_date and early_end_date columns in src/xer_mcp/db/schema.py
|
||||
- [x] T001 Update activities table schema to add milestone_type column in src/xer_mcp/db/schema.py
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational (Parser Enhancement)
|
||||
|
||||
**Purpose**: Parse early dates from TASK table and store in database
|
||||
**Purpose**: Parse milestone_type from TASK table and store in database
|
||||
|
||||
**⚠️ CRITICAL**: Must complete before relationship queries can compute driving flag
|
||||
**⚠️ CRITICAL**: Must complete before milestone queries can return the type
|
||||
|
||||
### Tests
|
||||
|
||||
- [x] T002 [P] Unit test for TASK handler parsing early dates in tests/unit/test_table_handlers.py (verify early_start_date, early_end_date extracted)
|
||||
- [x] T002 [P] Unit test for TASK handler parsing milestone_type in tests/unit/test_table_handlers.py (verify milestone_type extracted for TT_Mile activities)
|
||||
|
||||
### Implementation
|
||||
|
||||
- [x] T003 Update TASK table handler to parse early_start_date and early_end_date in src/xer_mcp/parser/table_handlers/task.py
|
||||
- [x] T004 Update database loader to store early dates when inserting activities in src/xer_mcp/db/loader.py
|
||||
- [x] T003 Update TASK table handler to parse milestone_type field in src/xer_mcp/parser/table_handlers/task.py
|
||||
- [x] T004 Update database loader to store milestone_type when inserting activities in src/xer_mcp/db/loader.py
|
||||
|
||||
**Checkpoint**: Early dates are now parsed and stored - driving computation can begin
|
||||
**Checkpoint**: milestone_type is now parsed and stored - queries can begin
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 3 - Add Driving Flag to Relationships (Priority: P2) 🎯 FOCUS
|
||||
## Phase 3: User Story 4 - Add Milestone Type to Queries (Priority: P3)
|
||||
|
||||
**Goal**: Compute and return `driving` flag in all relationship query responses
|
||||
**Goal**: Return `milestone_type` field in milestone query responses
|
||||
|
||||
**Independent Test**: Load XER file, query relationships, verify driving flag is present and correctly computed
|
||||
**Independent Test**: Load XER file, query milestones, verify milestone_type is present and correctly identifies start vs finish milestones
|
||||
|
||||
### Tests for User Story 3
|
||||
### Tests for User Story 4
|
||||
|
||||
> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**
|
||||
|
||||
- [x] T005 [P] [US3] Update contract test to verify driving flag in list_relationships response in tests/contract/test_list_relationships.py
|
||||
- [x] T006 [P] [US3] Update contract test to verify driving flag in get_predecessors response in tests/contract/test_get_predecessors.py
|
||||
- [x] T007 [P] [US3] Update contract test to verify driving flag in get_successors response in tests/contract/test_get_successors.py
|
||||
- [x] T008 [P] [US3] Unit test for driving flag computation logic in tests/unit/test_db_queries.py (test is_driving_relationship function)
|
||||
- [x] T005 [P] [US4] Update contract test to verify milestone_type field in list_milestones response in tests/contract/test_list_milestones.py
|
||||
- [x] T006 [P] [US4] Add test case verifying both start and finish milestones are returned with correct types in tests/contract/test_list_milestones.py
|
||||
|
||||
### Implementation for User Story 3
|
||||
### Implementation for User Story 4
|
||||
|
||||
- [x] T009 [US3] Create is_driving_relationship helper function in src/xer_mcp/db/queries.py (implements date comparison logic from research.md)
|
||||
- [x] T010 [US3] Update query_relationships function to JOIN on activity early dates and compute driving flag in src/xer_mcp/db/queries.py
|
||||
- [x] T011 [US3] Update get_predecessors function to JOIN on activity early dates and compute driving flag in src/xer_mcp/db/queries.py
|
||||
- [x] T012 [US3] Update get_successors function to JOIN on activity early dates and compute driving flag in src/xer_mcp/db/queries.py
|
||||
- [x] T007 [US4] Update query_milestones function to SELECT and return milestone_type in src/xer_mcp/db/queries.py
|
||||
- [x] T008 [US4] Update list_milestones tool docstring to document milestone_type field in src/xer_mcp/tools/list_milestones.py
|
||||
|
||||
**Checkpoint**: Driving flag now included in all relationship responses
|
||||
**Checkpoint**: milestone_type now included in milestone responses
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Polish & Validation
|
||||
## Phase 4: Documentation & Contracts
|
||||
|
||||
**Purpose**: Verify integration and documentation alignment
|
||||
**Purpose**: Update contracts and documentation to reflect the new field
|
||||
|
||||
- [x] T013 Integration test validating driving flag against sample XER data in tests/integration/test_xer_parsing.py
|
||||
- [x] T014 Run all tests to verify no regressions: pytest tests/
|
||||
- [x] T015 Run quickstart.md validation - verify driving flag examples match actual output
|
||||
- [x] T016 Run ruff check and fix any linting issues: ruff check src/
|
||||
- [x] T009 [P] Update ActivitySummary schema to include optional milestone_type field in specs/001-schedule-tools/contracts/mcp-tools.json
|
||||
- [x] T010 [P] Update Activity entity in data-model.md to include milestone_type field in specs/001-schedule-tools/data-model.md
|
||||
- [x] T011 Update quickstart.md milestone example to show milestone_type in output in specs/001-schedule-tools/quickstart.md
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Polish & Validation
|
||||
|
||||
**Purpose**: Verify integration and ensure no regressions
|
||||
|
||||
- [x] T012 Run all tests to verify no regressions: uv run pytest tests/
|
||||
- [x] T013 Run ruff check and fix any linting issues: uv run ruff check src/
|
||||
- [x] T014 Validate list_milestones output matches updated contract schema
|
||||
|
||||
---
|
||||
|
||||
@@ -99,107 +106,106 @@ Phase 2 (Parser)
|
||||
Phase 3 (Queries) ← MAIN WORK
|
||||
│
|
||||
▼
|
||||
Phase 4 (Polish)
|
||||
```
|
||||
|
||||
### Task Dependencies Within Phase 3
|
||||
|
||||
```
|
||||
T005, T006, T007, T008 (Tests) ← Write first, verify FAIL
|
||||
Phase 4 (Docs) ← Can run in parallel with Phase 3
|
||||
│
|
||||
▼
|
||||
T009 (Helper function)
|
||||
Phase 5 (Polish)
|
||||
```
|
||||
|
||||
### Task Dependencies Within Phases
|
||||
|
||||
```
|
||||
T001 (Schema)
|
||||
│
|
||||
├── T010 (query_relationships)
|
||||
├── T011 (get_predecessors)
|
||||
└── T012 (get_successors)
|
||||
├── T002 (Parser test)
|
||||
│
|
||||
▼
|
||||
All tests now PASS
|
||||
T003 (Parser impl) ← depends on T001
|
||||
│
|
||||
▼
|
||||
T004 (Loader) ← depends on T003
|
||||
│
|
||||
├── T005, T006 (Contract tests)
|
||||
│
|
||||
▼
|
||||
T007 (Query impl) ← depends on T004
|
||||
│
|
||||
▼
|
||||
T008 (Tool docs)
|
||||
```
|
||||
|
||||
### Parallel Opportunities
|
||||
|
||||
**Phase 2 Tests + Phase 3 Tests** (can write all tests in parallel):
|
||||
```bash
|
||||
# All test tasks can run in parallel:
|
||||
Task T002: "Unit test for TASK handler parsing early dates"
|
||||
Task T005: "Update contract test for list_relationships"
|
||||
Task T006: "Update contract test for get_predecessors"
|
||||
Task T007: "Update contract test for get_successors"
|
||||
Task T008: "Unit test for driving flag computation"
|
||||
Task T002: "Unit test for TASK handler parsing milestone_type"
|
||||
Task T005: "Update contract test for list_milestones"
|
||||
Task T006: "Add test for start/finish milestone types"
|
||||
```
|
||||
|
||||
**Phase 3 Query Updates** (after T009 is complete):
|
||||
**Phase 4 Documentation** (can run in parallel):
|
||||
```bash
|
||||
# These can run in parallel once helper function exists:
|
||||
Task T010: "Update query_relationships"
|
||||
Task T011: "Update get_predecessors"
|
||||
Task T012: "Update get_successors"
|
||||
Task T009: "Update ActivitySummary schema"
|
||||
Task T010: "Update Activity entity in data-model.md"
|
||||
Task T011: "Update quickstart.md example"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Parallel Example: Phase 3
|
||||
## Implementation Details
|
||||
|
||||
```bash
|
||||
# First: Write all tests in parallel
|
||||
Task: "Update contract test for list_relationships in tests/contract/test_list_relationships.py"
|
||||
Task: "Update contract test for get_predecessors in tests/contract/test_get_predecessors.py"
|
||||
Task: "Update contract test for get_successors in tests/contract/test_get_successors.py"
|
||||
Task: "Unit test for driving flag computation in tests/unit/test_db_queries.py"
|
||||
### Milestone Type Determination
|
||||
|
||||
# Verify all tests FAIL (driving flag not yet implemented)
|
||||
In Primavera P6, milestones are identified by `task_type = 'TT_Mile'`. The distinction between Start and Finish milestones is typically:
|
||||
|
||||
# Then: Implement helper function
|
||||
Task: "Create is_driving_relationship helper in src/xer_mcp/db/queries.py"
|
||||
1. **XER Field**: Check for `milestone_type` field in TASK table (values: `MS_Start`, `MS_Finish`)
|
||||
2. **Fallback**: If field not present, can infer from dates:
|
||||
- Start milestone: Has only `target_start_date` (or start = end)
|
||||
- Finish milestone: Has only `target_end_date` (or represents completion)
|
||||
|
||||
# Then: Update all queries in parallel
|
||||
Task: "Update query_relationships in src/xer_mcp/db/queries.py"
|
||||
Task: "Update get_predecessors in src/xer_mcp/db/queries.py"
|
||||
Task: "Update get_successors in src/xer_mcp/db/queries.py"
|
||||
|
||||
# Verify all tests PASS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
### Driving Flag Computation (from research.md)
|
||||
|
||||
The driving flag is computed at query time by comparing dates:
|
||||
|
||||
```python
|
||||
def is_driving_relationship(
|
||||
pred_early_end: str | None,
|
||||
succ_early_start: str | None,
|
||||
lag_hours: float,
|
||||
pred_type: str
|
||||
) -> bool:
|
||||
"""Determine if relationship is driving based on early dates."""
|
||||
if pred_early_end is None or succ_early_start is None:
|
||||
return False
|
||||
|
||||
# For FS: pred_end + lag = succ_start means driving
|
||||
if pred_type == "FS":
|
||||
# Parse dates, add lag, compare with 1-hour tolerance
|
||||
...
|
||||
|
||||
# Similar logic for SS, FF, SF
|
||||
return False
|
||||
```
|
||||
|
||||
### SQL Query Pattern
|
||||
### Schema Change
|
||||
|
||||
```sql
|
||||
SELECT r.*,
|
||||
pred.early_end_date,
|
||||
succ.early_start_date,
|
||||
-- Compute driving in Python after fetch, or use CASE in SQL
|
||||
FROM relationships r
|
||||
JOIN activities pred ON r.pred_task_id = pred.task_id
|
||||
JOIN activities succ ON r.task_id = succ.task_id
|
||||
-- Add to activities table
|
||||
milestone_type TEXT -- 'start', 'finish', or NULL for non-milestones
|
||||
```
|
||||
|
||||
### Query Change
|
||||
|
||||
```sql
|
||||
SELECT task_id, task_code, task_name,
|
||||
target_start_date, target_end_date,
|
||||
status_code, milestone_type
|
||||
FROM activities
|
||||
WHERE task_type = 'TT_Mile'
|
||||
ORDER BY target_start_date, task_code
|
||||
```
|
||||
|
||||
### Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"milestones": [
|
||||
{
|
||||
"task_id": "M001",
|
||||
"task_code": "MS-START",
|
||||
"task_name": "Project Start",
|
||||
"milestone_type": "start",
|
||||
"target_start_date": "2026-01-15T08:00:00",
|
||||
"target_end_date": "2026-01-15T08:00:00",
|
||||
"status_code": "TK_Complete"
|
||||
},
|
||||
{
|
||||
"task_id": "M002",
|
||||
"task_code": "MS-END",
|
||||
"task_name": "Project Complete",
|
||||
"milestone_type": "finish",
|
||||
"target_start_date": "2026-06-30T17:00:00",
|
||||
"target_end_date": "2026-06-30T17:00:00",
|
||||
"status_code": "TK_NotStart"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -210,30 +216,40 @@ JOIN activities succ ON r.task_id = succ.task_id
|
||||
|-------|-------|-------|
|
||||
| Setup | 1 | Schema update |
|
||||
| Foundational | 3 | Parser enhancement |
|
||||
| US3 Implementation | 8 | Driving flag computation |
|
||||
| Polish | 4 | Validation |
|
||||
| **Total** | **16** | |
|
||||
| US4 Implementation | 4 | Milestone type in queries |
|
||||
| Documentation | 3 | Contracts & docs |
|
||||
| Polish | 3 | Validation |
|
||||
| **Total** | **14** | |
|
||||
|
||||
### Task Distribution by Type
|
||||
|
||||
| Type | Count | IDs |
|
||||
|------|-------|-----|
|
||||
| Schema | 1 | T001 |
|
||||
| Tests | 5 | T002, T005-T008 |
|
||||
| Implementation | 6 | T003-T004, T009-T012 |
|
||||
| Validation | 4 | T013-T016 |
|
||||
| Tests | 3 | T002, T005, T006 |
|
||||
| Implementation | 4 | T003, T004, T007, T008 |
|
||||
| Documentation | 3 | T009, T010, T011 |
|
||||
| Validation | 3 | T012, T013, T014 |
|
||||
|
||||
### Independent Test Criteria
|
||||
|
||||
| Verification | How to Test |
|
||||
|--------------|-------------|
|
||||
| milestone_type parsed | Unit test: parse XER with milestones, verify milestone_type extracted |
|
||||
| milestone_type in response | Contract test: call list_milestones, verify milestone_type field present |
|
||||
| Start/Finish distinction | Contract test: verify "Project Start" has type="start", "Project Complete" has type="finish" |
|
||||
|
||||
### MVP Scope
|
||||
|
||||
Complete through T012 for minimal viable driving flag implementation.
|
||||
Complete through T008 for minimal viable milestone_type implementation.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- [P] tasks = different files, no dependencies on incomplete tasks
|
||||
- [US3] = User Story 3 (Query Activity Relationships)
|
||||
- [US4] = User Story 4 (Query Project Summary - milestones)
|
||||
- Constitution mandates TDD: write tests first, verify they fail, then implement
|
||||
- Driving flag is COMPUTED at query time, not stored in database
|
||||
- Use 1-hour tolerance for floating-point date arithmetic
|
||||
- milestone_type is stored in database, not computed at query time
|
||||
- For non-milestone activities, milestone_type should be NULL
|
||||
- Commit after each task or logical group
|
||||
|
||||
@@ -78,9 +78,9 @@ def load_parsed_data(parsed: ParsedXer, project_id: str) -> None:
|
||||
task_id, proj_id, wbs_id, task_code, task_name, task_type,
|
||||
target_start_date, target_end_date, early_start_date, early_end_date,
|
||||
act_start_date, act_end_date,
|
||||
total_float_hr_cnt, driving_path_flag, status_code
|
||||
total_float_hr_cnt, driving_path_flag, status_code, milestone_type
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
task["task_id"],
|
||||
@@ -98,6 +98,7 @@ def load_parsed_data(parsed: ParsedXer, project_id: str) -> None:
|
||||
task["total_float_hr_cnt"],
|
||||
1 if task["driving_path_flag"] else 0,
|
||||
task["status_code"],
|
||||
task.get("milestone_type"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ CREATE TABLE IF NOT EXISTS activities (
|
||||
total_float_hr_cnt REAL,
|
||||
driving_path_flag INTEGER DEFAULT 0,
|
||||
status_code TEXT,
|
||||
milestone_type TEXT,
|
||||
FOREIGN KEY (proj_id) REFERENCES projects(proj_id)
|
||||
);
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ from xer_mcp.server import is_file_loaded
|
||||
async def list_milestones() -> dict:
|
||||
"""List all milestone activities in the loaded project.
|
||||
|
||||
Returns both Start Milestones and Finish Milestones with their milestone_type.
|
||||
|
||||
Returns:
|
||||
Dictionary with list of milestones, each containing:
|
||||
- task_id: Activity ID
|
||||
@@ -15,6 +17,7 @@ async def list_milestones() -> dict:
|
||||
- target_start_date: Target start date
|
||||
- target_end_date: Target end date
|
||||
- status_code: Activity status
|
||||
- milestone_type: 'start' for start milestones, 'finish' for finish milestones
|
||||
"""
|
||||
if not is_file_loaded():
|
||||
return {
|
||||
|
||||
@@ -19,12 +19,12 @@ ERMHDR\t21.12\t2026-01-06\tProject\tADMIN\ttestuser\tdbTest\tProject Management\
|
||||
%R\t101\t1001\t\t1\t1\tN\tN\tWS_Open\tPH1\tPhase 1\t\t100\t6\t0.88\t0.0000\t0.0000\t\t\t\t\t\tEC_Cmp_pct\tEE_Rem_hr\twbs-guid-2\t\t
|
||||
%R\t102\t1001\t\t2\t1\tN\tN\tWS_Open\tPH2\tPhase 2\t\t100\t6\t0.88\t0.0000\t0.0000\t\t\t\t\t\tEC_Cmp_pct\tEE_Rem_hr\twbs-guid-3\t\t
|
||||
%T\tTASK
|
||||
%F\ttask_id\tproj_id\twbs_id\tclndr_id\tphys_complete_pct\trev_fdbk_flag\test_wt\tlock_plan_flag\tauto_compute_act_flag\tcomplete_pct_type\ttask_type\tduration_type\tstatus_code\ttask_code\ttask_name\trsrc_id\ttotal_float_hr_cnt\tfree_float_hr_cnt\tremain_drtn_hr_cnt\tact_work_qty\tremain_work_qty\ttarget_work_qty\ttarget_drtn_hr_cnt\ttarget_equip_qty\tact_equip_qty\tremain_equip_qty\tcstr_date\tact_start_date\tact_end_date\tlate_start_date\tlate_end_date\texpect_end_date\tearly_start_date\tearly_end_date\trestart_date\treend_date\ttarget_start_date\ttarget_end_date\trem_late_start_date\trem_late_end_date\tcstr_type\tpriority_type\tsuspend_date\tresume_date\tfloat_path\tfloat_path_order\tguid\ttmpl_guid\tcstr_date2\tcstr_type2\tdriving_path_flag\tact_this_per_work_qty\tact_this_per_equip_qty\texternal_early_start_date\texternal_late_end_date\tcreate_date\tupdate_date\tcreate_user\tupdate_user\tlocation_id\tcrt_path_num
|
||||
%R\t2001\t1001\t101\t1\t0\tN\t1\tN\tN\tCP_Drtn\tTT_Mile\tDT_FixedDrtn\tTK_NotStart\tA1000\tProject Start\t\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t\t\t\t2026-01-01 07:00\t2026-01-01 07:00\t\t2026-01-01 07:00\t2026-01-01 07:00\t2026-01-01 07:00\t2026-01-01 07:00\t2026-01-01 07:00\t2026-01-01 07:00\t2026-01-01 07:00\t2026-01-01 07:00\t\tPT_Normal\t\t\t1\t1\ttask-guid-1\t\t\t\tY\t0\t0\t\t\t2026-01-06 00:00\t2026-01-06 00:00\tADMIN\tADMIN\t\t
|
||||
%R\t2002\t1001\t101\t1\t0\tN\t1\tN\tN\tCP_Drtn\tTT_Task\tDT_FixedDUR2\tTK_NotStart\tA1010\tSite Preparation\t\t0\t0\t40\t0\t0\t0\t40\t0\t0\t0\t\t\t\t2026-01-02 07:00\t2026-01-08 15:00\t\t2026-01-02 07:00\t2026-01-08 15:00\t2026-01-02 07:00\t2026-01-08 15:00\t2026-01-02 07:00\t2026-01-08 15:00\t2026-01-02 07:00\t2026-01-08 15:00\t\tPT_Normal\t\t\t1\t2\ttask-guid-2\t\t\t\tY\t0\t0\t\t\t2026-01-06 00:00\t2026-01-06 00:00\tADMIN\tADMIN\t\t
|
||||
%R\t2003\t1001\t101\t1\t0\tN\t1\tN\tN\tCP_Drtn\tTT_Task\tDT_FixedDUR2\tTK_NotStart\tA1020\tFoundation Work\t\t80\t0\t80\t0\t0\t0\t80\t0\t0\t0\t\t\t\t2026-01-09 07:00\t2026-01-22 15:00\t\t2026-01-09 07:00\t2026-01-22 15:00\t2026-01-09 07:00\t2026-01-22 15:00\t2026-01-09 07:00\t2026-01-22 15:00\t2026-01-09 07:00\t2026-01-22 15:00\t\tPT_Normal\t\t\t1\t3\ttask-guid-3\t\t\t\tN\t0\t0\t\t\t2026-01-06 00:00\t2026-01-06 00:00\tADMIN\tADMIN\t\t
|
||||
%R\t2004\t1001\t102\t1\t0\tN\t1\tN\tN\tCP_Drtn\tTT_Task\tDT_FixedDUR2\tTK_NotStart\tA1030\tStructural Work\t\t0\t0\t160\t0\t0\t0\t160\t0\t0\t0\t\t\t\t2026-01-23 07:00\t2026-02-19 15:00\t\t2026-01-23 07:00\t2026-02-19 15:00\t2026-01-23 07:00\t2026-02-19 15:00\t2026-01-23 07:00\t2026-02-19 15:00\t2026-01-23 07:00\t2026-02-19 15:00\t\tPT_Normal\t\t\t1\t4\ttask-guid-4\t\t\t\tY\t0\t0\t\t\t2026-01-06 00:00\t2026-01-06 00:00\tADMIN\tADMIN\t\t
|
||||
%R\t2005\t1001\t102\t1\t0\tN\t1\tN\tN\tCP_Drtn\tTT_Mile\tDT_FixedDrtn\tTK_NotStart\tA1040\tProject Complete\t\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t\t\t\t2026-02-20 07:00\t2026-02-20 07:00\t\t2026-02-20 07:00\t2026-02-20 07:00\t2026-02-20 07:00\t2026-02-20 07:00\t2026-02-20 07:00\t2026-02-20 07:00\t2026-02-20 07:00\t2026-02-20 07:00\t\tPT_Normal\t\t\t1\t5\ttask-guid-5\t\t\t\tY\t0\t0\t\t\t2026-01-06 00:00\t2026-01-06 00:00\tADMIN\tADMIN\t\t
|
||||
%F\ttask_id\tproj_id\twbs_id\tclndr_id\tphys_complete_pct\trev_fdbk_flag\test_wt\tlock_plan_flag\tauto_compute_act_flag\tcomplete_pct_type\ttask_type\tduration_type\tstatus_code\ttask_code\ttask_name\trsrc_id\ttotal_float_hr_cnt\tfree_float_hr_cnt\tremain_drtn_hr_cnt\tact_work_qty\tremain_work_qty\ttarget_work_qty\ttarget_drtn_hr_cnt\ttarget_equip_qty\tact_equip_qty\tremain_equip_qty\tcstr_date\tact_start_date\tact_end_date\tlate_start_date\tlate_end_date\texpect_end_date\tearly_start_date\tearly_end_date\trestart_date\treend_date\ttarget_start_date\ttarget_end_date\trem_late_start_date\trem_late_end_date\tcstr_type\tpriority_type\tsuspend_date\tresume_date\tfloat_path\tfloat_path_order\tguid\ttmpl_guid\tcstr_date2\tcstr_type2\tdriving_path_flag\tact_this_per_work_qty\tact_this_per_equip_qty\texternal_early_start_date\texternal_late_end_date\tcreate_date\tupdate_date\tcreate_user\tupdate_user\tlocation_id\tcrt_path_num\tmilestone_type
|
||||
%R\t2001\t1001\t101\t1\t0\tN\t1\tN\tN\tCP_Drtn\tTT_Mile\tDT_FixedDrtn\tTK_NotStart\tA1000\tProject Start\t\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t\t\t\t2026-01-01 07:00\t2026-01-01 07:00\t\t2026-01-01 07:00\t2026-01-01 07:00\t2026-01-01 07:00\t2026-01-01 07:00\t2026-01-01 07:00\t2026-01-01 07:00\t2026-01-01 07:00\t2026-01-01 07:00\t\tPT_Normal\t\t\t1\t1\ttask-guid-1\t\t\t\tY\t0\t0\t\t\t2026-01-06 00:00\t2026-01-06 00:00\tADMIN\tADMIN\t\t\tMS_Start
|
||||
%R\t2002\t1001\t101\t1\t0\tN\t1\tN\tN\tCP_Drtn\tTT_Task\tDT_FixedDUR2\tTK_NotStart\tA1010\tSite Preparation\t\t0\t0\t40\t0\t0\t0\t40\t0\t0\t0\t\t\t\t2026-01-02 07:00\t2026-01-08 15:00\t\t2026-01-02 07:00\t2026-01-08 15:00\t2026-01-02 07:00\t2026-01-08 15:00\t2026-01-02 07:00\t2026-01-08 15:00\t2026-01-02 07:00\t2026-01-08 15:00\t\tPT_Normal\t\t\t1\t2\ttask-guid-2\t\t\t\tY\t0\t0\t\t\t2026-01-06 00:00\t2026-01-06 00:00\tADMIN\tADMIN\t\t\t
|
||||
%R\t2003\t1001\t101\t1\t0\tN\t1\tN\tN\tCP_Drtn\tTT_Task\tDT_FixedDUR2\tTK_NotStart\tA1020\tFoundation Work\t\t80\t0\t80\t0\t0\t0\t80\t0\t0\t0\t\t\t\t2026-01-09 07:00\t2026-01-22 15:00\t\t2026-01-09 07:00\t2026-01-22 15:00\t2026-01-09 07:00\t2026-01-22 15:00\t2026-01-09 07:00\t2026-01-22 15:00\t2026-01-09 07:00\t2026-01-22 15:00\t\tPT_Normal\t\t\t1\t3\ttask-guid-3\t\t\t\tN\t0\t0\t\t\t2026-01-06 00:00\t2026-01-06 00:00\tADMIN\tADMIN\t\t\t
|
||||
%R\t2004\t1001\t102\t1\t0\tN\t1\tN\tN\tCP_Drtn\tTT_Task\tDT_FixedDUR2\tTK_NotStart\tA1030\tStructural Work\t\t0\t0\t160\t0\t0\t0\t160\t0\t0\t0\t\t\t\t2026-01-23 07:00\t2026-02-19 15:00\t\t2026-01-23 07:00\t2026-02-19 15:00\t2026-01-23 07:00\t2026-02-19 15:00\t2026-01-23 07:00\t2026-02-19 15:00\t2026-01-23 07:00\t2026-02-19 15:00\t\tPT_Normal\t\t\t1\t4\ttask-guid-4\t\t\t\tY\t0\t0\t\t\t2026-01-06 00:00\t2026-01-06 00:00\tADMIN\tADMIN\t\t\t
|
||||
%R\t2005\t1001\t102\t1\t0\tN\t1\tN\tN\tCP_Drtn\tTT_Mile\tDT_FixedDrtn\tTK_NotStart\tA1040\tProject Complete\t\t0\t0\t0\t0\t0\t0\t0\t0\t0\t0\t\t\t\t2026-02-20 07:00\t2026-02-20 07:00\t\t2026-02-20 07:00\t2026-02-20 07:00\t2026-02-20 07:00\t2026-02-20 07:00\t2026-02-20 07:00\t2026-02-20 07:00\t2026-02-20 07:00\t2026-02-20 07:00\t\tPT_Normal\t\t\t1\t5\ttask-guid-5\t\t\t\tY\t0\t0\t\t\t2026-01-06 00:00\t2026-01-06 00:00\tADMIN\tADMIN\t\t\tMS_Finish
|
||||
%T\tTASKPRED
|
||||
%F\ttask_pred_id\ttask_id\tpred_task_id\tproj_id\tpred_proj_id\tpred_type\tlag_hr_cnt\tcomments\tfloat_path\taref\tarls
|
||||
%R\t3001\t2002\t2001\t1001\t1001\tPR_FS\t0\t\t\t2026-01-01 07:00\t2026-01-02 07:00
|
||||
|
||||
@@ -87,3 +87,38 @@ class TestListMilestonesContract:
|
||||
|
||||
assert "error" in result
|
||||
assert result["error"]["code"] == "NO_FILE_LOADED"
|
||||
|
||||
async def test_list_milestones_includes_milestone_type_field(
|
||||
self, sample_xer_single_project: Path
|
||||
) -> None:
|
||||
"""list_milestones returns milestones with milestone_type field."""
|
||||
from xer_mcp.tools.list_milestones import list_milestones
|
||||
from xer_mcp.tools.load_xer import load_xer
|
||||
|
||||
await load_xer(file_path=str(sample_xer_single_project))
|
||||
|
||||
result = await list_milestones()
|
||||
|
||||
# All milestones should have milestone_type field
|
||||
for milestone in result["milestones"]:
|
||||
assert "milestone_type" in milestone
|
||||
|
||||
async def test_list_milestones_returns_start_and_finish_types(
|
||||
self, sample_xer_single_project: Path
|
||||
) -> None:
|
||||
"""list_milestones returns milestones with correct start/finish types."""
|
||||
from xer_mcp.tools.list_milestones import list_milestones
|
||||
from xer_mcp.tools.load_xer import load_xer
|
||||
|
||||
await load_xer(file_path=str(sample_xer_single_project))
|
||||
|
||||
result = await list_milestones()
|
||||
|
||||
# Find milestones by name and verify their types
|
||||
milestones_by_name = {m["task_name"]: m for m in result["milestones"]}
|
||||
|
||||
# "Project Start" should be a start milestone
|
||||
assert milestones_by_name["Project Start"]["milestone_type"] == "start"
|
||||
|
||||
# "Project Complete" should be a finish milestone
|
||||
assert milestones_by_name["Project Complete"]["milestone_type"] == "finish"
|
||||
|
||||
Reference in New Issue
Block a user