Reference time categories system
Built 2026-04-30. Replaces the hardcoded _CATEGORY_COLOR dict + the wide-format Time Daily DB.
The pieces¶
| File | Role |
|---|---|
forge/data/time_categories.json |
Canonical list. Edit only via coordinator bot tools. |
forge/scripts/forge_time_categories.py |
Loader + mutators (add/remove/rename/set_aliases/set_keywords). Used by everything below. |
forge/scripts/forge_telegram_inbox_brain.py |
_CATEGORY_COLOR derived from JSON at import. Used to color GCal events on creation. |
forge/scripts/forge_time_daily_aggregator.py |
classify() reads colorId first via tc.color_to_category(), falls back to tc.fallback_pattern_for(name) (rich keyword regex per category). |
forge/scripts/forge_time_blocks_tools.py |
The 7 coordinator-bot tools (list/add/remove/rename/set_aliases/set_keywords/time_summary). |
forge/scripts/forge_time_blocks_provision.py |
One-shot Notion DB creator (idempotent; safe to re-run if Justin nukes the DB). |
Notion DB Time Blocks (id 3520950b-d7a9-81b1-b7cf-cc57c044ac4e) |
Long-format storage. One row per (Day × Category). |
Schema (Time Blocks DB)¶
| Property | Type | Purpose |
|---|---|---|
| Name | title | Auto-set as <YYYY-MM-DD> · <Category> |
| Day | date | The calendar day this row aggregates |
| Category | select | Dynamic options synced from JSON. Includes Untagged. |
| Hours | number | Decimal hours in this category on this day |
| Events count | number | How many GCal events rolled up |
| Computed at | rich_text | Last cron run timestamp |
| Notes | rich_text | Free-form |
Long-format means schema never grows when adding categories. Adding "Reading" = adding a select option, not a column. Notion views handle the dashboard layer (group-by Category, sum Hours).
Two distinct classification paths¶
| Field | Drives | When it fires |
|---|---|---|
aliases |
Inbox bot (interactive) | Justin says "log a workout" / "log gym" → maps to Workout colorId. Aliases ARE the routing terms. |
keywords |
Aggregator (passive) | Aggregator sees an event with NO colorId (Justin created it manually in Calendar UI). Matches keywords against summary text to guess category. |
For events the inbox bot creates, colorId is always set, so neither aliases nor keywords matter at aggregation time. The keyword fallback only matters for hand-created Calendar events.
Coordinator-bot tools¶
All in TOOL_HANDLERS, exposed only to coordinator persona (NOT in CAPTURE_TOOL_NAMES):
| Tool | What it does |
|---|---|
list_time_categories |
Show table of name/color/aliases/keyword-count + free GCal colors |
add_time_category(name, color_id, aliases?, keywords?) |
Append; sync Notion select; restart inbox bot |
remove_time_category(name) |
Drop from JSON; keep old DB rows tagged for history; restart inbox bot |
rename_time_category(old, new) |
Rename in JSON; new select option added; old DB rows keep old tag |
set_category_aliases(name, aliases) |
Replace alias list; restart inbox bot |
set_category_keywords(name, keywords) |
Replace keyword list; no restart needed (aggregator reads on next run) |
time_summary(period) |
Read Time Blocks DB, sum by category. Periods: today, yesterday, this_week, last_week, last_7, last_30, this_month, or YYYY-MM-DD |
Adding a new category, end-to-end (chat flow)¶
Justin to coordinator bot: "add a Reading category, sage color, aliases reading and book"
Coordinator calls:
1. list_time_categories (sanity check colorId 2 is free)
2. add_time_category(name="Reading", color_id=2, aliases=["reading","book"])
3. Reply: "Added Reading (color 2/Sage). Aliases: reading, book. Notion select option synced. Inbox bot restarted."
Justin sends to inbox bot: "log 30m of reading" → bot creates a calendar event with colorId 2. Aggregator picks it up on next run, totals it under Reading.
GCal color reference¶
Colors 1..11. Currently used: 1 Lavender (Sleep), 5 Banana (Errands), 6 Tangerine (Entertainment), 7 Peacock (Travel), 9 Blueberry (Work), 10 Basil (Personal), 11 Tomato (Workout). Free: 2 Sage, 3 Grape, 4 Flamingo, 8 Graphite. Once all 11 are used, no more categories can be added unless one is removed first.
Sunset notes¶
- Old wide-format
Time DailyDB (id3510950b-d7a9-8150-bee9-f611b878afe5) is no longer written by the aggregator. Keeping it for ~1 week then archive. - Old hardcoded
_CATEGORY_COLORdict inforge_telegram_inbox_brain.pyand_PATTERNSregex inforge_time_daily_aggregator.pywere both deleted; both modules now read from the JSON.
[Claude Code, 2026-04-30]