Skip to content

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 Daily DB (id 3510950b-d7a9-8150-bee9-f611b878afe5) is no longer written by the aggregator. Keeping it for ~1 week then archive.
  • Old hardcoded _CATEGORY_COLOR dict in forge_telegram_inbox_brain.py and _PATTERNS regex in forge_time_daily_aggregator.py were both deleted; both modules now read from the JSON.

[Claude Code, 2026-04-30]