Skip to content

feat(advice): flag-gated auto-advice + always-on budget exceeded alerts#95

Merged
alex-mextner merged 5 commits into
mainfrom
feat/auto-advice-flag
May 22, 2026
Merged

feat(advice): flag-gated auto-advice + always-on budget exceeded alerts#95
alex-mextner merged 5 commits into
mainfrom
feat/auto-advice-flag

Conversation

@alex-mextner
Copy link
Copy Markdown
Owner

Summary

  • Add AUTO_ADVICE_ENABLED env flag (default false)
  • maybeSmartAdvice three-way dispatch:
    • budget_threshold:exceeded → factual message to chat, once per month per category
    • Any other trigger + AUTO_ADVICE_ENABLED=true → AI advice to chat
    • Any other trigger + AUTO_ADVICE_ENABLED=false → log context only, nothing sent
  • checkSmartTriggers and all trigger logic in advice-triggers.ts unchanged
  • Closes disable-auto-advice (PR Suppress auto-advice, log trigger + context #87) and claude/financial-alert-env-flag-8BhYg

Once-per-month dedup for budget_exceeded

checkSmartTriggers already calls hasTopicThisMonth before returning exceeded triggers.
maybeSmartAdvice writes the advice_log entry after sending — that entry is what
hasTopicThisMonth finds on the next call. No code changes to trigger logic needed.

Test plan

  • bun run type-check — green
  • bun run lint — green
  • bun run test — all pass (3412/3412)
  • Prod, flag off: expense exceeds Food budget → one ⚠️ message; next expense same category same month → no repeat
  • Prod, flag off: velocity spike appears in pm2 logs as suppressed, nothing in chat
  • Set AUTO_ADVICE_ENABLED=true, restart → velocity spike fires AI advice in chat

🤖 Generated with Claude Code

- budget_threshold:exceeded → factual message to chat + advice_log (monthly dedup)
- other triggers + AUTO_ADVICE_ENABLED=true → AI advice via sendSmartAdvice
- other triggers + AUTO_ADVICE_ENABLED=false → log only, no Telegram message
- recordAdviceSent moved from sendSmartAdvice to caller (maybeSmartAdvice)
- formatAmount used for budget exceeded amounts (per project convention)
- add test: suppressed logs consume daily quota (Codex review finding)
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 21, 2026

🔁 PR closed. Stage bot switched back to main.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bed6c3cb9a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/bot/commands/ask.ts
Comment on lines +259 to +260
await sendSmartAdvice(groupId, trigger, snapshot);
recordAdviceSent(groupId, trigger.tier);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Record cooldown only after advice is actually delivered

recordAdviceSent now runs unconditionally after sendSmartAdvice, but sendSmartAdvice can return without sending anything (e.g., provider failure is swallowed in its catch, validator rejection, or short/empty output). In those cases this still consumes the in-memory tier cooldown, so subsequent triggers are suppressed for 1h/4h even though the user received no advice.

Useful? React with 👍 / 👎.

Comment thread src/bot/commands/ask.ts Outdated
Comment on lines +244 to +246
await sendMessage(text);
database.adviceLogs.create({
group_id: groupId,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Skip budget dedup log when alert message was not sent

This path writes an advice_log entry immediately after sendMessage without checking whether sending succeeded. In this codebase sendMessage is best-effort and can return null on delivery/parse errors, so a failed send still marks the topic as sent for monthly dedup and prevents retries, causing users to miss the budget-exceeded alert for the rest of the month.

Useful? React with 👍 / 👎.

alex-mextner and others added 3 commits May 22, 2026 10:11
…t duplicate alerts

Race condition: two expenses added quickly both pass hasTopicThisMonth before
either commits to DB (sendMessage is async and yields the event loop mid-flight).
Fix: DB write + recordAdviceSent happen synchronously before the await.
Also adds recordAdviceSent for :exceeded triggers (was missing, left cooldown stale).

Regression test verifies advice_log is written before sendMessage is called.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The `:exceeded` branch in `maybeSmartAdvice` was added in bed6c3c
as part of the three-way dispatch, creating a second code path that
sent a `⚠️ бюджет превышен` message every time a budget was exceeded.
The original `checkBudgetLimit` in `expense-saver.ts` already sent
`🔴 ПРЕВЫШЕН БЮДЖЕТ!` — so two messages fired on every save.

Fix:
- Remove the `:exceeded` dispatch from `maybeSmartAdvice` and
  `checkSmartTriggers`; exceeded is a fact handled synchronously
  by `checkBudgetLimit`, not by the async advice flow.
- Add monthly dedup to `checkBudgetLimit`: writes to `advice_log`
  before `sendMessage` so concurrent saves can't both fire.
- Simplify the early-continue guard in `checkSmartTriggers` to
  remove the now-dead `!== 'exceeded'` exception.
- Update tests accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@alex-mextner alex-mextner merged commit 0fe1287 into main May 22, 2026
4 checks passed
@alex-mextner alex-mextner deleted the feat/auto-advice-flag branch May 22, 2026 10:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant