Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions apps/api/src/humancompiler_api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ class GoalStatus(str, Enum):
class TaskCategory(str, Enum):
"""Weekly recurring task category enum"""

MEETING = "meeting"
STUDY = "study"
EXERCISE = "exercise"
HOBBY = "hobby"
Comment on lines 48 to 53
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Add migration/backfill for removed TaskCategory

Removing the MEETING enum value from TaskCategory means any existing DB rows with category='meeting' will fail to hydrate through SQLAlchemy’s enum mapping (the category fields in models.py use SQLEnum(TaskCategory, ...)). In deployments with historical meeting tasks, reads (e.g., weekly recurring task listings) will raise a LookupError/validation error and 500. The commit doesn’t include a migration/backfill to rewrite existing rows, so this breaks existing data unless handled elsewhere.

Useful? React with 👍 / 👎.

Expand Down Expand Up @@ -1894,9 +1893,7 @@ class TimeSlotSchema(BaseModel):

start: str = Field(..., pattern=r"^\d{2}:\d{2}$", description="Start time (HH:mm)")
end: str = Field(..., pattern=r"^\d{2}:\d{2}$", description="End time (HH:mm)")
kind: str = Field(
..., description="Slot kind (study, focused_work, light_work, meeting)"
)
kind: str = Field(..., description="Slot kind (study, focused_work, light_work)")
capacity_hours: float | None = Field(
None, ge=0, description="Slot capacity in hours"
)
Expand Down
40 changes: 20 additions & 20 deletions apps/api/tests/test_weekly_recurring_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ def test_user(test_session):
def sample_weekly_recurring_task_data():
"""Sample weekly recurring task data for testing."""
return {
"title": "週次振り返り会議",
"description": "チームの週次振り返り会議",
"title": "週次レビュー",
"description": "チームの週次レビュー",
"estimate_hours": 2.0,
"category": TaskCategory.MEETING,
"category": TaskCategory.REVIEW,
"is_active": True,
}

Expand All @@ -67,10 +67,10 @@ def test_create_weekly_recurring_task(
test_session, task_data, test_user.id
)

assert task.title == "週次振り返り会議"
assert task.description == "チームの週次振り返り会議"
assert task.title == "週次レビュー"
assert task.description == "チームの週次レビュー"
assert task.estimate_hours == Decimal("2.0")
assert task.category == TaskCategory.MEETING
assert task.category == TaskCategory.REVIEW
assert task.is_active is True
assert task.user_id == test_user.id
assert task.id is not None
Expand Down Expand Up @@ -111,12 +111,12 @@ def test_get_weekly_recurring_tasks(
def test_get_weekly_recurring_tasks_with_filters(test_session: Session, test_user):
"""Test getting weekly recurring tasks with filters."""
# Create tasks with different categories and statuses
active_meeting_task = weekly_recurring_task_service.create_weekly_recurring_task(
active_admin_task = weekly_recurring_task_service.create_weekly_recurring_task(
Copy link

Copilot AI Feb 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable active_admin_task is not used.

Copilot uses AI. Check for mistakes.
test_session,
WeeklyRecurringTaskCreate(
title="週次会議",
title="事務作業",
estimate_hours=1.5,
category=TaskCategory.MEETING,
category=TaskCategory.ADMIN,
is_active=True,
),
test_user.id,
Expand All @@ -134,11 +134,11 @@ def test_get_weekly_recurring_tasks_with_filters(test_session: Session, test_use
)

# Filter by category
meeting_tasks = weekly_recurring_task_service.get_weekly_recurring_tasks(
test_session, test_user.id, category=TaskCategory.MEETING
admin_tasks = weekly_recurring_task_service.get_weekly_recurring_tasks(
test_session, test_user.id, category=TaskCategory.ADMIN
)
assert len(meeting_tasks) >= 1
assert all(task.category == TaskCategory.MEETING for task in meeting_tasks)
assert len(admin_tasks) >= 1
assert all(task.category == TaskCategory.ADMIN for task in admin_tasks)

# Filter by active status
active_tasks = weekly_recurring_task_service.get_weekly_recurring_tasks(
Expand Down Expand Up @@ -311,10 +311,10 @@ def test_weekly_recurring_task_filter_by_category(
"""Test filtering weekly recurring tasks by category."""

# Create tasks with different categories
meeting_task = {
"title": "会議",
admin_task = {
"title": "事務作業",
"estimate_hours": 1.0,
"category": TaskCategory.MEETING,
"category": TaskCategory.ADMIN,
"is_active": True,
}

Expand All @@ -327,7 +327,7 @@ def test_weekly_recurring_task_filter_by_category(

# Create both tasks
response = client.post(
"/api/weekly-recurring-tasks/", json=meeting_task, headers=auth_headers
"/api/weekly-recurring-tasks/", json=admin_task, headers=auth_headers
)
assert response.status_code == 201

Expand All @@ -336,15 +336,15 @@ def test_weekly_recurring_task_filter_by_category(
)
assert response.status_code == 201

# Filter by meeting category
# Filter by admin category
response = client.get(
"/api/weekly-recurring-tasks/?category=meeting", headers=auth_headers
"/api/weekly-recurring-tasks/?category=admin", headers=auth_headers
)

assert response.status_code == 200
tasks = response.json()
assert len(tasks) >= 1
assert all(task["category"] == TaskCategory.MEETING for task in tasks)
assert all(task["category"] == TaskCategory.ADMIN for task in tasks)

# Filter by study category
response = client.get(
Expand Down
1 change: 0 additions & 1 deletion apps/web/src/app/ai-planning/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,6 @@ export default function AIPlanningPage() {

const getCategoryColor = (category: string) => {
const colors: Record<string, string> = {
meeting: 'bg-blue-100 text-blue-800',
study: 'bg-green-100 text-green-800',
exercise: 'bg-orange-100 text-orange-800',
hobby: 'bg-purple-100 text-purple-800',
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/app/weekly-tasks/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,12 @@ export default function WeeklyTasksPage() {

const getCategoryColor = (category: string) => {
const colors: Record<string, string> = {
meeting: "bg-blue-100 text-blue-800",
study: "bg-green-100 text-green-800",
exercise: "bg-orange-100 text-orange-800",
hobby: "bg-purple-100 text-purple-800",
admin: "bg-gray-100 text-gray-800",
maintenance: "bg-indigo-100 text-indigo-800",
review: "bg-pink-100 text-pink-800",
}
return colors[category] || "bg-gray-100 text-gray-800"
}
Expand Down
12 changes: 5 additions & 7 deletions apps/web/src/constants/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,18 @@

import { logger } from '@/lib/logger'

export type SlotKind = 'study' | 'focused_work' | 'light_work' | 'meeting';
export type SlotKind = 'study' | 'focused_work' | 'light_work';

export const slotKindLabels: Record<SlotKind, string> = {
study: '学習',
focused_work: '集中作業',
light_work: '軽作業',
meeting: '会議',
} as const;

export const slotKindColors: Record<SlotKind, string> = {
study: 'bg-blue-100 text-blue-800',
focused_work: 'bg-purple-100 text-purple-800',
light_work: 'bg-green-100 text-green-800',
meeting: 'bg-orange-100 text-orange-800',
} as const;

/**
Expand All @@ -29,8 +27,8 @@ export const getSlotKindLabel = (slotKind: string): string => {
const typedSlotKind = slotKind as SlotKind;

if (!(slotKind in slotKindLabels)) {
logger.warn('Unknown slot kind, using fallback to meeting', { slotKind }, { component: 'schedule' });
return slotKindLabels.meeting;
logger.warn('Unknown slot kind, using fallback to light_work', { slotKind }, { component: 'schedule' });
return slotKindLabels.light_work;
}

return slotKindLabels[typedSlotKind];
Expand All @@ -45,8 +43,8 @@ export const getSlotKindColor = (slotKind: string): string => {
const typedSlotKind = slotKind as SlotKind;

if (!(slotKind in slotKindColors)) {
logger.warn('Unknown slot kind, using fallback to meeting', { slotKind }, { component: 'schedule' });
return slotKindColors.meeting;
logger.warn('Unknown slot kind, using fallback to light_work', { slotKind }, { component: 'schedule' });
return slotKindColors.light_work;
}

return slotKindColors[typedSlotKind];
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/types/ai-planning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export interface TimeSlot {
/** 終了時刻 (HH:mm形式) */
end: string;
/** スロット種別(作業タイプ) */
kind: 'study' | 'focused_work' | 'light_work' | 'meeting';
kind: 'study' | 'focused_work' | 'light_work';
/** スロットのキャパシティ(時間単位) */
capacity_hours?: number;
/** 割り当てプロジェクトID(スロット単位での割り当て) */
Expand Down
Loading