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/
|
.tox/
|
||||||
.nox/
|
.nox/
|
||||||
|
|
||||||
# XER files (may contain sensitive project data)
|
# Schedule files
|
||||||
*.xer
|
schedules/
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
|
||||||
|
.claude/skills/
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ Auto-generated from all feature plans. Last updated: 2026-01-06
|
|||||||
|
|
||||||
## Active Technologies
|
## Active Technologies
|
||||||
- SQLite in-memory database (001-schedule-tools)
|
- 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)
|
- 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
|
Python 3.14: Follow standard conventions
|
||||||
|
|
||||||
## Recent Changes
|
## 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)
|
||||||
|
|
||||||
- 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_start_date": { "type": "string", "format": "date-time" },
|
||||||
"target_end_date": { "type": "string", "format": "date-time" },
|
"target_end_date": { "type": "string", "format": "date-time" },
|
||||||
"status_code": { "type": "string" },
|
"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": {
|
"ActivityDetail": {
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ A unit of work in the schedule.
|
|||||||
| task_code | string | Yes | User-visible activity code |
|
| task_code | string | Yes | User-visible activity code |
|
||||||
| task_name | string | Yes | Activity description |
|
| task_name | string | Yes | Activity description |
|
||||||
| task_type | enum | Yes | TT_Task, TT_Mile, TT_LOE, TT_WBS, TT_Rsrc |
|
| 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_start_date | datetime | No | Planned start |
|
||||||
| target_end_date | datetime | No | Planned finish |
|
| target_end_date | datetime | No | Planned finish |
|
||||||
| early_start_date | datetime | No | Calculated early start (for driving computation) |
|
| 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`
|
**Input**: Feature specification from `/specs/001-schedule-tools/spec.md`
|
||||||
|
|
||||||
## Summary
|
## 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
|
## Technical Context
|
||||||
|
|
||||||
**Language/Version**: Python 3.14
|
**Language/Version**: Python 3.14
|
||||||
**Primary Dependencies**: mcp (MCP SDK), sqlite3 (stdlib)
|
**Primary Dependencies**: mcp>=1.0.0 (MCP SDK), sqlite3 (stdlib)
|
||||||
**Storage**: SQLite in-memory database
|
**Storage**: In-memory SQLite database (populated from XER files at runtime)
|
||||||
**Testing**: pytest, pytest-asyncio
|
**Testing**: pytest>=8.0.0, pytest-asyncio>=0.24.0
|
||||||
**Target Platform**: Linux server (local MCP server)
|
**Target Platform**: Local server (Linux/macOS/Windows with file system access)
|
||||||
**Project Type**: single
|
**Project Type**: Single project
|
||||||
**Performance Goals**: Query response < 1 second, load < 5 seconds for 10k activities
|
**Performance Goals**: Load + query XER files ≤5 seconds for 10,000 activities; query response <1 second
|
||||||
**Constraints**: Single-user operation, in-memory storage for loaded XER data
|
**Constraints**: Memory sufficient for 50,000 activities; single-user operation
|
||||||
**Scale/Scope**: Files up to 50,000 activities
|
**Scale/Scope**: MCP server with 9 tools; handles typical P6 project files (up to 50,000 activities)
|
||||||
|
|
||||||
## Constitution Check
|
## Constitution Check
|
||||||
|
|
||||||
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
||||||
|
|
||||||
| Principle | Status | Evidence/Notes |
|
| Principle | Requirement | Status | Notes |
|
||||||
|-----------|--------|----------------|
|
|-----------|-------------|--------|-------|
|
||||||
| I. Test-First Development | ✅ PASS | TDD required - write failing tests first for driving flag in relationship responses |
|
| **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 | ✅ PASS | Current design already separates parser → db → query → tool layers |
|
| **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 | ✅ PASS | Tool schemas already JSON Schema compliant; no schema changes needed |
|
| **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 | ✅ PASS | Must parse `driving_flag` from TASKPRED table in XER files |
|
| **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 | ✅ PASS | Adding new field to response is backward-compatible (MINOR) |
|
| **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
|
**Gate Result**: PASS - All constitution principles satisfied. Proceed to Phase 0.
|
||||||
|
|
||||||
**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)
|
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
@@ -49,53 +40,116 @@ Extend the existing XER MCP Server implementation to include the "driving" flag
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
specs/001-schedule-tools/
|
specs/001-schedule-tools/
|
||||||
├── plan.md # This file
|
├── spec.md # Feature specification
|
||||||
├── research.md # Phase 0 output
|
├── plan.md # This file (/speckit.plan command output)
|
||||||
├── data-model.md # Phase 1 output
|
├── research.md # Phase 0 output (/speckit.plan command)
|
||||||
├── quickstart.md # Phase 1 output
|
├── data-model.md # Phase 1 output (/speckit.plan command)
|
||||||
├── contracts/ # Phase 1 output
|
├── quickstart.md # Phase 1 output (/speckit.plan command)
|
||||||
└── tasks.md # Phase 2 output (/speckit.tasks)
|
├── contracts/ # Phase 1 output (/speckit.plan command)
|
||||||
|
└── tasks.md # Phase 2 output (/speckit.tasks command - NOT created by /speckit.plan)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Source Code (repository root)
|
### Source Code (repository root)
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/
|
src/xer_mcp/
|
||||||
├── xer_mcp/
|
├── __init__.py
|
||||||
│ ├── models/
|
├── __main__.py # Entry point
|
||||||
│ │ └── relationship.py # Add driving field to dataclass
|
├── server.py # MCP server setup and tool registration
|
||||||
│ ├── parser/
|
├── errors.py # Error types
|
||||||
│ │ └── table_handlers/
|
├── models/ # Data models (dataclasses)
|
||||||
│ │ └── taskpred.py # Parse driving_flag from XER
|
│ ├── __init__.py
|
||||||
│ ├── db/
|
│ ├── project.py
|
||||||
│ │ ├── schema.py # Add driving column to relationships table
|
│ ├── activity.py
|
||||||
│ │ ├── loader.py # Store driving flag when loading
|
│ ├── relationship.py
|
||||||
│ │ └── queries.py # Return driving in relationship queries
|
│ ├── wbs.py
|
||||||
│ └── tools/
|
│ ├── calendar.py
|
||||||
│ ├── list_relationships.py # Response already uses query result
|
│ └── pagination.py
|
||||||
│ ├── get_predecessors.py # Response already uses query result
|
├── parser/ # XER file parsing
|
||||||
│ └── get_successors.py # Response already uses query result
|
│ ├── __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/
|
tests/
|
||||||
├── contract/
|
├── __init__.py
|
||||||
│ ├── test_list_relationships.py # Verify driving flag in response
|
├── conftest.py # Shared fixtures
|
||||||
│ ├── test_get_predecessors.py # Verify driving flag in response
|
├── contract/ # MCP tool contract tests
|
||||||
│ └── test_get_successors.py # Verify driving flag in response
|
│ ├── __init__.py
|
||||||
├── integration/
|
│ ├── test_load_xer.py
|
||||||
│ └── test_xer_parsing.py # Verify driving flag parsed from XER
|
│ ├── test_list_activities.py
|
||||||
└── unit/
|
│ ├── test_get_activity.py
|
||||||
├── test_table_handlers.py # Test TASKPRED handler parses driving
|
│ ├── test_list_relationships.py
|
||||||
└── test_db_queries.py # Test queries return driving flag
|
│ ├── 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
|
## 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 |
|
No violations. All constitution principles satisfied.
|
||||||
|------|------------|
|
|
||||||
| Scope | Small - single field addition across 4 layers |
|
## Post-Design Constitution Re-Check
|
||||||
| Risk | Low - additive change, no behavior modification |
|
|
||||||
| Testing | Contract tests + unit tests for each layer |
|
*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
|
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
|
## Pagination
|
||||||
|
|
||||||
All list operations support 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**:
|
**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
|
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
|
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
|
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-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-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-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-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-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
|
- **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
|
### Key Entities
|
||||||
|
|
||||||
- **Project**: The top-level container representing a P6 project with name, ID, start/finish dates, and calendar assignments
|
- **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
|
- **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
|
- **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)
|
- **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: 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 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: 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
|
## 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/`
|
**Input**: Design documents from `/specs/001-schedule-tools/`
|
||||||
**Prerequisites**: plan.md (required), spec.md (required), research.md, data-model.md, contracts/
|
**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.
|
**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`
|
## Format: `[ID] [P?] [Story] Description`
|
||||||
|
|
||||||
- **[P]**: Can run in parallel (different files, no dependencies)
|
- **[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
|
- Include exact file paths in descriptions
|
||||||
|
|
||||||
## Path Conventions
|
## Path Conventions
|
||||||
@@ -23,65 +25,70 @@
|
|||||||
|
|
||||||
## Phase 1: Setup (Schema Enhancement)
|
## 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)
|
## 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
|
### 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
|
### 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] 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 early dates when inserting activities in src/xer_mcp/db/loader.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**
|
> **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] 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] [US3] Update contract test to verify driving flag in get_predecessors response in tests/contract/test_get_predecessors.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
|
||||||
- [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)
|
|
||||||
|
|
||||||
### 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] T007 [US4] Update query_milestones function to SELECT and return milestone_type in src/xer_mcp/db/queries.py
|
||||||
- [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] T008 [US4] Update list_milestones tool docstring to document milestone_type field in src/xer_mcp/tools/list_milestones.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
|
|
||||||
|
|
||||||
**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] T009 [P] Update ActivitySummary schema to include optional milestone_type field in specs/001-schedule-tools/contracts/mcp-tools.json
|
||||||
- [x] T014 Run all tests to verify no regressions: pytest tests/
|
- [x] T010 [P] Update Activity entity in data-model.md to include milestone_type field in specs/001-schedule-tools/data-model.md
|
||||||
- [x] T015 Run quickstart.md validation - verify driving flag examples match actual output
|
- [x] T011 Update quickstart.md milestone example to show milestone_type in output in specs/001-schedule-tools/quickstart.md
|
||||||
- [x] T016 Run ruff check and fix any linting issues: ruff check src/
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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 3 (Queries) ← MAIN WORK
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
Phase 4 (Polish)
|
Phase 4 (Docs) ← Can run in parallel with Phase 3
|
||||||
```
|
|
||||||
|
|
||||||
### Task Dependencies Within Phase 3
|
|
||||||
|
|
||||||
```
|
|
||||||
T005, T006, T007, T008 (Tests) ← Write first, verify FAIL
|
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
T009 (Helper function)
|
Phase 5 (Polish)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task Dependencies Within Phases
|
||||||
|
|
||||||
|
```
|
||||||
|
T001 (Schema)
|
||||||
│
|
│
|
||||||
├── T010 (query_relationships)
|
├── T002 (Parser test)
|
||||||
├── T011 (get_predecessors)
|
|
||||||
└── T012 (get_successors)
|
|
||||||
│
|
│
|
||||||
▼
|
▼
|
||||||
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
|
### Parallel Opportunities
|
||||||
|
|
||||||
**Phase 2 Tests + Phase 3 Tests** (can write all tests in parallel):
|
**Phase 2 Tests + Phase 3 Tests** (can write all tests in parallel):
|
||||||
```bash
|
```bash
|
||||||
# All test tasks can run in parallel:
|
Task T002: "Unit test for TASK handler parsing milestone_type"
|
||||||
Task T002: "Unit test for TASK handler parsing early dates"
|
Task T005: "Update contract test for list_milestones"
|
||||||
Task T005: "Update contract test for list_relationships"
|
Task T006: "Add test for start/finish milestone types"
|
||||||
Task T006: "Update contract test for get_predecessors"
|
|
||||||
Task T007: "Update contract test for get_successors"
|
|
||||||
Task T008: "Unit test for driving flag computation"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Phase 3 Query Updates** (after T009 is complete):
|
**Phase 4 Documentation** (can run in parallel):
|
||||||
```bash
|
```bash
|
||||||
# These can run in parallel once helper function exists:
|
Task T009: "Update ActivitySummary schema"
|
||||||
Task T010: "Update query_relationships"
|
Task T010: "Update Activity entity in data-model.md"
|
||||||
Task T011: "Update get_predecessors"
|
Task T011: "Update quickstart.md example"
|
||||||
Task T012: "Update get_successors"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Parallel Example: Phase 3
|
## Implementation Details
|
||||||
|
|
||||||
```bash
|
### Milestone Type Determination
|
||||||
# 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"
|
|
||||||
|
|
||||||
# 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
|
1. **XER Field**: Check for `milestone_type` field in TASK table (values: `MS_Start`, `MS_Finish`)
|
||||||
Task: "Create is_driving_relationship helper in src/xer_mcp/db/queries.py"
|
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
|
### Schema Change
|
||||||
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
|
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT r.*,
|
-- Add to activities table
|
||||||
pred.early_end_date,
|
milestone_type TEXT -- 'start', 'finish', or NULL for non-milestones
|
||||||
succ.early_start_date,
|
```
|
||||||
-- Compute driving in Python after fetch, or use CASE in SQL
|
|
||||||
FROM relationships r
|
### Query Change
|
||||||
JOIN activities pred ON r.pred_task_id = pred.task_id
|
|
||||||
JOIN activities succ ON r.task_id = succ.task_id
|
```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 |
|
| Setup | 1 | Schema update |
|
||||||
| Foundational | 3 | Parser enhancement |
|
| Foundational | 3 | Parser enhancement |
|
||||||
| US3 Implementation | 8 | Driving flag computation |
|
| US4 Implementation | 4 | Milestone type in queries |
|
||||||
| Polish | 4 | Validation |
|
| Documentation | 3 | Contracts & docs |
|
||||||
| **Total** | **16** | |
|
| Polish | 3 | Validation |
|
||||||
|
| **Total** | **14** | |
|
||||||
|
|
||||||
### Task Distribution by Type
|
### Task Distribution by Type
|
||||||
|
|
||||||
| Type | Count | IDs |
|
| Type | Count | IDs |
|
||||||
|------|-------|-----|
|
|------|-------|-----|
|
||||||
| Schema | 1 | T001 |
|
| Schema | 1 | T001 |
|
||||||
| Tests | 5 | T002, T005-T008 |
|
| Tests | 3 | T002, T005, T006 |
|
||||||
| Implementation | 6 | T003-T004, T009-T012 |
|
| Implementation | 4 | T003, T004, T007, T008 |
|
||||||
| Validation | 4 | T013-T016 |
|
| 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
|
### MVP Scope
|
||||||
|
|
||||||
Complete through T012 for minimal viable driving flag implementation.
|
Complete through T008 for minimal viable milestone_type implementation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- [P] tasks = different files, no dependencies on incomplete tasks
|
- [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
|
- Constitution mandates TDD: write tests first, verify they fail, then implement
|
||||||
- Driving flag is COMPUTED at query time, not stored in database
|
- milestone_type is stored in database, not computed at query time
|
||||||
- Use 1-hour tolerance for floating-point date arithmetic
|
- For non-milestone activities, milestone_type should be NULL
|
||||||
- Commit after each task or logical group
|
- 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,
|
task_id, proj_id, wbs_id, task_code, task_name, task_type,
|
||||||
target_start_date, target_end_date, early_start_date, early_end_date,
|
target_start_date, target_end_date, early_start_date, early_end_date,
|
||||||
act_start_date, act_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"],
|
task["task_id"],
|
||||||
@@ -98,6 +98,7 @@ def load_parsed_data(parsed: ParsedXer, project_id: str) -> None:
|
|||||||
task["total_float_hr_cnt"],
|
task["total_float_hr_cnt"],
|
||||||
1 if task["driving_path_flag"] else 0,
|
1 if task["driving_path_flag"] else 0,
|
||||||
task["status_code"],
|
task["status_code"],
|
||||||
|
task.get("milestone_type"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ CREATE TABLE IF NOT EXISTS activities (
|
|||||||
total_float_hr_cnt REAL,
|
total_float_hr_cnt REAL,
|
||||||
driving_path_flag INTEGER DEFAULT 0,
|
driving_path_flag INTEGER DEFAULT 0,
|
||||||
status_code TEXT,
|
status_code TEXT,
|
||||||
|
milestone_type TEXT,
|
||||||
FOREIGN KEY (proj_id) REFERENCES projects(proj_id)
|
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:
|
async def list_milestones() -> dict:
|
||||||
"""List all milestone activities in the loaded project.
|
"""List all milestone activities in the loaded project.
|
||||||
|
|
||||||
|
Returns both Start Milestones and Finish Milestones with their milestone_type.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary with list of milestones, each containing:
|
Dictionary with list of milestones, each containing:
|
||||||
- task_id: Activity ID
|
- task_id: Activity ID
|
||||||
@@ -15,6 +17,7 @@ async def list_milestones() -> dict:
|
|||||||
- target_start_date: Target start date
|
- target_start_date: Target start date
|
||||||
- target_end_date: Target end date
|
- target_end_date: Target end date
|
||||||
- status_code: Activity status
|
- status_code: Activity status
|
||||||
|
- milestone_type: 'start' for start milestones, 'finish' for finish milestones
|
||||||
"""
|
"""
|
||||||
if not is_file_loaded():
|
if not is_file_loaded():
|
||||||
return {
|
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\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
|
%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
|
%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
|
%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
|
%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
|
%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
|
%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
|
%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
|
%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
|
%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
|
%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
|
%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 "error" in result
|
||||||
assert result["error"]["code"] == "NO_FILE_LOADED"
|
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