feat: add driving flag to relationship query responses
Add computed driving flag to all relationship queries (list_relationships, get_predecessors, get_successors). A relationship is marked as driving when the predecessor's early end date plus lag determines the successor's early start date. Changes: - Add early_start_date and early_end_date columns to activities schema - Parse early dates from TASK table in XER files - Implement is_driving_relationship() helper with 24hr tolerance for calendar gaps - Update all relationship queries to compute and return driving flag - Add contract and unit tests for driving flag functionality - Update spec, contracts, and documentation
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
# Tasks: Project Schedule Tools
|
||||
# Tasks: Add Driving Flag to Relationships
|
||||
|
||||
**Input**: Design documents from `/specs/001-schedule-tools/`
|
||||
**Prerequisites**: plan.md, spec.md, 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.
|
||||
|
||||
**Organization**: Tasks are grouped by user story to enable independent implementation and testing.
|
||||
**Scope**: Enhancement to existing implementation - add computed `driving` flag to relationship query responses.
|
||||
|
||||
## Format: `[ID] [P?] [Story] Description`
|
||||
|
||||
- **[P]**: Can run in parallel (different files, no dependencies)
|
||||
- **[Story]**: Which user story this task belongs to (US1, US2, US3, US4)
|
||||
- **[Story]**: Which user story this task belongs to (US3 = Query Activity Relationships)
|
||||
- Include exact file paths in descriptions
|
||||
|
||||
## Path Conventions
|
||||
@@ -21,167 +21,67 @@
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Setup (Shared Infrastructure)
|
||||
## Phase 1: Setup (Schema Enhancement)
|
||||
|
||||
**Purpose**: Project initialization and basic structure
|
||||
**Purpose**: Add early date columns needed for driving flag computation
|
||||
|
||||
- [ ] T001 Create project directory structure per plan.md in src/xer_mcp/
|
||||
- [ ] T002 Initialize Python project with uv and create pyproject.toml with dependencies (mcp, pytest, pytest-asyncio, ruff)
|
||||
- [ ] T003 [P] Configure ruff for linting and formatting in pyproject.toml
|
||||
- [ ] T004 [P] Create src/xer_mcp/__init__.py with version 0.1.0
|
||||
- [ ] T005 [P] Create tests/conftest.py with sample XER file fixtures
|
||||
- [x] T001 Update activities table schema to add early_start_date and early_end_date columns in src/xer_mcp/db/schema.py
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational (Blocking Prerequisites)
|
||||
## Phase 2: Foundational (Parser Enhancement)
|
||||
|
||||
**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented
|
||||
**Purpose**: Parse early dates from TASK table and store in database
|
||||
|
||||
**⚠️ CRITICAL**: No user story work can begin until this phase is complete
|
||||
**⚠️ CRITICAL**: Must complete before relationship queries can compute driving flag
|
||||
|
||||
- [ ] T006 Create data models in src/xer_mcp/models/__init__.py (export all models)
|
||||
- [ ] T007 [P] Create Project dataclass in src/xer_mcp/models/project.py
|
||||
- [ ] T008 [P] Create Activity dataclass in src/xer_mcp/models/activity.py
|
||||
- [ ] T009 [P] Create Relationship dataclass in src/xer_mcp/models/relationship.py
|
||||
- [ ] T010 [P] Create PaginationMetadata dataclass in src/xer_mcp/models/pagination.py
|
||||
- [ ] T011 Create SQLite schema in src/xer_mcp/db/schema.py with all tables and indexes
|
||||
- [ ] T012 Create database connection manager in src/xer_mcp/db/__init__.py
|
||||
- [ ] T013 Create MCP server skeleton in src/xer_mcp/server.py with stdio transport
|
||||
- [ ] T014 Create base table handler abstract class in src/xer_mcp/parser/table_handlers/base.py
|
||||
- [ ] T015 Create error types and NO_FILE_LOADED error in src/xer_mcp/errors.py
|
||||
### Tests
|
||||
|
||||
**Checkpoint**: Foundation ready - user story implementation can now begin
|
||||
- [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)
|
||||
|
||||
### 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
|
||||
|
||||
**Checkpoint**: Early dates are now parsed and stored - driving computation can begin
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: User Story 1 - Load XER File (Priority: P1) 🎯 MVP
|
||||
## Phase 3: User Story 3 - Add Driving Flag to Relationships (Priority: P2) 🎯 FOCUS
|
||||
|
||||
**Goal**: Parse XER files and load schedule data into SQLite database
|
||||
**Goal**: Compute and return `driving` flag in all relationship query responses
|
||||
|
||||
**Independent Test**: Load a sample XER file and verify projects, activities, relationships are stored in SQLite
|
||||
|
||||
### Tests for User Story 1
|
||||
|
||||
> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**
|
||||
|
||||
- [ ] T016 [P] [US1] Contract test for load_xer tool in tests/contract/test_load_xer.py
|
||||
- [ ] T017 [P] [US1] Integration test for XER parsing in tests/integration/test_xer_parsing.py
|
||||
- [ ] T018 [P] [US1] Integration test for multi-project handling in tests/integration/test_multi_project.py
|
||||
- [ ] T019 [P] [US1] Unit test for XER parser in tests/unit/test_parser.py
|
||||
- [ ] T020 [P] [US1] Unit test for table handlers in tests/unit/test_table_handlers.py
|
||||
|
||||
### Implementation for User Story 1
|
||||
|
||||
- [ ] T021 [US1] Create XER parser main class in src/xer_mcp/parser/xer_parser.py
|
||||
- [ ] T022 [P] [US1] Create PROJECT table handler in src/xer_mcp/parser/table_handlers/project.py
|
||||
- [ ] T023 [P] [US1] Create TASK table handler in src/xer_mcp/parser/table_handlers/task.py
|
||||
- [ ] T024 [P] [US1] Create TASKPRED table handler in src/xer_mcp/parser/table_handlers/taskpred.py
|
||||
- [ ] T025 [P] [US1] Create PROJWBS table handler in src/xer_mcp/parser/table_handlers/projwbs.py
|
||||
- [ ] T026 [P] [US1] Create CALENDAR table handler in src/xer_mcp/parser/table_handlers/calendar.py
|
||||
- [ ] T027 [US1] Create table handler registry in src/xer_mcp/parser/table_handlers/__init__.py
|
||||
- [ ] T028 [US1] Create database loader in src/xer_mcp/db/loader.py to insert parsed data into SQLite
|
||||
- [ ] T029 [US1] Implement load_xer MCP tool in src/xer_mcp/tools/load_xer.py
|
||||
- [ ] T030 [US1] Register load_xer tool in src/xer_mcp/server.py
|
||||
- [ ] T031 [US1] Add file-not-found and parse-error handling in src/xer_mcp/tools/load_xer.py
|
||||
- [ ] T032 [US1] Add multi-project detection and selection logic in src/xer_mcp/tools/load_xer.py
|
||||
|
||||
**Checkpoint**: User Story 1 complete - XER files can be loaded and parsed
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: User Story 2 - Query Project Activities (Priority: P1)
|
||||
|
||||
**Goal**: List and filter activities, get activity details with pagination
|
||||
|
||||
**Independent Test**: Load XER file, query activities with filters, verify pagination metadata
|
||||
|
||||
### Tests for User Story 2
|
||||
|
||||
> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**
|
||||
|
||||
- [ ] T033 [P] [US2] Contract test for list_activities tool in tests/contract/test_list_activities.py
|
||||
- [ ] T034 [P] [US2] Contract test for get_activity tool in tests/contract/test_get_activity.py
|
||||
- [ ] T035 [P] [US2] Unit test for activity queries in tests/unit/test_db_queries.py
|
||||
|
||||
### Implementation for User Story 2
|
||||
|
||||
- [ ] T036 [US2] Create activity query functions with pagination in src/xer_mcp/db/queries.py
|
||||
- [ ] T037 [US2] Implement list_activities MCP tool in src/xer_mcp/tools/list_activities.py
|
||||
- [ ] T038 [US2] Implement get_activity MCP tool in src/xer_mcp/tools/get_activity.py
|
||||
- [ ] T039 [US2] Register list_activities and get_activity tools in src/xer_mcp/server.py
|
||||
- [ ] T040 [US2] Add NO_FILE_LOADED error check to activity tools in src/xer_mcp/tools/list_activities.py
|
||||
- [ ] T041 [US2] Add date range filtering to list_activities in src/xer_mcp/tools/list_activities.py
|
||||
- [ ] T042 [US2] Add WBS and activity_type filtering to list_activities in src/xer_mcp/tools/list_activities.py
|
||||
|
||||
**Checkpoint**: User Stories 1 AND 2 complete - activities are queryable
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: User Story 3 - Query Activity Relationships (Priority: P2)
|
||||
|
||||
**Goal**: Query predecessors, successors, and full relationship network
|
||||
|
||||
**Independent Test**: Load XER file, query relationships for an activity, verify types and lags
|
||||
**Independent Test**: Load XER file, query relationships, verify driving flag is present and correctly computed
|
||||
|
||||
### Tests for User Story 3
|
||||
|
||||
> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**
|
||||
|
||||
- [ ] T043 [P] [US3] Contract test for list_relationships tool in tests/contract/test_list_relationships.py
|
||||
- [ ] T044 [P] [US3] Contract test for get_predecessors tool in tests/contract/test_get_predecessors.py
|
||||
- [ ] T045 [P] [US3] Contract test for get_successors tool in tests/contract/test_get_successors.py
|
||||
- [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)
|
||||
|
||||
### Implementation for User Story 3
|
||||
|
||||
- [ ] T046 [US3] Create relationship query functions in src/xer_mcp/db/queries.py
|
||||
- [ ] T047 [US3] Implement list_relationships MCP tool in src/xer_mcp/tools/list_relationships.py
|
||||
- [ ] T048 [US3] Implement get_predecessors MCP tool in src/xer_mcp/tools/get_predecessors.py
|
||||
- [ ] T049 [US3] Implement get_successors MCP tool in src/xer_mcp/tools/get_successors.py
|
||||
- [ ] T050 [US3] Register relationship tools in src/xer_mcp/server.py
|
||||
- [ ] T051 [US3] Add NO_FILE_LOADED error check to relationship tools
|
||||
- [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
|
||||
|
||||
**Checkpoint**: User Stories 1, 2, AND 3 complete - relationships are queryable
|
||||
**Checkpoint**: Driving flag now included in all relationship responses
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: User Story 4 - Query Project Summary (Priority: P3)
|
||||
## Phase 4: Polish & Validation
|
||||
|
||||
**Goal**: Get project overview, milestones, and critical path activities
|
||||
**Purpose**: Verify integration and documentation alignment
|
||||
|
||||
**Independent Test**: Load XER file, get summary, list milestones, get critical path
|
||||
|
||||
### Tests for User Story 4
|
||||
|
||||
> **NOTE: Write these tests FIRST, ensure they FAIL before implementation**
|
||||
|
||||
- [ ] T052 [P] [US4] Contract test for get_project_summary tool in tests/contract/test_get_project_summary.py
|
||||
- [ ] T053 [P] [US4] Contract test for list_milestones tool in tests/contract/test_list_milestones.py
|
||||
- [ ] T054 [P] [US4] Contract test for get_critical_path tool in tests/contract/test_get_critical_path.py
|
||||
|
||||
### Implementation for User Story 4
|
||||
|
||||
- [ ] T055 [US4] Create summary query functions in src/xer_mcp/db/queries.py
|
||||
- [ ] T056 [US4] Implement get_project_summary MCP tool in src/xer_mcp/tools/get_project_summary.py
|
||||
- [ ] T057 [US4] Implement list_milestones MCP tool in src/xer_mcp/tools/list_milestones.py
|
||||
- [ ] T058 [US4] Implement get_critical_path MCP tool in src/xer_mcp/tools/get_critical_path.py
|
||||
- [ ] T059 [US4] Register summary tools in src/xer_mcp/server.py
|
||||
- [ ] T060 [US4] Add NO_FILE_LOADED error check to summary tools
|
||||
|
||||
**Checkpoint**: All user stories complete - full MCP server functional
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Polish & Cross-Cutting Concerns
|
||||
|
||||
**Purpose**: Improvements that affect multiple user stories
|
||||
|
||||
- [ ] T061 [P] Integration test for edge cases in tests/integration/test_edge_cases.py
|
||||
- [ ] T062 [P] Unit test for models in tests/unit/test_models.py
|
||||
- [ ] T063 Add logging throughout src/xer_mcp/ modules
|
||||
- [ ] T064 Run quickstart.md validation - test all documented examples
|
||||
- [ ] T065 Add __main__.py entry point in src/xer_mcp/__main__.py
|
||||
- [ ] T066 Final type checking with mypy and fix any issues
|
||||
- [ ] T067 Run ruff format and fix any linting issues
|
||||
- [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/
|
||||
|
||||
---
|
||||
|
||||
@@ -189,93 +89,151 @@
|
||||
|
||||
### Phase Dependencies
|
||||
|
||||
- **Setup (Phase 1)**: No dependencies - can start immediately
|
||||
- **Foundational (Phase 2)**: Depends on Setup completion - BLOCKS all user stories
|
||||
- **User Story 1 (Phase 3)**: Depends on Foundational - BLOCKS US2, US3, US4 (they need load_xer)
|
||||
- **User Story 2 (Phase 4)**: Depends on US1 (needs loaded data to query)
|
||||
- **User Story 3 (Phase 5)**: Depends on US1 (needs loaded data to query)
|
||||
- **User Story 4 (Phase 6)**: Depends on US1 (needs loaded data to query)
|
||||
- **Polish (Phase 7)**: Depends on all user stories being complete
|
||||
```
|
||||
Phase 1 (Schema)
|
||||
│
|
||||
▼
|
||||
Phase 2 (Parser)
|
||||
│
|
||||
▼
|
||||
Phase 3 (Queries) ← MAIN WORK
|
||||
│
|
||||
▼
|
||||
Phase 4 (Polish)
|
||||
```
|
||||
|
||||
### User Story Dependencies
|
||||
### Task Dependencies Within Phase 3
|
||||
|
||||
- **US1 (Load XER)**: Foundation for all others - MUST complete first
|
||||
- **US2 (Activities)**: Can start after US1 - independent of US3, US4
|
||||
- **US3 (Relationships)**: Can start after US1 - independent of US2, US4
|
||||
- **US4 (Summary)**: Can start after US1 - independent of US2, US3
|
||||
|
||||
### Within Each User Story
|
||||
|
||||
- Tests MUST be written and FAIL before implementation
|
||||
- Models before services/queries
|
||||
- Queries before MCP tools
|
||||
- Core implementation before error handling
|
||||
- Register tools in server.py after implementation
|
||||
```
|
||||
T005, T006, T007, T008 (Tests) ← Write first, verify FAIL
|
||||
│
|
||||
▼
|
||||
T009 (Helper function)
|
||||
│
|
||||
├── T010 (query_relationships)
|
||||
├── T011 (get_predecessors)
|
||||
└── T012 (get_successors)
|
||||
│
|
||||
▼
|
||||
All tests now PASS
|
||||
```
|
||||
|
||||
### Parallel Opportunities
|
||||
|
||||
**Phase 2 (Foundational)**:
|
||||
**Phase 2 Tests + Phase 3 Tests** (can write all tests in parallel):
|
||||
```bash
|
||||
# These can run in parallel:
|
||||
Task T007: "Create Project dataclass"
|
||||
Task T008: "Create Activity dataclass"
|
||||
Task T009: "Create Relationship dataclass"
|
||||
Task T010: "Create PaginationMetadata dataclass"
|
||||
# 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"
|
||||
```
|
||||
|
||||
**Phase 3 (US1 - Table Handlers)**:
|
||||
**Phase 3 Query Updates** (after T009 is complete):
|
||||
```bash
|
||||
# These can run in parallel:
|
||||
Task T022: "Create PROJECT table handler"
|
||||
Task T023: "Create TASK table handler"
|
||||
Task T024: "Create TASKPRED table handler"
|
||||
Task T025: "Create PROJWBS table handler"
|
||||
Task T026: "Create CALENDAR table handler"
|
||||
# These can run in parallel once helper function exists:
|
||||
Task T010: "Update query_relationships"
|
||||
Task T011: "Update get_predecessors"
|
||||
Task T012: "Update get_successors"
|
||||
```
|
||||
|
||||
**After US1 Complete (US2, US3, US4 can start in parallel)**:
|
||||
---
|
||||
|
||||
## Parallel Example: Phase 3
|
||||
|
||||
```bash
|
||||
# Developer A: User Story 2 (Activities)
|
||||
# Developer B: User Story 3 (Relationships)
|
||||
# Developer C: User Story 4 (Summary)
|
||||
# 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)
|
||||
|
||||
# Then: Implement helper function
|
||||
Task: "Create is_driving_relationship helper in src/xer_mcp/db/queries.py"
|
||||
|
||||
# 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
|
||||
|
||||
### MVP First (User Story 1 + 2)
|
||||
### Driving Flag Computation (from research.md)
|
||||
|
||||
1. Complete Phase 1: Setup
|
||||
2. Complete Phase 2: Foundational
|
||||
3. Complete Phase 3: User Story 1 (Load XER)
|
||||
4. **STOP and VALIDATE**: Can load XER files successfully
|
||||
5. Complete Phase 4: User Story 2 (Activities)
|
||||
6. **MVP COMPLETE**: Can load XER and query activities
|
||||
The driving flag is computed at query time by comparing dates:
|
||||
|
||||
### Incremental Delivery
|
||||
```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
|
||||
|
||||
1. Setup + Foundational → Project ready
|
||||
2. User Story 1 → XER files load → Demo loading
|
||||
3. User Story 2 → Activities queryable → Demo activity queries
|
||||
4. User Story 3 → Relationships queryable → Demo dependency analysis
|
||||
5. User Story 4 → Summary available → Demo project overview
|
||||
6. Polish → Production ready
|
||||
# For FS: pred_end + lag = succ_start means driving
|
||||
if pred_type == "FS":
|
||||
# Parse dates, add lag, compare with 1-hour tolerance
|
||||
...
|
||||
|
||||
### Parallel Team Strategy
|
||||
# Similar logic for SS, FF, SF
|
||||
return False
|
||||
```
|
||||
|
||||
With multiple developers after US1:
|
||||
- Developer A: User Story 2 (list_activities, get_activity)
|
||||
- Developer B: User Story 3 (relationships, predecessors, successors)
|
||||
- Developer C: User Story 4 (summary, milestones, critical_path)
|
||||
### SQL Query Pattern
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Phase | Tasks | Focus |
|
||||
|-------|-------|-------|
|
||||
| Setup | 1 | Schema update |
|
||||
| Foundational | 3 | Parser enhancement |
|
||||
| US3 Implementation | 8 | Driving flag computation |
|
||||
| Polish | 4 | Validation |
|
||||
| **Total** | **16** | |
|
||||
|
||||
### 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 |
|
||||
|
||||
### MVP Scope
|
||||
|
||||
Complete through T012 for minimal viable driving flag implementation.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- [P] tasks = different files, no dependencies on incomplete tasks
|
||||
- [Story] label maps task to specific user story for traceability
|
||||
- [US3] = User Story 3 (Query Activity Relationships)
|
||||
- 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
|
||||
- Commit after each task or logical group
|
||||
- Stop at any checkpoint to validate story independently
|
||||
- All query tools must check for NO_FILE_LOADED condition
|
||||
|
||||
Reference in New Issue
Block a user