# 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: ```python # 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 ```sql -- 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`