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')
8.7 KiB
Data Model: Project Schedule Tools
Date: 2026-01-06
Branch: 001-schedule-tools
Entity Overview
┌─────────────┐ ┌─────────────┐
│ Project │───────│ WBS │
└─────────────┘ └─────────────┘
│ │
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Activity │◄──────│ Relationship│
└─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Calendar │ (internal only)
└─────────────┘
Entities
Project
Top-level container representing a P6 project.
| Field | Type | Required | Description |
|---|---|---|---|
| proj_id | string | Yes | Unique project identifier from P6 |
| proj_short_name | string | Yes | Short display name |
| plan_start_date | datetime | No | Planned start date |
| plan_end_date | datetime | No | Planned end date |
| loaded_at | datetime | Yes | When file was loaded into server |
XER Source: PROJECT table
Validation Rules:
- proj_id must be unique within loaded data
- proj_short_name must not be empty
Activity
A unit of work in the schedule.
| Field | Type | Required | Description |
|---|---|---|---|
| task_id | string | Yes | Unique activity identifier |
| proj_id | string | Yes | Parent project reference |
| wbs_id | string | No | WBS element reference |
| 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) |
| early_end_date | datetime | No | Calculated early finish (for driving computation) |
| act_start_date | datetime | No | Actual start |
| act_end_date | datetime | No | Actual finish |
| total_float_hr_cnt | float | No | Total float in hours |
| driving_path_flag | boolean | No | True if on critical path |
| status_code | enum | No | TK_NotStart, TK_Active, TK_Complete |
XER Source: TASK table
Validation Rules:
- task_id must be unique
- task_code must not be empty
- task_name must not be empty
- If act_start_date exists, target_start_date should exist
- If act_end_date exists, act_start_date must exist
State Transitions:
TK_NotStart ──[actual start recorded]──► TK_Active
TK_Active ──[actual finish recorded]──► TK_Complete
Relationship
A dependency link between two activities.
| Field | Type | Required | Description |
|---|---|---|---|
| task_pred_id | string | Yes | Unique relationship identifier |
| task_id | string | Yes | Successor activity (the one being constrained) |
| pred_task_id | string | Yes | Predecessor activity (the one constraining) |
| pred_type | enum | Yes | PR_FS, PR_SS, PR_FF, PR_SF |
| lag_hr_cnt | float | No | Lag time in hours (can be negative) |
| driving | boolean | No | Computed - True if this relationship determines successor's early dates |
XER Source: TASKPRED table
Driving Flag Computation (not stored, computed at query time):
The driving flag indicates whether this predecessor relationship constrains the successor activity's early start date. It is computed by comparing dates:
# For FS (Finish-to-Start) relationships:
driving = (predecessor.early_end_date + lag_hr_cnt ≈ successor.early_start_date)
# With 1-hour tolerance for floating point arithmetic
This requires JOIN on activity dates when querying relationships.
Relationship Types:
| Code | Name | Meaning |
|---|---|---|
| PR_FS | Finish-to-Start | Successor starts after predecessor finishes |
| PR_SS | Start-to-Start | Successor starts after predecessor starts |
| PR_FF | Finish-to-Finish | Successor finishes after predecessor finishes |
| PR_SF | Start-to-Finish | Successor finishes after predecessor starts |
Validation Rules:
- task_id and pred_task_id must reference existing activities
- task_id must not equal pred_task_id (no self-reference)
- pred_type must be one of the four valid types
WBS (Work Breakdown Structure)
Hierarchical organization of activities.
| Field | Type | Required | Description |
|---|---|---|---|
| wbs_id | string | Yes | Unique WBS identifier |
| proj_id | string | Yes | Parent project reference |
| parent_wbs_id | string | No | Parent WBS element (null for root) |
| wbs_short_name | string | Yes | Short code |
| wbs_name | string | No | Full description |
| wbs_level | integer | No | Hierarchy depth (0 = root) |
XER Source: PROJWBS table
Validation Rules:
- wbs_id must be unique
- parent_wbs_id must reference existing WBS or be null
- No circular parent references
Calendar (Internal)
Work schedule definition. Not exposed via MCP tools.
| Field | Type | Required | Description |
|---|---|---|---|
| clndr_id | string | Yes | Unique calendar identifier |
| clndr_name | string | Yes | Calendar name |
| day_hr_cnt | float | No | Hours per work day |
| week_hr_cnt | float | No | Hours per work week |
XER Source: CALENDAR table
Note: Parsed and stored but not exposed as queryable. Used internally if duration calculations are needed in future.
PaginationMetadata
Response wrapper for paginated queries.
| Field | Type | Required | Description |
|---|---|---|---|
| total_count | integer | Yes | Total items matching query |
| offset | integer | Yes | Current offset (0-based) |
| limit | integer | Yes | Items per page |
| has_more | boolean | Yes | True if more items exist |
SQLite Schema
-- Projects
CREATE TABLE projects (
proj_id TEXT PRIMARY KEY,
proj_short_name TEXT NOT NULL,
plan_start_date TEXT,
plan_end_date TEXT,
loaded_at TEXT NOT NULL DEFAULT (datetime('now'))
);
-- Activities
CREATE TABLE activities (
task_id TEXT PRIMARY KEY,
proj_id TEXT NOT NULL,
wbs_id TEXT,
task_code TEXT NOT NULL,
task_name TEXT NOT NULL,
task_type TEXT NOT NULL,
target_start_date TEXT,
target_end_date TEXT,
early_start_date TEXT, -- Used for driving relationship computation
early_end_date TEXT, -- Used for driving relationship computation
act_start_date TEXT,
act_end_date TEXT,
total_float_hr_cnt REAL,
driving_path_flag INTEGER DEFAULT 0,
status_code TEXT,
FOREIGN KEY (proj_id) REFERENCES projects(proj_id),
FOREIGN KEY (wbs_id) REFERENCES wbs(wbs_id)
);
-- Relationships
CREATE TABLE relationships (
task_pred_id TEXT PRIMARY KEY,
task_id TEXT NOT NULL,
pred_task_id TEXT NOT NULL,
pred_type TEXT NOT NULL,
lag_hr_cnt REAL DEFAULT 0,
FOREIGN KEY (task_id) REFERENCES activities(task_id),
FOREIGN KEY (pred_task_id) REFERENCES activities(task_id)
);
-- WBS
CREATE TABLE wbs (
wbs_id TEXT PRIMARY KEY,
proj_id TEXT NOT NULL,
parent_wbs_id TEXT,
wbs_short_name TEXT NOT NULL,
wbs_name TEXT,
FOREIGN KEY (proj_id) REFERENCES projects(proj_id),
FOREIGN KEY (parent_wbs_id) REFERENCES wbs(wbs_id)
);
-- Calendars (internal)
CREATE TABLE calendars (
clndr_id TEXT PRIMARY KEY,
clndr_name TEXT NOT NULL,
day_hr_cnt REAL,
week_hr_cnt REAL
);
-- Indexes
CREATE INDEX idx_activities_proj ON activities(proj_id);
CREATE INDEX idx_activities_wbs ON activities(wbs_id);
CREATE INDEX idx_activities_type ON activities(task_type);
CREATE INDEX idx_activities_critical ON activities(driving_path_flag) WHERE driving_path_flag = 1;
CREATE INDEX idx_activities_dates ON activities(target_start_date, target_end_date);
CREATE INDEX idx_relationships_task ON relationships(task_id);
CREATE INDEX idx_relationships_pred ON relationships(pred_task_id);
CREATE INDEX idx_wbs_parent ON wbs(parent_wbs_id);
CREATE INDEX idx_wbs_proj ON wbs(proj_id);
Date Handling
All dates stored as ISO8601 strings in SQLite for portability:
- Format:
YYYY-MM-DDTHH:MM:SS - Timezone: Preserved from XER if present, otherwise naive
- Comparisons: String comparison works correctly with ISO8601
XER date format: YYYY-MM-DD HH:MM (space-separated, no seconds)
Conversion on import: Add :00 for seconds, replace space with T