Skip to content

feat: life-hours framing in verdict nudge + real hourly rate calculator#28

Merged
timothylee58 merged 2 commits into
mainfrom
claude/funny-johnson-bVave
Jun 10, 2026
Merged

feat: life-hours framing in verdict nudge + real hourly rate calculator#28
timothylee58 merged 2 commits into
mainfrom
claude/funny-johnson-bVave

Conversation

@timothylee58

@timothylee58 timothylee58 commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • Life-hours framing in verdict nudge: After every check, the verdict overlay now shows an animated pill — "⏱ 4.7 hours of your working life" — so spending feels real, not abstract. The nudge prompt also instructs Claude to weave in this framing naturally (e.g. "tu bersamaan 4.7 jam kerja").
  • Real hourly rate calculator on income page: Two new sliders (daily commute 0–4h, weekly overtime 0–20h) compute the gap between apparent rate (gross ÷ 176h) and real rate (gross ÷ actual hours). A motivational callout shows "Every RM100 = X hours of your working life".
  • DB migration: Adds commute_hours_per_day and overtime_hours_per_week columns to profiles.

Test plan

  • Go to /income, set a gross salary, drag the commute and overtime sliders — verify the real rate card updates live and "Every RM100 = X hours" recalculates
  • Save income — verify no errors and the values persist on page reload
  • Run a spend check at /check — verify the verdict overlay shows the life-hours pill for amounts > 0
  • ruff check app passes (already verified)
  • npx tsc --noEmit passes (already verified)
  • Apply Supabase migration 20260610000001_life_hours_columns.sql to the linked project

https://claude.ai/code/session_017cLXjZu6kkjXRf2BemMPpp


Generated by Claude Code


Summary by cubic

Adds life-hours framing to purchase verdicts and a real hourly rate calculator so spending feels concrete. Uses saved income and work hours to compute a real hourly rate and show “X hours of your working life.”

  • New Features

    • Verdict overlay shows a pill like "⏱ 4.7 hours of your working life".
    • Nudge copy now receives life_hours_str and real_hourly_rate and includes the framing when it adds impact.
    • /income: commute (0–4h/day) and overtime (0–20h/week) sliders with live apparent vs. real hourly rate and “Every RM100 = X hours”.
    • Backend computes real hourly rate from DB income and work hours, converts amounts to life-hours; API: CheckResponse adds real_hourly_rate and life_hours_cost.
    • Stability: safe None handling in hourly-rate calc; income page keeps commute/overtime in sync after save.
  • Migration

    • Run supabase/migrations/20260610000001_life_hours_columns.sql to add commute_hours_per_day and overtime_hours_per_week to profiles, with CHECK constraints (commute 0–24h/day, overtime 0–168h/week).

Written for commit 4275334. Summary will update on new commits.

Review in cubic

- New hourly_rate_service.py: compute real hourly rate after commute/overtime,
  format as Malaysian "jam kerja" / "hari kerja", convert amount to life hours
- budget_service.py: fetch commute_hours_per_day and overtime_hours_per_week
  from profiles for the reasoning graph
- reasoning_graph.py / nudge_agent/service.py: pass life_hours_str and
  real_hourly_rate through the pipeline so Claude weaves in the framing
- CheckResponse schema + service: expose real_hourly_rate and life_hours_cost
- VerdictOverlay: animated pill showing "X hours of your working life" below nudge
- income/page.tsx: commute (0–4h/day) and overtime (0–20h/week) sliders with
  live apparent vs real hourly rate comparison and RM100 = X hours motivator
- Supabase migration: add commute_hours_per_day and overtime_hours_per_week
  columns to profiles

https://claude.ai/code/session_017cLXjZu6kkjXRf2BemMPpp
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
bajet-buddy 4275334 Jun 10 2026, 09:02 AM

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a "Life-hours framing" feature that calculates a user's real hourly rate (accounting for commute and overtime) and displays purchase costs in terms of working life hours. Key feedback includes using the real monthly income from the budget summary instead of mock profile data, defensively handling potential null values in the hourly rate calculation, synchronizing all updated fields in the frontend state upon saving, and adding database check constraints to ensure data integrity for the new columns.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread apps/api/app/agents/reasoning_graph.py Outdated

# Life-hours framing
commute, overtime = await fetch_work_hours_profile(state.user_id)
monthly_income = profile.get("monthly_income", 3200)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The profile dictionary (which is state.user_profile) contains hardcoded mock data initialized in _observe_transaction_intent (with monthly_income set to 3200.0). Using profile.get("monthly_income", 3200) here ignores the user's actual monthly income saved in the database.\n\nInstead, you should use the real monthly income fetched from the database, which is already available in state.budget_summary["total_income"].

Suggested change
monthly_income = profile.get("monthly_income", 3200)
monthly_income = state.budget_summary.get("total_income") or profile.get("monthly_income", 3200)

Comment on lines +7 to +22
def compute_real_hourly_rate(
monthly_salary: float,
commute_hours_per_day: float = 0.0,
overtime_hours_per_week: float = 0.0,
) -> float:
"""
Real hourly rate = salary / (standard + commute + overtime hours).

Standard: 22 working days × 8h = 176h/month
Commute: commute_hours_per_day × 22 days
Overtime: overtime_hours_per_week × 4.33 weeks
"""
commute_hours = commute_hours_per_day * 22
overtime_hours = overtime_hours_per_week * 4.33
total_hours = STANDARD_WORK_HOURS_PER_MONTH + commute_hours + overtime_hours
return round(monthly_salary / max(total_hours, 1), 2)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If monthly_salary is None (e.g., if the user's profile does not have a monthly income set yet), this function will raise a TypeError when performing division.\n\nAdditionally, we should defensively handle potential None values for commute_hours_per_day and overtime_hours_per_week to prevent runtime exceptions.

def compute_real_hourly_rate(\n    monthly_salary: float | None,\n    commute_hours_per_day: float | None = 0.0,\n    overtime_hours_per_week: float | None = 0.0,\n) -> float:\n    \"\"\"\n    Real hourly rate = salary / (standard + commute + overtime hours).\n\n    Standard: 22 working days × 8h = 176h/month\n    Commute:  commute_hours_per_day × 22 days\n    Overtime: overtime_hours_per_week × 4.33 weeks\n    \"\"\"\n    salary = monthly_salary or 0.0\n    commute_hours = (commute_hours_per_day or 0.0) * 22\n    overtime_hours = (overtime_hours_per_week or 0.0) * 4.33\n    total_hours = STANDARD_WORK_HOURS_PER_MONTH + commute_hours + overtime_hours\n    return round(salary / max(total_hours, 1.0), 2)

Comment thread apps/web/app/(app)/income/page.tsx Outdated
.update({ monthly_income: gross, commute_hours_per_day: commute, overtime_hours_per_week: overtime })
.eq("id", user.id);
if (dbErr) throw dbErr;
setIncome((prev) => ({ ...prev, monthly_income: gross }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When saving the income data, only monthly_income is updated in the local income state. The commute_hours_per_day and overtime_hours_per_week fields should also be synchronized in the local income state to ensure data consistency across the application.

      setIncome((prev) => ({\n        ...prev,\n        monthly_income: gross,\n        commute_hours_per_day: commute,\n        overtime_hours_per_week: overtime,\n      }));

Comment on lines +2 to +4
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS commute_hours_per_day NUMERIC(4,1) DEFAULT 0,
ADD COLUMN IF NOT EXISTS overtime_hours_per_week NUMERIC(4,1) DEFAULT 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To ensure data integrity and prevent invalid values (such as negative hours or unrealistically high values), it is highly recommended to add CHECK constraints to the new columns.

ALTER TABLE public.profiles\n  ADD COLUMN IF NOT EXISTS commute_hours_per_day  NUMERIC(4,1) DEFAULT 0 CHECK (commute_hours_per_day >= 0 AND commute_hours_per_day <= 24),\n  ADD COLUMN IF NOT EXISTS overtime_hours_per_week NUMERIC(4,1) DEFAULT 0 CHECK (overtime_hours_per_week >= 0 AND overtime_hours_per_week <= 168);

@netlify

netlify Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploy Preview for bajet-buddy failed.

Name Link
🔨 Latest commit 4275334
🔍 Latest deploy log https://app.netlify.com/projects/bajet-buddy/deploys/6a2927eb4355c100077f5ff3

- hourly_rate_service: accept None for all three params and coerce with
  `or 0.0` so missing profile data never raises TypeError
- reasoning_graph: use real DB income (budget_summary.total_income) for
  hourly rate; fall back to mock profile value only when absent
- income/page.tsx: sync commute and overtime into local income state on
  save so displayed values stay consistent
- migration: add CHECK constraints (commute 0-24h, overtime 0-168h) to
  guard data integrity

https://claude.ai/code/session_017cLXjZu6kkjXRf2BemMPpp
@timothylee58
timothylee58 marked this pull request as ready for review June 10, 2026 09:02
@timothylee58
timothylee58 merged commit d88add0 into main Jun 10, 2026
2 of 7 checks passed
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.

2 participants