Skip to content
Merged
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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ The gate has a **second pass** for sources that emit `sleep_unspecified` only (n
| `REPORT_EVENING_WEEKEND` | `21` | Evening report hour on weekends |
| `REPORT_MORNING_CAP` | `morningHour+4` (floor 11) | Smart-retry deadline for morning report — past this hour the report force-sends with a stale-data banner regardless of `SleepSettled`. Server prefers an adaptive cap from `GetTypicalWakeTime(14) + 60min` when ≥7 days of per-segment sleep data are available; falls back to this env value otherwise. Override via env or `report_morning_cap` setting. **Floor:** `MorningCapTime` pushes the cap to `now + MinPromptWindow` (60min) whenever the computed cap is already in the past at call time — adaptive cap can land before `morning_hour` for users whose schedule puts the morning report well after typical wake, and without the floor the smart-retry loop enters past cap on first tick and silently skips `MorningActionPrompt`, disabling subjective check-in entirely. Pinned by `TestMorningCapTime_FloorsPastCapsToPromptWindow` |
| `REPORT_TZ` | system local | Timezone for report scheduling (e.g. `Europe/Belgrade`) |
| `TELEGRAM_RICH_MESSAGES` | `false` | Opt-in Telegram Bot API Rich Messages for morning/evening reports. DB setting `telegram_rich_messages` uses the same boolean semantics. Fallback stays on legacy `sendMessage` HTML when rich send fails. |
| `GEMINI_API_KEY` | — | Gemini API key; enables AI morning briefing |
| `GEMINI_MODEL` | `gemini-2.5-flash` | Gemini model; overridable in Admin UI |
| `GEMINI_MAX_TOKENS` | `5000` | Max output tokens for AI briefing; overridable in Admin UI |
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ The gate has a **second pass** for sources that emit `sleep_unspecified` only (n
| `REPORT_EVENING_WEEKEND` | `21` | Evening report hour on weekends |
| `REPORT_MORNING_CAP` | `morningHour+4` (floor 11) | Smart-retry deadline for morning report — past this hour the report force-sends with a stale-data banner regardless of `SleepSettled`. Server prefers an adaptive cap from `GetTypicalWakeTime(14) + 60min` when ≥7 days of per-segment sleep data are available; falls back to this env value otherwise. Override via env or `report_morning_cap` setting. **Floor:** `MorningCapTime` pushes the cap to `now + MinPromptWindow` (60min) whenever the computed cap is already in the past at call time — adaptive cap can land before `morning_hour` for users whose schedule puts the morning report well after typical wake, and without the floor the smart-retry loop enters past cap on first tick and silently skips `MorningActionPrompt`, disabling subjective check-in entirely. Pinned by `TestMorningCapTime_FloorsPastCapsToPromptWindow` |
| `REPORT_TZ` | system local | Timezone for report scheduling (e.g. `Europe/Belgrade`) |
| `TELEGRAM_RICH_MESSAGES` | `false` | Opt-in Telegram Bot API Rich Messages for morning/evening reports. DB setting `telegram_rich_messages` uses the same boolean semantics. Fallback stays on legacy `sendMessage` HTML when rich send fails. |
| `GEMINI_API_KEY` | — | Gemini API key; enables AI morning briefing |
| `GEMINI_MODEL` | `gemini-2.5-flash` | Gemini model; overridable in Admin UI |
| `GEMINI_MAX_TOKENS` | `5000` | Max output tokens for AI briefing; overridable in Admin UI |
Expand Down
47 changes: 29 additions & 18 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,16 @@ func main() {

// Env-level defaults for the first/only tenant.
envNotifyDefaults := storage.NotifyConfig{
Token: os.Getenv("TELEGRAM_TOKEN"),
ChatID: os.Getenv("TELEGRAM_CHAT_ID"),
Lang: getEnv("REPORT_LANG", "en"),
Timezone: getEnv("REPORT_TZ", ""),
MorningWeekdayHour: getEnvInt("REPORT_MORNING_WEEKDAY", 8),
MorningWeekendHour: getEnvInt("REPORT_MORNING_WEEKEND", 9),
EveningWeekdayHour: getEnvInt("REPORT_EVENING_WEEKDAY", 20),
EveningWeekendHour: getEnvInt("REPORT_EVENING_WEEKEND", 21),
MorningCapHour: getEnvInt("REPORT_MORNING_CAP", 0),
Token: os.Getenv("TELEGRAM_TOKEN"),
ChatID: os.Getenv("TELEGRAM_CHAT_ID"),
Lang: getEnv("REPORT_LANG", "en"),
Timezone: getEnv("REPORT_TZ", ""),
MorningWeekdayHour: getEnvInt("REPORT_MORNING_WEEKDAY", 8),
MorningWeekendHour: getEnvInt("REPORT_MORNING_WEEKEND", 9),
EveningWeekdayHour: getEnvInt("REPORT_EVENING_WEEKDAY", 20),
EveningWeekendHour: getEnvInt("REPORT_EVENING_WEEKEND", 21),
TelegramRichMessages: getEnvBool("TELEGRAM_RICH_MESSAGES", false),
MorningCapHour: getEnvInt("REPORT_MORNING_CAP", 0),
}
envAIDefaults := storage.AIConfig{
APIKey: os.Getenv("GEMINI_API_KEY"),
Expand Down Expand Up @@ -506,15 +507,16 @@ func tenantLocalNow(db *storage.DB, defaults storage.NotifyConfig) time.Time {
// finding all three call sites.
func buildNotifyCfg(db *storage.DB, c storage.NotifyConfig) notify.Config {
cfg := notify.Config{
Token: c.Token,
ChatID: c.ChatID,
Lang: c.Lang,
Timezone: c.Timezone,
MorningWeekdayHour: c.MorningWeekdayHour,
MorningWeekendHour: c.MorningWeekendHour,
EveningWeekdayHour: c.EveningWeekdayHour,
EveningWeekendHour: c.EveningWeekendHour,
MorningCapHour: c.MorningCapHour,
Token: c.Token,
ChatID: c.ChatID,
Lang: c.Lang,
Timezone: c.Timezone,
MorningWeekdayHour: c.MorningWeekdayHour,
MorningWeekendHour: c.MorningWeekendHour,
EveningWeekdayHour: c.EveningWeekdayHour,
EveningWeekendHour: c.EveningWeekendHour,
TelegramRichMessages: c.TelegramRichMessages,
MorningCapHour: c.MorningCapHour,
}
if h, m, ok := db.GetTypicalWakeTime(14); ok {
cfg.TypicalWakeHour = h
Expand Down Expand Up @@ -1230,3 +1232,12 @@ func getEnvInt(key string, fallback int) int {
}
return fallback
}

func getEnvBool(key string, fallback bool) bool {
if v := os.Getenv(key); v != "" {
if b, err := strconv.ParseBool(v); err == nil {
return b
}
}
return fallback
}
Loading