KINDNICK bedbb1e9ec
Some checks failed
CI / test (push) Has been cancelled
Initial release v2.2.0
핵심 기능:
- 단축근무·표준·반일 등 다양한 근무 패턴 (5개 프리셋 + 사용자 정의)
- Windows 이벤트 뷰어 자동 출퇴근 감지
- 30분 단위 연장근무 적립/사용 시스템
- 1.0/0.5/0.25일 연차·반차·반반차
- 자동 점심·저녁·외출·자동 백업·화면 잠금 자동 외출
- 한국 공휴일 자동 등록 (음력 포함, holidays 패키지)
- matplotlib 차트 기반 주간/월간/패턴 통계
- 미니 위젯 + 시스템 트레이 통합
- 한국어/English i18n
- 자가 업데이트 (updater.exe + Gitea Releases)

아키텍처:
- core/ (db, time_calculator, notifier, i18n, version, settings_keys)
- ui/ (main_window + 9 dialogs + 3 controllers)
- utils/ (backup, lock_detector, debug_log, updater_client, time_format)
- tests/ (66 pytest 단위) + 통합/i18n GUI 검증

CI/CD:
- .gitea/workflows/ci.yml: push 시 pytest + 통합 테스트
- .gitea/workflows/release.yml: v* 태그 push 시 두 .exe 자동 빌드 + Releases 첨부

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 12:54:40 +09:00

5.2 KiB

Project Conventions and Operational Gotchas

🛠️ Setup & Execution

  • Dependencies: pip install -r requirements.txt. Optional: pip install anthropic for AI insight feature.
  • Run: python main.py
  • Module-level tests:
    • Event monitoring: python core/event_monitor.py
    • Time calculation: python core/time_calculator.py
  • Integration tests: python _integration_test.py (35 scenarios), python _i18n_gui_test.py (5 ko/en GUI), python _gui_smoke_test.py (8 widget). All should be green before release.

🗄️ Architecture Notes (Core Business Logic)

  • 8 SQLite tables in database.db: work_records, overtime_bank, overtime_usage, leave_records, break_records, settings, achievements, holidays. Migrations in init_database() ALTER existing DBs.
  • work_records.date UNIQUE — one row per workday.
  • Overtime tracking: earned (bank) and used (usage) tables separate. Both have NULLable work_record_id for manual entries — never filter them out.
  • Time representation: TimeCalculator.work_minutes is the canonical attribute (int). work_hours is a property for compatibility. UI/DB sync WORK_MINUTES ↔ WORK_HOURS automatically (floor on minutes→hours).
  • Settings: all keys defined in core/settings_keys.py. Import constants — never use raw strings. get_setting() returns string; use get_setting_int/float/bool() helpers or read from get_settings() dict (already typed).
  • i18n: tr('key', **kwargs) and tr_html('help.html.X'). Sentences use format placeholders. Language switch requires restart for full effect.

⚠️ Critical Invariants (MUST PRESERVE)

1. Time-off subtraction order in update_display()

Pass actual break_minutes to calculate_remaining_time. Subtract total_time_off = overtime_used + leave_used from the resulting timedelta AFTER the call. NEVER mutate break_minutes to break_minutes - overtime_used — this caused a +29h display bug previously and was the original Phase 1 fix.

2. Hot-path caching

update_display() runs at 1Hz. Any DB call inside this method must be cached (see _auto_lunch_enabled_cache, _today_non_working_cache, cached_time_format). Periodic checks (health/weekly notifications) are gated by now.minute % 5 == 0.

3. Time format separation

24-hour datetime for ALL internal calculation. 12-hour conversion happens only in MainWindow.format_time() (adds Korean "오전"/"오후" markers when applicable).

4. Workday boundary

workday_boundary_hour (default 6). Overnight work stays on the previous day's record until that hour. Don't naively use date.today() in time logic without considering this rollover.

5. Migration idempotency

All migrate_* methods must early-return if already applied. Use sentinel keys (balance_adjustment_migrated_v2, annual_leave_keys_migrated) — without them, every startup runs the migration query.

⚙️ Build Process

python -m PyInstaller --clean main.spec
  • Output: dist/main.exe (console disabled, UPX compressed)
  • main.spec includes icon (3d-alarm.ico), data file (3d-alarm.png)
  • Stale dist/main.exe runningPermissionError. Kill it first.
  • Optional packages (holidays, anthropic) only baked in if installed in build env.

🚦 External Integrations

  • Anthropic Claude API (optional): core/ai_analysis.py. Without anthropic package or API key, falls back to static_summary(). Don't crash the stats view.
  • HTTP API (utils/http_api.py): bound to 127.0.0.1:17389 only — never expose externally. Read-only by design.
  • Cloud sync via db_path_override: settings stores DB path; main.py + main_window.py both bootstrap with default DB to read this key, then reopen with override path. Don't break the bootstrap order.
  • holidays package: add_korean_holidays_auto() returns -1 if package missing → UI falls back to add_korean_holidays() (8 fixed dates).

🐞 Past Incidents

  • +29h remaining time bug (Phase 1): caused by break_minutes -= overtime_used. Fixed by subtracting total_time_off AFTER calculate_remaining_time call. Never re-introduce.
  • Manual overtime invisible: overtime_view previously filtered work_record_id IS NOT NULL. Manual additions (no work_record) were hidden. Show all rows; label NULL rows as "수동 추가" / "Manual".
  • annual_leave_total vs annual_leave_days: two keys for the same value. UI used _days, internal methods used _total. Now auto-synced in save_settings(). If you add a method reading either, also handle the sibling.
  • Banker's rounding: round(450/60) = 8 in Python (round-half-even). Use int(value) // 60 (floor) for hours derivation when consistency matters.

🌐 i18n Coverage Status

  • Fully translated: window titles, menus, buttons, group boxes, mini widget, tray menu, all 6 notifications, HelpView 6 tabs, settings_view core labels, stats_view labels.
  • Partially translated: settings_view sub-labels (입력란 placeholder 등), calendar_view detail labels.
  • Not yet translated: dialog inner labels in break_view/overtime_view/leave_view. Window titles for these dialogs ARE translated.

Adding new translations: add key to _DICT['ko'] AND _DICT['en'], replace literal with tr('key').