feat: implement workload heatmap module
- Backend (FastAPI):
- Workload heatmap API with load level calculation
- User workload detail endpoint with task breakdown
- Redis caching for workload calculations (1hr TTL)
- Department isolation and access control
- WorkloadSnapshot model for historical data
- Alembic migration for workload_snapshots table
- API Endpoints:
- GET /api/workload/heatmap - Team workload overview
- GET /api/workload/user/{id} - User workload detail
- GET /api/workload/me - Current user workload
- Load Levels:
- normal: <80%, warning: 80-99%, overloaded: >=100%
- Tests:
- 26 unit/API tests
- 15 E2E automated tests
- 77 total tests passing
- OpenSpec:
- add-resource-workload change archived
- resource-management spec updated
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
38
backend/migrations/versions/003_workload_snapshots_table.py
Normal file
38
backend/migrations/versions/003_workload_snapshots_table.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""Workload snapshots table
|
||||
|
||||
Revision ID: 003
|
||||
Revises: 002
|
||||
Create Date: 2024-01-XX
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '003'
|
||||
down_revision = '002'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Create pjctrl_workload_snapshots table
|
||||
op.create_table(
|
||||
'pjctrl_workload_snapshots',
|
||||
sa.Column('id', sa.String(36), primary_key=True),
|
||||
sa.Column('user_id', sa.String(36), sa.ForeignKey('pjctrl_users.id', ondelete='CASCADE'), nullable=False),
|
||||
sa.Column('week_start', sa.Date, nullable=False),
|
||||
sa.Column('allocated_hours', sa.Numeric(8, 2), nullable=False, server_default='0'),
|
||||
sa.Column('capacity_hours', sa.Numeric(8, 2), nullable=False, server_default='40'),
|
||||
sa.Column('load_percentage', sa.Numeric(5, 2), nullable=False, server_default='0'),
|
||||
sa.Column('task_count', sa.Integer, nullable=False, server_default='0'),
|
||||
sa.Column('created_at', sa.DateTime, server_default=sa.func.now(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime, server_default=sa.func.now(), onupdate=sa.func.now(), nullable=False),
|
||||
sa.UniqueConstraint('user_id', 'week_start', name='uk_user_week'),
|
||||
)
|
||||
op.create_index('idx_workload_user', 'pjctrl_workload_snapshots', ['user_id'])
|
||||
op.create_index('idx_workload_week_start', 'pjctrl_workload_snapshots', ['week_start'])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table('pjctrl_workload_snapshots')
|
||||
Reference in New Issue
Block a user