# 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 ```bash 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` running** β†’ `PermissionError`. 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')`.