# Implementation Plan

## Validation Checklist

- [x] All specification file paths are correct and exist
- [x] Context priming section is complete
- [x] All implementation phases are defined
- [x] Each phase follows TDD: Prime → Test → Implement → Validate
- [x] Dependencies between phases are clear (no circular dependencies)
- [x] Parallel work is properly tagged with `[parallel: true]`
- [x] Activity hints provided for specialist selection `[activity: type]`
- [x] Every phase references relevant SDD sections
- [x] Every test references PRD acceptance criteria
- [x] Integration & E2E tests defined in final phase
- [x] Project commands match actual project setup
- [x] A developer could follow this plan independently

---

## Specification Compliance Guidelines

### How to Ensure Specification Adherence

1. **Before Each Phase**: Complete the Pre-Implementation Specification Gate
2. **During Implementation**: Reference specific SDD sections in each task
3. **After Each Task**: Run Specification Compliance checks
4. **Phase Completion**: Verify all specification requirements are met

### Deviation Protocol

If implementation cannot follow specification exactly:
1. Document the deviation and reason
2. Get approval before proceeding
3. Update SDD if the deviation is an improvement
4. Never deviate without documentation

## Metadata Reference

- `[parallel: true]` - Tasks that can run concurrently
- `[component: component-name]` - For multi-component features
- `[ref: document/section; lines: 1, 2-3]` - Links to specifications, patterns, or interfaces and (if applicable) line(s)
- `[activity: type]` - Activity hint for specialist agent selection

---

## Context Priming

*GATE: You MUST fully read all files mentioned in this section before starting any implementation.*

**Specification**:

- `docs/specs/027-wait-time-quick-wins/product-requirements.md` - Product Requirements (5 features, 4 Must Have + 1 Could Have)
- `docs/specs/027-wait-time-quick-wins/solution-design.md` - Solution Design (6 ADRs confirmed)

**Key Design Decisions** (from SDD ADRs):

- **ADR-1**: Separate Service Layer - Create dedicated `WaitTimeFactorService` rather than inline logic
- **ADR-2**: Redis for Factor Caching - Redis as primary cache with database as source of truth
- **ADR-3**: Multiplicative Factor Combination - `base × dynamic × queue × efficiency`
- **ADR-4**: Nightly Cache Refresh - Pre-calculate at 2am store local time, 24-hour TTL
- **ADR-5**: Employee Efficiency Precedence - WhenIWork → Scheduled → All Active → Default 1.0
- **ADR-6**: Efficiency Factor as DIVISOR - Apply efficiency factor as divisor, not multiplier

**Implementation Context**:

Commands to run:
```bash
# Testing
./test.sh                                    # Run all tests
./test.sh --testsuite unit                   # Unit tests only
./test.sh tests/Unit/WaitTime/               # Run wait time tests specifically

# Static Analysis
cd userfrosting && ./vendor/bin/phpstan analyse src/BuyerKiosk/WaitTime/

# Database Migrations
php userfrosting/conductor run               # Run pending migrations

# TaskEngine (for job testing)
php userfrosting/bin/task job:list
php userfrosting/bin/task job:dispatch wait-time-cache-refresh --store=ou00
php userfrosting/bin/task job:dispatch wait-time-calibration --store=ou00

# Cache Operations (debugging)
redis-cli KEYS "waittime:*"
redis-cli DEL "waittime:ou00:*"
```

Patterns to follow:
- `userfrosting/src/BuyerKiosk/Analytics/Services/WaitTimeService.php` - Existing heatmap/caching patterns
- `userfrosting/src/BuyerKiosk/Scheduling/AiScheduling/Services/AiSuggestionCacheService.php` - Redis caching with TTL
- `userfrosting/src/BuyerKiosk/TaskEngine/Domain/Job/BaseJob.php` - Background job pattern
- `userfrosting/src/BuyerKiosk/Workbook/WhenIWorkSchedule.php` - On-duty employee fetching

Key interfaces to implement:
- `WaitTimeFactorService::calculateFactors(Store $store): FactorResult` `[ref: SDD; lines: 415-420]`
- `WaitTimeFactorRepository::getHistoricalAverageBySlot()` `[ref: SDD; lines: 432-434]`
- `WaitTimeFactorCache::get/set/warmAll()` `[ref: SDD; lines: 425-428]`

---

## Risks & Mitigations

| Risk | Impact | Likelihood | Mitigation |
|------|--------|------------|------------|
| **Redis unavailable during cache refresh** | Medium | Low | Graceful fallback to static factors; retry logic in jobs |
| **Sparse historical data for new stores** | Medium | Medium | Automatic fallback to static factors when < 50 transactions |
| **Timezone handling errors** | High | Medium | Explicit timezone conversion tests; use store's timezone consistently |
| **MySQL dayOfWeek conversion mistakes** | High | Medium | Unit tests for 0=Sunday (PHP) vs 1=Sunday (MySQL) conversion |
| **Job runtime overruns** | Medium | Low | Performance budgets defined in SDD; batch processing; monitoring |
| **WhenIWork API failures** | Low | Medium | Fallback chain tested: WhenIWork → Scheduled → Active → Default |
| **Auto-calibration causes unexpected changes** | Medium | Low | 15% threshold, mpcLocked flag, change logging |

---

## Implementation Phases

### Phase 1: Foundation - Database Migrations & Models

*Dependencies: None (first phase)*
*Delivers: Database schema, DTOs, and interface contracts required by all subsequent phases*

**Definition of Done:**
- [ ] All 5 migrations created and applied to test database
- [ ] All 3 DTOs implemented with passing unit tests
- [ ] Store model extended with feature flag getters
- [ ] Interface contracts defined for repository and cache (for Phase 2 mocking)
- [ ] PHPStan clean on all new code

- [ ] T1 Phase 1: Foundation - Database Migrations & Models

    - [ ] T1.1 Prime Context
        - [ ] T1.1.1 Read SDD Data Storage Changes section `[ref: SDD; lines: 346-407]`
        - [ ] T1.1.2 Review existing migration patterns `[ref: userfrosting/migrations/input/*.json]`
        - [ ] T1.1.3 Review existing DTO patterns (e.g., CalibrationResult) `[ref: SDD; lines: 519-560]`

    - [ ] T1.2 Write Tests
        - [ ] T1.2.1 Write unit tests for FactorResult DTO - getCombinedFactor(), isUsingFallbacks() `[ref: PRD; lines: 59-78]` `[activity: backend-test]`
        - [ ] T1.2.2 Write unit tests for WaitTimePrediction DTO - toArray(), getRange() `[activity: backend-test]`
        - [ ] T1.2.3 Write unit tests for CalibrationResult DTO `[activity: backend-test]`

    - [ ] T1.3 Implement Database Migrations `[activity: database]`
        - [ ] T1.3.1 Create migration: `20260108_020_wait_time_factors.json` - waitTimeFactorCache table `[ref: SDD; lines: 349-359]`
        - [ ] T1.3.2 Create migration: `20260108_021_wait_time_predictions.json` - waitTimePredictions table with actualMinutes column `[ref: SDD; lines: 361-376]`
        - [ ] T1.3.3 Create migration: `20260108_022_stores_wait_time_flags.json` - Add feature flag columns to stores `[ref: SDD; lines: 402-407]`
        - [ ] T1.3.4 Create migration: `20260108_023_wait_time_accuracy_daily.json` - Daily accuracy aggregation table `[ref: SDD; lines: 378-390]`
        - [ ] T1.3.5 Create migration: `20260108_024_mpc_calibration_log.json` - Calibration change logging `[ref: SDD; lines: 392-400]`

    - [ ] T1.4 Implement Models/DTOs `[activity: backend-api]`
        - [ ] T1.4.1 Create `WaitTime/Models/FactorResult.php` - Factor calculation result DTO `[ref: SDD; lines: 522-533]`
        - [ ] T1.4.2 Create `WaitTime/Models/WaitTimePrediction.php` - Full prediction with breakdown `[ref: SDD; lines: 535-545]`
        - [ ] T1.4.3 Create `WaitTime/Models/CalibrationResult.php` - Calibration job result DTO
        - [ ] T1.4.4 Extend `Core/Store.php` - Add feature flag getters `[ref: SDD; lines: 547-560]`

    - [ ] T1.5 Define Interface Contracts (enables Phase 2 mocking) `[activity: backend-api]`
        - [ ] T1.5.1 Create `WaitTime/Contracts/WaitTimeFactorRepositoryInterface.php` `[ref: SDD; lines: 429-445]`
        - [ ] T1.5.2 Create `WaitTime/Contracts/WaitTimeFactorCacheInterface.php` `[ref: SDD; lines: 425-428]`
        - [ ] T1.5.3 Create `WaitTime/Contracts/WaitTimeAccuracyRepositoryInterface.php` `[ref: SDD; lines: 438-444]`

    - [ ] T1.6 Validate
        - [ ] T1.6.1 Run migrations on test database `[activity: database]`
        - [ ] T1.6.2 Run PHPStan on new models and interfaces `[activity: lint-code]`
        - [ ] T1.6.3 Run unit tests for DTOs `[activity: run-tests]`
        - [ ] T1.6.4 Verify Store model changes don't break existing functionality `[activity: run-tests]`
        - [ ] T1.6.5 Verify interface contracts match SDD method signatures `[activity: review-code]`

---

### Phase 2: Core Factor Calculators (Parallel)

*Dependencies: Phase 1 complete (models and interface contracts exist for mocking)*
*Delivers: Three independent calculator classes for dynamic factor, queue depth, and employee efficiency*

**Definition of Done:**
- [ ] All 3 calculators implemented with passing unit tests
- [ ] Tests mock repository/cache using interfaces from Phase 1
- [ ] Each calculator bounded within specified ranges
- [ ] Feature flag checks implemented
- [ ] PHPStan clean on all calculator code

- [ ] T2 Phase 2: Core Factor Calculators

    - [ ] T2.1 Dynamic Factor Calculator `[parallel: true]` `[component: dynamic-factor]`

        - [ ] T2.1.1 Prime Context
            - [ ] T2.1.1.1 Read PRD Feature 1 acceptance criteria `[ref: PRD; lines: 162-172]`
            - [ ] T2.1.1.2 Read SDD DynamicFactorCalculator example `[ref: SDD; lines: 627-711]`
            - [ ] T2.1.1.3 Read PRD edge cases for time slots `[ref: PRD; lines: 255-261]`
            - [ ] T2.1.1.4 Review existing WaitTimeRepository patterns `[ref: userfrosting/src/BuyerKiosk/Analytics/Repositories/WaitTimeRepository.php]`

        - [ ] T2.1.2 Write Tests `[activity: backend-test]`
            - [ ] T2.1.2.1 Test: calculates factor with sufficient data (>= 50 transactions) `[ref: PRD; lines: 170]`
            - [ ] T2.1.2.2 Test: falls back to static factor when < 50 transactions `[ref: PRD; lines: 170]`
            - [ ] T2.1.2.3 Test: expands to ±1 hour window when 30-49 transactions `[ref: PRD; lines: 258]`
            - [ ] T2.1.2.4 Test: bounds factor between 0.5 and 2.0 `[ref: PRD; lines: 169]`
            - [ ] T2.1.2.5 Test: returns static factor when feature flag disabled
            - [ ] T2.1.2.6 Test: cache hit returns cached value without DB query
            - [ ] T2.1.2.7 Test: cache miss calculates and caches result

        - [ ] T2.1.3 Implement `[activity: backend-api]`
            - [ ] T2.1.3.1 Create `WaitTime/Services/DynamicFactorCalculator.php` `[ref: SDD; lines: 627-711]`
            - [ ] T2.1.3.2 Implement `calculate(Store $store, DateTime $time): float`
            - [ ] T2.1.3.3 Implement threshold checking with expansion logic
            - [ ] T2.1.3.4 Implement factor bounding (0.5-2.0)

        - [ ] T2.1.4 Validate
            - [ ] T2.1.4.1 Run unit tests `[activity: run-tests]`
            - [ ] T2.1.4.2 Run PHPStan `[activity: lint-code]`
            - [ ] T2.1.4.3 Verify PRD Feature 1 acceptance criteria `[activity: business-acceptance]`

    - [ ] T2.2 Queue Depth Calculator `[parallel: true]` `[component: queue-depth]`

        - [ ] T2.2.1 Prime Context
            - [ ] T2.2.1.1 Read PRD Feature 2 acceptance criteria `[ref: PRD; lines: 175-182]`
            - [ ] T2.2.1.2 Read PRD Queue Depth definition `[ref: PRD; lines: 47-56]`
            - [ ] T2.2.1.3 Read SDD Queue Depth Query Contract `[ref: SDD; lines: 449-456]`
            - [ ] T2.2.1.4 Read SDD QueueDepthCalculator example `[ref: SDD; lines: 719-739]`

        - [ ] T2.2.2 Write Tests `[activity: backend-test]`
            - [ ] T2.2.2.1 Test: queue depth = 0 returns multiplier 1.0 `[ref: PRD; lines: 180]`
            - [ ] T2.2.2.2 Test: queue depth = 10 returns multiplier 1.15 `[ref: PRD; lines: 181]`
            - [ ] T2.2.2.3 Test: queue depth = 30 returns multiplier 1.45 `[ref: PRD; lines: 182]`
            - [ ] T2.2.2.4 Test: queue depth >= 66 caps at multiplier 2.0 `[ref: PRD; lines: 179]`
            - [ ] T2.2.2.5 Test: excludes remote transactions not dropped off `[ref: PRD; lines: 49]`
            - [ ] T2.2.2.6 Test: excludes canceled, abandoned, on_hold transactions `[ref: PRD; lines: 50]`
            - [ ] T2.2.2.7 Test: database error returns default 1.0 `[ref: PRD; lines: 277]`

        - [ ] T2.2.3 Implement `[activity: backend-api]`
            - [ ] T2.2.3.1 Create `WaitTime/Services/QueueDepthCalculator.php` `[ref: SDD; lines: 719-739]`
            - [ ] T2.2.3.2 Implement `calculate(Store $store): float`
            - [ ] T2.2.3.3 Implement PRD-compliant queue depth query `[ref: SDD; lines: 449-456]`

        - [ ] T2.2.4 Validate
            - [ ] T2.2.4.1 Run unit tests `[activity: run-tests]`
            - [ ] T2.2.4.2 Run PHPStan `[activity: lint-code]`
            - [ ] T2.2.4.3 Verify PRD Feature 2 acceptance criteria `[activity: business-acceptance]`

    - [ ] T2.3 Employee Efficiency Calculator `[parallel: true]` `[component: employee-efficiency]`

        - [ ] T2.3.1 Prime Context
            - [ ] T2.3.1.1 Read PRD Feature 4 acceptance criteria `[ref: PRD; lines: 199-209]`
            - [ ] T2.3.1.2 Read SDD EmployeeEfficiencyCalculator example `[ref: SDD; lines: 745-854]`
            - [ ] T2.3.1.3 Read SDD ADR-6 efficiency factor as divisor `[ref: SDD; lines: 1219-1229]`
            - [ ] T2.3.1.4 Review WhenIWorkSchedule patterns `[ref: userfrosting/src/BuyerKiosk/Workbook/WhenIWorkSchedule.php; lines: 61-86]`

        - [ ] T2.3.2 Write Tests `[activity: backend-test]`
            - [ ] T2.3.2.1 Test: returns 1.0 when feature flag disabled
            - [ ] T2.3.2.2 Test: uses WhenIWork on-duty list when available `[ref: PRD; lines: 205]`
            - [ ] T2.3.2.3 Test: falls back to scheduled shifts when WhenIWork unavailable `[ref: PRD; lines: 206]`
            - [ ] T2.3.2.4 Test: falls back to active employees when no schedule (>= 3 with metrics) `[ref: PRD; lines: 207]`
            - [ ] T2.3.2.5 Test: returns 1.0 when no employee data meets criteria `[ref: PRD; lines: 208]`
            - [ ] T2.3.2.6 Test: excludes employees with stale metrics (> 30 days) `[ref: PRD; lines: 209]`
            - [ ] T2.3.2.7 Test: bounds factor between 0.5 and 2.0 `[ref: PRD; lines: 203]`
            - [ ] T2.3.2.8 Test: WhenIWork API error triggers fallback chain `[ref: SDD; lines: 826-832]`

        - [ ] T2.3.3 Implement `[activity: backend-api]`
            - [ ] T2.3.3.1 Create `WaitTime/Services/EmployeeEfficiencyCalculator.php` `[ref: SDD; lines: 745-854]`
            - [ ] T2.3.3.2 Implement `calculate(Store $store): float`
            - [ ] T2.3.3.3 Implement `getOnDutyEmployees()` with full fallback chain `[ref: SDD; lines: 808-854]`
            - [ ] T2.3.3.4 Implement `filterFreshMetrics()` with 30-day staleness check

        - [ ] T2.3.4 Validate
            - [ ] T2.3.4.1 Run unit tests `[activity: run-tests]`
            - [ ] T2.3.4.2 Run PHPStan `[activity: lint-code]`
            - [ ] T2.3.4.3 Verify PRD Feature 4 acceptance criteria `[activity: business-acceptance]`

---

### Phase 3: Data Layer - Repository & Cache

*Dependencies: Phase 1 complete (database schema and interfaces exist)*
*Delivers: Data access layer for factor calculations and caching*

**Definition of Done:**
- [ ] All repositories implement interfaces from Phase 1
- [ ] All cache operations working with Redis
- [ ] Timezone conversion tests passing (PHP 0=Sunday vs MySQL 1=Sunday)
- [ ] Data quality logging implemented per SDD contract
- [ ] waitTimeFactorCache table read/write operations working
- [ ] PHPStan clean on all data layer code

- [ ] T3 Phase 3: Data Layer - Repository & Cache

    - [ ] T3.1 Wait Time Factor Repository `[parallel: true]` `[component: repository]`

        - [ ] T3.1.1 Prime Context
            - [ ] T3.1.1.1 Read SDD Repository methods specification `[ref: SDD; lines: 429-445]`
            - [ ] T3.1.1.2 Read SDD Query Contracts `[ref: SDD; lines: 449-515]`
            - [ ] T3.1.1.3 Review existing WaitTimeRepository patterns `[ref: userfrosting/src/BuyerKiosk/Analytics/Repositories/WaitTimeRepository.php]`

        - [ ] T3.1.2 Write Tests `[activity: backend-test]`
            - [ ] T3.1.2.1 Test: getHistoricalAverageBySlot returns average for time slot
            - [ ] T3.1.2.2 Test: getHistoricalAverageByExpandedSlot uses ±1 hour window
            - [ ] T3.1.2.3 Test: getOverallAverage calculates 30-day rolling average
            - [ ] T3.1.2.4 Test: getSlotTransactionCount returns count for threshold checking
            - [ ] T3.1.2.5 Test: getCurrentQueueDepth applies PRD filters exactly `[ref: PRD; lines: 47-56]`
            - [ ] T3.1.2.6 Test: getAverageContainerTime for calibration job
            - [ ] T3.1.2.7 Test: dayOfWeek conversion handles PHP 0=Sunday vs MySQL 1=Sunday `[ref: SDD; lines: 1257]`
            - [ ] T3.1.2.8 Test: timezone-aware queries use store's timezone correctly
            - [ ] T3.1.2.9 Test: saveCachedFactor persists to waitTimeFactorCache table
            - [ ] T3.1.2.10 Test: getCachedFactors retrieves from waitTimeFactorCache table

        - [ ] T3.1.3 Implement `[activity: backend-api]`
            - [ ] T3.1.3.1 Create `WaitTime/Repositories/WaitTimeFactorRepository.php`
            - [ ] T3.1.3.2 Implement all methods from SDD specification `[ref: SDD; lines: 429-445]`
            - [ ] T3.1.3.3 Ensure SQL matches PRD definitions exactly `[ref: SDD; lines: 449-456]`
            - [ ] T3.1.3.4 Handle MySQL dayOfWeek conversion (MySQL uses 1=Sunday vs PHP 0=Sunday) `[ref: SDD; lines: 1257]`
            - [ ] T3.1.3.5 Implement `saveCachedFactor()` to persist to waitTimeFactorCache table `[ref: SDD; lines: 349-359]`
            - [ ] T3.1.3.6 Implement `getCachedFactors()` to read from waitTimeFactorCache table
            - [ ] T3.1.3.7 Implement `clearExpiredFactors()` for cache maintenance

        - [ ] T3.1.4 Validate
            - [ ] T3.1.4.1 Run unit tests with PdoMockBuilder `[activity: run-tests]`
            - [ ] T3.1.4.2 Run PHPStan `[activity: lint-code]`
            - [ ] T3.1.4.3 Verify timezone handling with different store timezones `[activity: run-tests]`

    - [ ] T3.2 Wait Time Accuracy Repository `[parallel: true]` `[component: accuracy-repository]`

        - [ ] T3.2.1 Prime Context
            - [ ] T3.2.1.1 Read PRD Feature 5 acceptance criteria `[ref: PRD; lines: 215-220]`
            - [ ] T3.2.1.2 Read SDD Actual Wait Time Calculation Contract `[ref: SDD; lines: 458-515]`
            - [ ] T3.2.1.3 Read PRD Wait Time Edge Cases `[ref: PRD; lines: 36-44]`

        - [ ] T3.2.2 Write Tests `[activity: backend-test]`
            - [ ] T3.2.2.1 Test: calculateActualWaitTime excludes abandoned transactions `[ref: PRD; lines: 38]`
            - [ ] T3.2.2.2 Test: calculateActualWaitTime excludes canceled transactions `[ref: PRD; lines: 39]`
            - [ ] T3.2.2.3 Test: calculateActualWaitTime falls back to timeStarted when sortStarted invalid `[ref: PRD; lines: 41]`
            - [ ] T3.2.2.4 Test: calculateActualWaitTime returns null when both timestamps invalid `[ref: PRD; lines: 42]`
            - [ ] T3.2.2.5 Test: calculateActualWaitTime caps at 480 minutes `[ref: PRD; lines: 43]`
            - [ ] T3.2.2.6 Test: backfillActualWaitTimes processes pending predictions
            - [ ] T3.2.2.7 Test: aggregateDailyAccuracy calculates MAPE correctly
            - [ ] T3.2.2.8 Test: pruneOldRecords respects 12-month retention `[ref: PRD; lines: 220]`
            - [ ] T3.2.2.9 Test: logDataQualityIssue called when both timestamps invalid `[ref: SDD; lines: 483-485]`
            - [ ] T3.2.2.10 Test: logOutlier called when wait > 480 minutes `[ref: SDD; lines: 494-497]`
            - [ ] T3.2.2.11 Test: negative wait time logged as data quality issue `[ref: SDD; lines: 500-503]`

        - [ ] T3.2.3 Implement `[activity: backend-api]`
            - [ ] T3.2.3.1 Create `WaitTime/Repositories/WaitTimeAccuracyRepository.php`
            - [ ] T3.2.3.2 Implement `calculateActualWaitTime()` per SDD contract `[ref: SDD; lines: 465-515]`
            - [ ] T3.2.3.3 Implement `backfillActualWaitTimes()` for completed transactions
            - [ ] T3.2.3.4 Implement `aggregateDailyAccuracy()` with MAPE calculation
            - [ ] T3.2.3.5 Implement `pruneOldRecords()` with 12-month retention
            - [ ] T3.2.3.6 Implement `logDataQualityIssue()` for invalid timestamps per SDD contract `[ref: SDD; lines: 483-485]`
            - [ ] T3.2.3.7 Implement `logOutlier()` for 480+ minute waits per SDD contract `[ref: SDD; lines: 494-497]`
            - [ ] T3.2.3.8 Implement `isValidTimestamp()` helper per SDD contract `[ref: SDD; lines: 509-514]`

        - [ ] T3.2.4 Validate
            - [ ] T3.2.4.1 Run unit tests `[activity: run-tests]`
            - [ ] T3.2.4.2 Verify PRD Feature 5 edge cases covered `[activity: business-acceptance]`
            - [ ] T3.2.4.3 Verify data quality logging produces expected log entries `[activity: run-tests]`

    - [ ] T3.3 Wait Time Factor Cache `[parallel: true]` `[component: cache]`

        - [ ] T3.3.1 Prime Context
            - [ ] T3.3.1.1 Read SDD WaitTimeFactorCache methods `[ref: SDD; lines: 425-428]`
            - [ ] T3.3.1.2 Read SDD caching integration points `[ref: SDD; lines: 577-580]`
            - [ ] T3.3.1.3 Review existing Redis caching patterns `[ref: userfrosting/src/BuyerKiosk/Scheduling/AiScheduling/Services/AiSuggestionCacheService.php]`

        - [ ] T3.3.2 Write Tests `[activity: backend-test]`
            - [ ] T3.3.2.1 Test: get returns cached factor when exists
            - [ ] T3.3.2.2 Test: get returns null when cache miss
            - [ ] T3.3.2.3 Test: set stores factor with correct TTL (86400 seconds)
            - [ ] T3.3.2.4 Test: warmAll pre-populates all 168 time slots (7 days × 24 hours)
            - [ ] T3.3.2.5 Test: invalidate removes all store factors
            - [ ] T3.3.2.6 Test: key format matches SDD pattern `[ref: SDD; lines: 579]`

        - [ ] T3.3.3 Implement `[activity: backend-api]`
            - [ ] T3.3.3.1 Create `WaitTime/Cache/WaitTimeFactorCache.php`
            - [ ] T3.3.3.2 Implement `get(string $typeNum, int $dayOfWeek, int $hour): ?float`
            - [ ] T3.3.3.3 Implement `set(...)` with Predis positional args `[ref: SDD; lines: 1259]`
            - [ ] T3.3.3.4 Implement `warmAll(string $typeNum): void`
            - [ ] T3.3.3.5 Implement `invalidate(string $typeNum): void`

        - [ ] T3.3.4 Validate
            - [ ] T3.3.4.1 Run unit tests with RedisMock `[activity: run-tests]`
            - [ ] T3.3.4.2 Run PHPStan `[activity: lint-code]`

---

### Phase 4: Service Layer Integration

*Dependencies: Phases 2 and 3 complete (calculators and data layer exist)*
*Delivers: Orchestration service and integration with EstimatedWaitTime*

**Definition of Done:**
- [ ] WaitTimeFactorService orchestrates all calculators
- [ ] EstimatedWaitTime integration complete with backward compatibility
- [ ] All fallback scenarios working (Redis, DB, WhenIWork)
- [ ] Prediction logging implemented
- [ ] Performance < 100ms verified
- [ ] PHPStan clean on service layer code

- [ ] T4 Phase 4: Service Layer Integration

    - [ ] T4.1 Prime Context
        - [ ] T4.1.1 Read SDD WaitTimeFactorService specification `[ref: SDD; lines: 414-420]`
        - [ ] T4.1.2 Read SDD Runtime View - Primary Flow `[ref: SDD; lines: 860-911]`
        - [ ] T4.1.3 Read SDD Error Handling section `[ref: SDD; lines: 915-939]`
        - [ ] T4.1.4 Read existing EstimatedWaitTime class `[ref: userfrosting/src/BuyerKiosk/Core/EstimatedWaitTime.php]`

    - [ ] T4.2 Write Tests `[activity: backend-test]`
        - [ ] T4.2.1 Test: calculateFactors returns combined FactorResult with all factors
        - [ ] T4.2.2 Test: factors are combined multiplicatively `[ref: PRD; lines: 63-69]`
        - [ ] T4.2.3 Test: efficiency factor applied as divisor per ADR-6 `[ref: SDD; lines: 1219-1229]`
        - [ ] T4.2.4 Test: Redis unavailable falls back to static factors `[ref: SDD; lines: 927-928]`
        - [ ] T4.2.5 Test: Database error returns default factors (all 1.0) `[ref: SDD; lines: 929-930]`
        - [ ] T4.2.6 Test: all factors bounded within ranges (dynamic 0.5-2.0, queue 1.0-2.0, efficiency 0.5-2.0)
        - [ ] T4.2.7 Test: prediction logged with all factor values `[ref: PRD; lines: 319]`
        - [ ] T4.2.8 Test: calculation completes in < 100ms `[ref: PRD; lines: 330]`

    - [ ] T4.3 Implement WaitTimeFactorService `[activity: backend-api]`
        - [ ] T4.3.1 Create `WaitTime/Services/WaitTimeFactorService.php`
        - [ ] T4.3.2 Implement `calculateFactors(Store $store): FactorResult`
        - [ ] T4.3.3 Implement `getDynamicFactor()`, `getQueueMultiplier()`, `getEfficiencyFactor()`
        - [ ] T4.3.4 Implement `refreshCache(Store $store): void`
        - [ ] T4.3.5 Implement error handling with graceful fallbacks `[ref: SDD; lines: 923-939]`

    - [ ] T4.4 Integrate with EstimatedWaitTime `[activity: backend-api]`
        - [ ] T4.4.1 Modify `Core/EstimatedWaitTime.php` to inject WaitTimeFactorService
        - [ ] T4.4.2 Update calculation method to use dynamic factors when enabled
        - [ ] T4.4.3 Preserve backward compatibility for stores with features disabled
        - [ ] T4.4.4 Add prediction logging with factor breakdown

    - [ ] T4.5 Validate
        - [ ] T4.5.1 Run all unit tests `[activity: run-tests]`
        - [ ] T4.5.2 Run PHPStan on entire WaitTime module `[activity: lint-code]`
        - [ ] T4.5.3 Verify existing EstimatedWaitTime tests still pass `[activity: run-tests]`
        - [ ] T4.5.4 Verify PRD performance constraint < 100ms `[activity: business-acceptance]`
        - [ ] T4.5.5 Verify factor combination per PRD formula `[activity: business-acceptance]`

---

### Phase 5: Background Jobs

*Dependencies: Phases 3 and 4 complete (repository, cache, and service exist)*
*Delivers: Five TaskEngine jobs for cache refresh, calibration, accuracy tracking*

**Definition of Done:**
- [ ] All 5 jobs registered in TaskEngine JobDefinitions
- [ ] Jobs respect store timezone for scheduling
- [ ] Performance budgets met (30s backfill, 60s aggregation)
- [ ] Feature flag checks implemented in each job
- [ ] Jobs persist to waitTimeFactorCache table (cache refresh)
- [ ] PHPStan clean on all job code

- [ ] T5 Phase 5: Background Jobs

    - [ ] T5.1 Cache Refresh Job `[parallel: true]` `[component: cache-refresh-job]`

        - [ ] T5.1.1 Prime Context
            - [ ] T5.1.1.1 Read SDD CacheRefreshJob integration `[ref: SDD; lines: 588-591]`
            - [ ] T5.1.1.2 Read PRD nightly refresh requirement `[ref: PRD; lines: 171]`
            - [ ] T5.1.1.3 Review BaseJob pattern `[ref: userfrosting/src/BuyerKiosk/TaskEngine/Domain/Job/BaseJob.php]`

        - [ ] T5.1.2 Write Tests `[activity: backend-test]`
            - [ ] T5.1.2.1 Test: job refreshes all 168 time slots for store
            - [ ] T5.1.2.2 Test: job handles stores with insufficient data gracefully
            - [ ] T5.1.2.3 Test: job updates waitTimeFactorsCachedAt on store
            - [ ] T5.1.2.4 Test: job persists factors to waitTimeFactorCache table
            - [ ] T5.1.2.5 Test: job uses store's timezone for scheduling
            - [ ] T5.1.2.6 Test: job warms Redis cache from database table

        - [ ] T5.1.3 Implement `[activity: backend-api]`
            - [ ] T5.1.3.1 Create `WaitTime/Jobs/WaitTimeCacheRefreshJob.php`
            - [ ] T5.1.3.2 Register job in TaskEngine JobDefinitions
            - [ ] T5.1.3.3 Configure schedule: daily at 2am store local time
            - [ ] T5.1.3.4 Persist calculated factors to waitTimeFactorCache table via repository
            - [ ] T5.1.3.5 Warm Redis cache from database for fast reads

        - [ ] T5.1.4 Validate
            - [ ] T5.1.4.1 Run unit tests `[activity: run-tests]`
            - [ ] T5.1.4.2 Test manual dispatch: `php userfrosting/bin/task job:dispatch wait-time-cache-refresh --store=ou00`

    - [ ] T5.2 Calibration Job `[parallel: true]` `[component: calibration-job]`

        - [ ] T5.2.1 Prime Context
            - [ ] T5.2.1.1 Read PRD Feature 3 acceptance criteria `[ref: PRD; lines: 188-195]`
            - [ ] T5.2.1.2 Read SDD calibration integration `[ref: SDD; lines: 593-598]`

        - [ ] T5.2.2 Write Tests `[activity: backend-test]`
            - [ ] T5.2.2.1 Test: job skips stores with autoCalibrationEnabled = false
            - [ ] T5.2.2.2 Test: job skips stores with mpcLocked = true `[ref: PRD; lines: 193]`
            - [ ] T5.2.2.3 Test: job requires >= 100 transactions `[ref: PRD; lines: 191]`
            - [ ] T5.2.2.4 Test: job only updates if diff > 15% `[ref: PRD; lines: 192]`
            - [ ] T5.2.2.5 Test: job logs old/new values to mpcCalibrationLog `[ref: PRD; lines: 194]`

        - [ ] T5.2.3 Implement `[activity: backend-api]`
            - [ ] T5.2.3.1 Create `WaitTime/Jobs/MinutesPerContainerCalibrationJob.php`
            - [ ] T5.2.3.2 Register job in TaskEngine JobDefinitions
            - [ ] T5.2.3.3 Configure schedule: weekly on Sunday at 3am store local time

        - [ ] T5.2.4 Validate
            - [ ] T5.2.4.1 Run unit tests `[activity: run-tests]`
            - [ ] T5.2.4.2 Verify PRD Feature 3 acceptance criteria `[activity: business-acceptance]`

    - [ ] T5.3 Actual Wait Time Backfill Job `[parallel: true]` `[component: backfill-job]`

        - [ ] T5.3.1 Prime Context
            - [ ] T5.3.1.1 Read PRD Feature 5 acceptance criteria `[ref: PRD; lines: 215-220]`
            - [ ] T5.3.1.2 Read SDD ActualWaitTimeBackfillJob integration `[ref: SDD; lines: 600-606]`

        - [ ] T5.3.2 Write Tests `[activity: backend-test]`
            - [ ] T5.3.2.1 Test: job finds predictions with null actualMinutes
            - [ ] T5.3.2.2 Test: job calculates actual wait per contract `[ref: SDD; lines: 458-515]`
            - [ ] T5.3.2.3 Test: job completes within 30 second budget `[ref: SDD; lines: 605]`

        - [ ] T5.3.3 Implement `[activity: backend-api]`
            - [ ] T5.3.3.1 Create `WaitTime/Jobs/ActualWaitTimeBackfillJob.php`
            - [ ] T5.3.3.2 Register job in TaskEngine JobDefinitions
            - [ ] T5.3.3.3 Configure schedule: hourly

        - [ ] T5.3.4 Validate
            - [ ] T5.3.4.1 Run unit tests `[activity: run-tests]`

    - [ ] T5.4 Daily Accuracy Aggregation Job `[parallel: true]` `[component: aggregation-job]`

        - [ ] T5.4.1 Prime Context
            - [ ] T5.4.1.1 Read PRD tracking requirements `[ref: PRD; lines: 317-323]`
            - [ ] T5.4.1.2 Read SDD DailyAccuracyAggregationJob integration `[ref: SDD; lines: 607-612]`

        - [ ] T5.4.2 Write Tests `[activity: backend-test]`
            - [ ] T5.4.2.1 Test: job calculates MAPE correctly
            - [ ] T5.4.2.2 Test: job calculates ±10 minute accuracy percentage
            - [ ] T5.4.2.3 Test: job stores results in waitTimeAccuracyDaily
            - [ ] T5.4.2.4 Test: job completes within 60 second budget `[ref: SDD; lines: 612]`

        - [ ] T5.4.3 Implement `[activity: backend-api]`
            - [ ] T5.4.3.1 Create `WaitTime/Jobs/DailyAccuracyAggregationJob.php`
            - [ ] T5.4.3.2 Register job in TaskEngine JobDefinitions
            - [ ] T5.4.3.3 Configure schedule: daily at 4am store local time

        - [ ] T5.4.4 Validate
            - [ ] T5.4.4.1 Run unit tests `[activity: run-tests]`
            - [ ] T5.4.4.2 Verify PRD Feature 5 acceptance criteria `[activity: business-acceptance]`

    - [ ] T5.5 Accuracy Data Retention Job `[component: retention-job]`

        - [ ] T5.5.1 Prime Context
            - [ ] T5.5.1.1 Read PRD 12-month retention requirement `[ref: PRD; lines: 220]`
            - [ ] T5.5.1.2 Read SDD AccuracyDataRetentionJob integration `[ref: SDD; lines: 614-619]`

        - [ ] T5.5.2 Write Tests `[activity: backend-test]`
            - [ ] T5.5.2.1 Test: job prunes waitTimePredictions older than 12 months
            - [ ] T5.5.2.2 Test: job prunes waitTimeAccuracyDaily older than 12 months

        - [ ] T5.5.3 Implement `[activity: backend-api]`
            - [ ] T5.5.3.1 Create `WaitTime/Jobs/AccuracyDataRetentionJob.php`
            - [ ] T5.5.3.2 Register job in TaskEngine JobDefinitions
            - [ ] T5.5.3.3 Configure schedule: monthly on 1st at 5am

        - [ ] T5.5.4 Validate
            - [ ] T5.5.4.1 Run unit tests `[activity: run-tests]`

---

### Phase 6: Integration & End-to-End Validation

*Dependencies: All previous phases complete*
*Delivers: Fully validated, production-ready feature*

**Definition of Done:**
- [ ] All integration tests passing
- [ ] All PRD acceptance criteria verified
- [ ] Performance requirements met (< 100ms calculation, < 10ms Redis)
- [ ] Security validation complete (store isolation, input validation)
- [ ] Test coverage > 80% for all new code
- [ ] PHPStan clean on entire WaitTime module
- [ ] Feature flags documented for rollout
- [ ] Rollback procedure documented

- [ ] T6 Phase 6: Integration & End-to-End Validation

    - [ ] T6.1 Integration Tests
        - [ ] T6.1.1 Test: Full estimation flow with all factors enabled `[ref: SDD; lines: 1266-1278]`
        - [ ] T6.1.2 Test: Fallback scenarios (Redis unavailable, WhenIWork error) `[ref: SDD; lines: 1279-1298]`
        - [ ] T6.1.3 Test: Auto-calibration end-to-end `[ref: SDD; lines: 1299-1309]`
        - [ ] T6.1.4 Test: Time slot expansion (30-49 transactions) `[ref: SDD; lines: 1311-1319]`
        - [ ] T6.1.5 Test: Actual wait time edge cases `[ref: SDD; lines: 1321-1339]`
        - [ ] T6.1.6 Test: Queue depth calculation precision `[ref: SDD; lines: 1341-1351]`
        - [ ] T6.1.7 Test: Daily accuracy aggregation `[ref: SDD; lines: 1353-1364]`
        - [ ] T6.1.8 Test: Employee efficiency fallback chain `[ref: SDD; lines: 1366-1379]`
        - [ ] T6.1.9 Test: Mobile API wait-time endpoint returns new prediction range `[ref: SDD; lines: 159-163]`
        - [ ] T6.1.10 Test: Check-in flow regression (existing behavior preserved when flags disabled)

    - [ ] T6.2 Performance Validation
        - [ ] T6.2.1 Verify calculation < 100ms (cache hit path) `[ref: PRD; lines: 330]`
        - [ ] T6.2.2 Verify calculation < 50ms cache miss path `[ref: SDD; lines: 1022]`
        - [ ] T6.2.3 Verify Redis read < 10ms `[ref: SDD; lines: 30]`
        - [ ] T6.2.4 Verify > 95% cache hit rate during business hours `[ref: SDD; lines: 1023]`

    - [ ] T6.3 Security Validation `[ref: SDD; lines: 1057-1079]`
        - [ ] T6.3.1 Verify store isolation (no cross-store data leakage)
        - [ ] T6.3.2 Verify typeNum validation pattern `[a-z][a-z]\d+`
        - [ ] T6.3.3 Verify employee efficiency data never exposed to customers

    - [ ] T6.4 PRD Acceptance Criteria Verification
        - [ ] T6.4.1 Feature 1: Dynamic Wait Time Factor `[ref: PRD; lines: 162-172]`
            - [ ] Dynamic factor calculated from historical data
            - [ ] 30-day rolling window
            - [ ] Factor bounded 0.5-2.0
            - [ ] Falls back when < 50 transactions
            - [ ] 24-hour cache TTL
        - [ ] T6.4.2 Feature 2: Queue Depth Multiplier `[ref: PRD; lines: 175-182]`
            - [ ] Formula: 1.0 + (depth/10) × 0.15
            - [ ] Capped at 2.0
            - [ ] Excludes remote/canceled/abandoned/on_hold
        - [ ] T6.4.3 Feature 3: Rolling Average Calibration `[ref: PRD; lines: 188-195]`
            - [ ] Weekly at 3am Sunday
            - [ ] Requires 100+ transactions
            - [ ] Only updates if > 15% diff
            - [ ] Respects mpcLocked flag
            - [ ] Changes logged
        - [ ] T6.4.4 Feature 4: Employee Efficiency Weighting `[ref: PRD; lines: 199-209]`
            - [ ] Uses WhenIWork → Scheduled → Active → Default precedence
            - [ ] Factor bounded 0.5-2.0
            - [ ] Excludes stale metrics (> 30 days)
        - [ ] T6.4.5 Feature 5: Prediction Accuracy Logging `[ref: PRD; lines: 215-220]`
            - [ ] Predictions logged at check-in
            - [ ] Actuals calculated when transaction completes
            - [ ] Daily MAPE aggregation
            - [ ] 12-month retention

    - [ ] T6.5 Code Quality
        - [ ] T6.5.1 Run PHPStan on entire WaitTime module - zero errors `[activity: lint-code]`
        - [ ] T6.5.2 Test coverage > 80% for all new classes `[ref: SDD; lines: 1386-1398]`
        - [ ] T6.5.3 Run full test suite - all tests pass `[activity: run-tests]`

    - [ ] T6.6 Documentation & Deployment Prep
        - [ ] T6.6.1 Verify all migration files ready `[activity: database]`
        - [ ] T6.6.2 Document feature flags for rollout `[ref: SDD; lines: 993-1007]`
        - [ ] T6.6.3 Document rollback procedure (disable feature flags)
        - [ ] T6.6.4 Update API documentation if any changes

    - [ ] T6.7 Final Sign-off
        - [ ] T6.7.1 All PRD requirements implemented `[ref: PRD]`
        - [ ] T6.7.2 Implementation follows SDD design `[ref: SDD]`
        - [ ] T6.7.3 Ready for pilot store deployment

---

## Summary

### Phase Dependencies

```
Phase 1 (Foundation)
    ↓
Phase 2 (Calculators) ←─┬─→ Phase 3 (Data Layer)
    ↓                   │       ↓
    └───────────────────┴───────┘
                ↓
         Phase 4 (Service Integration)
                ↓
         Phase 5 (Background Jobs)
                ↓
         Phase 6 (Integration & E2E)
```

### Parallel Execution Opportunities

| Phase | Parallel Components |
|-------|---------------------|
| Phase 2 | DynamicFactorCalculator, QueueDepthCalculator, EmployeeEfficiencyCalculator |
| Phase 3 | WaitTimeFactorRepository, WaitTimeAccuracyRepository, WaitTimeFactorCache |
| Phase 5 | CacheRefreshJob, CalibrationJob, BackfillJob, AggregationJob |

### PRD Feature → Phase Mapping

| PRD Feature | Primary Phase | Validation Phase |
|-------------|---------------|------------------|
| Feature 1: Dynamic Factor | Phase 2.1 | Phase 6.4.1 |
| Feature 2: Queue Depth | Phase 2.2 | Phase 6.4.2 |
| Feature 3: Auto-Calibration | Phase 5.2 | Phase 6.4.3 |
| Feature 4: Employee Efficiency | Phase 2.3 | Phase 6.4.4 |
| Feature 5: Accuracy Logging | Phase 3.2, 5.3, 5.4 | Phase 6.4.5 |

### Task Count Summary

| Phase | Tasks | Parallel Groups |
|-------|-------|-----------------|
| Phase 1: Foundation | 22 | 0 |
| Phase 2: Calculators | 36 | 3 |
| Phase 3: Data Layer | 40 | 3 |
| Phase 4: Service Layer | 17 | 0 |
| Phase 5: Background Jobs | 30 | 4 |
| Phase 6: Integration | 29 | 0 |
| **Total** | **174** | **10** |

### Codex Review Changes (2026-01-08)

The following changes were made based on Codex review:

1. **Added Interface Contracts Phase** (T1.5) - Enables Phase 2 calculator testing via mocks
2. **Added waitTimeFactorCache table operations** - Repository methods and job tasks for persistence
3. **Added data quality/outlier logging** - Tests and implementation per SDD contract
4. **Added timezone handling tests** - PHP dayOfWeek vs MySQL dayOfWeek conversion
5. **Added Risks & Mitigations section** - Risk tracking from PRD/SDD
6. **Added Definition of Done per phase** - Clear completion criteria
7. **Added Mobile API integration test** - Verify endpoint returns new prediction range
8. **Added check-in flow regression test** - Ensure backward compatibility
