
Savings Goals
- 391 installs
- 161 repo stars
- Updated July 18, 2026
- joellewis/finance_skills
savings-goals is a UX design skill that helps developers build goal-based savings experiences with targets, progress tracking, and nudges in consumer finance apps.
About
savings-goals is a UX design skill that guides developers implementing goal-based savings features such as creating a target amount, tracking progress over time, and triggering nudges that encourage continued deposits. savings-goals fits mobile and web finance products where a developer needs to convert product intent into concrete UI states, backend fields, and event-driven behaviors. savings-goals focuses on the mechanics that make a goals feature feel reliable: how to represent goal status, handle edits and partial funding, and communicate progress clearly across screens and notifications. savings-goals is useful when a team is adding savings journeys to an existing ledger or balance system and needs a consistent UX pattern for goal creation, contributions, milestones, and user feedback loops.
- Target amounts
- Progress tracking
- Contribution nudges
- Timeline projections
- Milestone rewards
Savings Goals by the numbers
- 391 all-time installs (skills.sh)
- +15 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #272 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/joellewis/finance_skills --skill savings-goalsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 391 |
|---|---|
| repo stars | ★ 161 |
| Last updated | July 18, 2026 |
| Repository | joellewis/finance_skills ↗ |
How do you design a savings goals UX?
Build goal-based savings UX with targets, progress tracking, and nudges for retail finance applications.
Who is it for?
savings-goals fits developers implementing a goals feature in a consumer finance product with clear progress visualization.
Skip if: savings-goals is not for developers who only need banking connectivity or transaction ingestion.
When should I use this skill?
Invoke when a developer asks about building a goal-based savings feature, progress tracking UI, or nudge timing rules.
What you get
Savings goal UX flow, UI states for progress, nudge trigger ideas, goal data model fields.
- ux flow
- ui state map
- nudge trigger rules
Files
Savings Goals
Core Concepts
Required Monthly Savings
To accumulate a future value FV in n periods at rate r per period:
PMT = FV × r / [(1+r)^n - 1]
This is the sinking fund formula (future value of annuity solved for PMT).
Inflation-Adjusted Targets
Always compute goals in future (nominal) dollars:
FV_nominal = FV_today × (1 + inflation)^years
Then solve for the required savings using the nominal return, or use the real return with today's dollars.
Education Funding
- 529 plans: tax-free growth for qualified education expenses, state tax deductions in many states
- Current costs (2025-26, total cost of attendance, College Board Trends in College Pricing): ~$30K/year (public four-year in-state) to ~$65K/year (private nonprofit), growing ~5%/year
- Front-loading: maximize early contributions for compound growth
- Superfunding: 5-year gift tax averaging (contribute 5× annual exclusion at once)
- Financial aid impact: parent-owned 529 assets are assessed at a maximum of 5.64% in the federal Student Aid Index (SAI) formula — FAFSA has used the SAI in place of the Expected Family Contribution (EFC) since the 2024-25 award year
Retirement Accumulation
- Target nest egg: annual spending need / safe withdrawal rate
- Example: $80K/year spending / 0.04 = $2,000,000
- Safe withdrawal rate: traditionally 4% (Bengen rule), adjusted for fees, taxes, longevity
- Required savings rate: depends on starting age, current savings, expected returns
- Employer match: always capture full match — it's an immediate 50-100% return
- Catch-up contributions: additional 401(k)/IRA contributions allowed after age 50
Down Payment Saving
- Typical target: 20% of home price (avoids PMI)
- Timeline: typically 2-7 years → conservative allocation (HYSA, short-term bonds)
- Include closing costs (2-5% of purchase price) in savings target
Goal Priority Framework
Recommended priority order: 1. Emergency fund (3-6 months expenses) 2. Employer 401(k) match (free money) 3. High-interest debt payoff (>6-8% rate) 4. HSA (triple tax advantage if eligible) 5. Max retirement accounts (401k, IRA, Roth) 6. Education funding (529) 7. Other goals (home, vacation, etc.)
Multiple Goal Balancing
- Allocate savings across goals based on priority, timeline, and flexibility
- Non-negotiable goals (retirement) take precedence over flexible goals
- Shorter timelines need more conservative investment allocation
- Use goal-based investing: separate sub-portfolios per goal with appropriate risk
Savings Rate Benchmarks
- Minimum: 15% of gross income for retirement (including employer match)
- Aggressive: 25-50%+ for early retirement / FIRE
- Savings rate = total savings / gross income
Key Formulas
| Formula | Expression | Use Case |
|---|---|---|
| Required savings (PMT) | PMT = FV × r / [(1+r)^n - 1] | Monthly savings for a goal |
| Future value with savings | FV = PV(1+r)^n + PMT×[(1+r)^n - 1]/r | Project goal balance |
| Inflation adjustment | FV_real = FV_today × (1+π)^t | Convert today's dollars to future |
| Retirement target | Nest egg = annual spend / SWR | Size the retirement goal |
| Years to goal | n = ln(FV×r/PMT + 1) / ln(1+r) | How long until goal is funded |
| Savings rate | SR = total savings / gross income | Track savings discipline |
Worked Examples
Example 1: College Savings (529)
Given: Need $200,000 in 18 years, expect 7% annual return, starting from $0 Calculate: Required monthly savings Solution:
- Monthly rate: r = 0.07/12 = 0.005833
- Months: n = 18 × 12 = 216
- PMT = $200,000 × 0.005833 / [(1.005833)^216 - 1]
- PMT = $1,166.67 / [3.5125 - 1]
- PMT = $1,166.67 / 2.5125 = $464.34/month
Example 2: Retirement Accumulation
Given: Age 30, $50,000 currently saved, wants $2,000,000 by age 65, expects 8% annual return Calculate: Required monthly savings Solution:
- Monthly rate: r = 0.08/12 = 0.006667; months: n = 35 × 12 = 420
- FV of current savings (monthly compounding): $50,000 × (1.006667)^420 = $50,000 × 16.2925 = $814,627
- Remaining needed: $2,000,000 - $814,627 = $1,185,373
- PMT = $1,185,373 × 0.006667 / [(1.006667)^420 - 1]
- PMT = $7,902.49 / [16.2925 - 1]
- PMT = $7,902.49 / 15.2925 = $517/month
- With employer match of $200/mo: personal contribution = $317/month
Common Pitfalls
- Not inflation-adjusting future goals (college in 18 years costs much more than today)
- Neglecting employer match — it's the highest guaranteed return available
- Too conservative allocation for long-horizon goals (20+ years can tolerate equity risk)
- Saving for college before adequately funding retirement (retirement has no financial aid)
- Not revisiting savings rate as income grows (lifestyle creep absorbs raises)
- Using average returns without considering sequence risk near goal date
Cross-References
- time-value-of-money (core plugin, Layer 0): FV/PV calculations, annuity formulas
- emergency-fund (wealth-management plugin, Layer 6): must be funded before other goals
- debt-management (wealth-management plugin, Layer 6): high-interest debt payoff competes with savings
- tax-efficiency (wealth-management plugin, Layer 5): 529 tax benefits, Roth vs traditional, HSA
- investment-policy (wealth-management plugin, Layer 5): goal-based allocation aligns with IPS constraints
- asset-allocation (wealth-management plugin, Layer 4): glide paths for target-date retirement savings
- finance-psychology (wealth-management plugin, Layer 7): mental accounting, present bias, commitment devices
- financial-planning-workflow (advisory-practice plugin, Layer 10): savings goals are key inputs to the comprehensive financial planning process
Running the script
Run the reference implementation directly:
uv run scripts/savings_goals.py # PEP 723 header resolves dependencies automatically
python3 scripts/savings_goals.py # standard library only — no installs neededA bare run prints a demo covering required monthly savings, retirement accumulation, time-to-goal, inflation adjustment, shortfall analysis, education funding, savings rate, and real returns. Use --verify to recompute the demo figures and assert they match this skill's worked examples (prints PASS/FAIL, exits nonzero on mismatch), and --help to list the available classes and functions. The file is primarily meant to be imported as a module (from savings_goals import SavingsGoals) rather than run standalone.
# /// script
# dependencies = []
# requires-python = ">=3.11"
# ///
"""
Savings Goals
=============
Compute required savings rates, future value projections, time-to-goal,
inflation-adjusted targets, and multi-goal prioritization.
Part of Layer 6 (Personal Finance) in the finance skills framework.
"""
import argparse
import math
import sys
class SavingsGoals:
"""Savings goal computations: required savings, projections, and goal analysis.
All methods are static — no instance state is required.
"""
@staticmethod
def required_monthly_savings(
future_value: float,
annual_rate: float,
years: int,
current_savings: float = 0.0,
) -> float:
"""Compute the required monthly savings to reach a future goal.
Uses the sinking fund formula: PMT = FV * r / [(1+r)^n - 1],
adjusted for existing savings that will compound.
Parameters
----------
future_value : float
Target amount in dollars (nominal or real, depending on rate used).
annual_rate : float
Expected annual return as a decimal (e.g., 0.07 for 7%).
years : int
Number of years until the goal.
current_savings : float, optional
Amount already saved toward this goal. Default is 0.0.
Returns
-------
float
Required monthly savings amount.
"""
if years <= 0:
return max(future_value - current_savings, 0.0)
r = annual_rate / 12.0
n = years * 12
# Future value of current savings
if current_savings > 0:
fv_existing = current_savings * (1.0 + r) ** n
else:
fv_existing = 0.0
remaining_fv = future_value - fv_existing
if remaining_fv <= 0:
return 0.0
if r == 0:
return remaining_fv / n
# Sinking fund formula: PMT = FV * r / [(1+r)^n - 1]
factor = (1.0 + r) ** n - 1.0
return remaining_fv * r / factor
@staticmethod
def future_value_with_contributions(
current_savings: float,
monthly_contribution: float,
annual_rate: float,
years: int,
) -> float:
"""Project the future value of current savings plus regular contributions.
FV = PV(1+r)^n + PMT * [(1+r)^n - 1] / r
Parameters
----------
current_savings : float
Amount currently saved.
monthly_contribution : float
Regular monthly contribution.
annual_rate : float
Expected annual return as a decimal.
years : int
Number of years to project.
Returns
-------
float
Projected future value.
"""
r = annual_rate / 12.0
n = years * 12
fv_lump = current_savings * (1.0 + r) ** n
if r == 0:
fv_annuity = monthly_contribution * n
else:
fv_annuity = monthly_contribution * ((1.0 + r) ** n - 1.0) / r
return fv_lump + fv_annuity
@staticmethod
def time_to_goal(
future_value: float,
monthly_contribution: float,
annual_rate: float,
current_savings: float = 0.0,
) -> float:
"""Compute the number of years to reach a savings goal.
Solves n = ln(FV*r/PMT + 1) / ln(1+r), adjusted for existing savings.
Parameters
----------
future_value : float
Target amount in dollars.
monthly_contribution : float
Regular monthly savings amount.
annual_rate : float
Expected annual return as a decimal.
current_savings : float, optional
Amount already saved. Default is 0.0.
Returns
-------
float
Years until the goal is reached. Returns inf if contributions
are insufficient to ever reach the goal.
"""
if current_savings >= future_value:
return 0.0
r = annual_rate / 12.0
if r == 0:
if monthly_contribution <= 0:
return float("inf")
months = (future_value - current_savings) / monthly_contribution
return months / 12.0
# We need to solve: PV*(1+r)^n + PMT*[(1+r)^n - 1]/r = FV
# Let x = (1+r)^n: PV*x + PMT*(x-1)/r = FV
# PV*x + PMT*x/r - PMT/r = FV
# x*(PV + PMT/r) = FV + PMT/r
# x = (FV + PMT/r) / (PV + PMT/r)
pmt_over_r = monthly_contribution / r
numerator = future_value + pmt_over_r
denominator = current_savings + pmt_over_r
if denominator <= 0 or numerator / denominator <= 0:
return float("inf")
x = numerator / denominator
if x <= 1.0:
return 0.0
n_months = math.log(x) / math.log(1.0 + r)
return n_months / 12.0
@staticmethod
def inflation_adjusted_goal(
todays_cost: float, inflation_rate: float, years: int
) -> float:
"""Adjust a goal amount for inflation.
FV_nominal = FV_today * (1 + inflation)^years
Parameters
----------
todays_cost : float
Cost in today's dollars.
inflation_rate : float
Expected annual inflation as a decimal (e.g., 0.03 for 3%).
years : int
Years until the expense occurs.
Returns
-------
float
Inflation-adjusted (nominal) cost.
"""
return todays_cost * (1.0 + inflation_rate) ** years
@staticmethod
def real_rate_of_return(nominal_rate: float, inflation_rate: float) -> float:
"""Compute the real (inflation-adjusted) rate of return.
real_rate = (1 + nominal) / (1 + inflation) - 1
Parameters
----------
nominal_rate : float
Nominal annual return as a decimal.
inflation_rate : float
Annual inflation rate as a decimal.
Returns
-------
float
Real rate of return as a decimal.
"""
return (1.0 + nominal_rate) / (1.0 + inflation_rate) - 1.0
@staticmethod
def retirement_target(
annual_spending: float, safe_withdrawal_rate: float = 0.04
) -> float:
"""Compute the target nest egg for retirement.
Nest egg = annual spending / safe withdrawal rate
Parameters
----------
annual_spending : float
Desired annual spending in retirement.
safe_withdrawal_rate : float, optional
Safe withdrawal rate as a decimal. Default is 0.04 (4%).
Returns
-------
float
Required retirement savings target.
"""
if safe_withdrawal_rate <= 0:
return float("inf")
return annual_spending / safe_withdrawal_rate
@staticmethod
def savings_rate(total_savings: float, gross_income: float) -> float:
"""Compute the personal savings rate.
Savings rate = total savings / gross income
Parameters
----------
total_savings : float
Total amount saved in the period.
gross_income : float
Gross income for the period.
Returns
-------
float
Savings rate as a decimal.
"""
if gross_income <= 0:
return 0.0
return total_savings / gross_income
@staticmethod
def contribution_shortfall(
future_value: float,
annual_rate: float,
years: int,
current_savings: float,
current_monthly: float,
) -> dict:
"""Assess whether current contributions are on track for a goal.
Parameters
----------
future_value : float
Target goal amount.
annual_rate : float
Expected annual return as a decimal.
years : int
Years until the goal.
current_savings : float
Amount already saved.
current_monthly : float
Current monthly contribution.
Returns
-------
dict
Keys: 'projected_value', 'target', 'surplus_or_shortfall',
'required_monthly', 'additional_monthly_needed', 'on_track' (bool).
"""
projected = SavingsGoals.future_value_with_contributions(
current_savings, current_monthly, annual_rate, years
)
required = SavingsGoals.required_monthly_savings(
future_value, annual_rate, years, current_savings
)
additional = max(required - current_monthly, 0.0)
return {
"projected_value": round(projected, 2),
"target": round(future_value, 2),
"surplus_or_shortfall": round(projected - future_value, 2),
"required_monthly": round(required, 2),
"additional_monthly_needed": round(additional, 2),
"on_track": projected >= future_value,
}
@staticmethod
def education_funding_plan(
annual_cost_today: float,
years_until_enrollment: int,
years_of_education: int = 4,
education_inflation: float = 0.05,
investment_return: float = 0.07,
current_529_balance: float = 0.0,
) -> dict:
"""Plan education funding with inflation-adjusted costs.
Parameters
----------
annual_cost_today : float
Current annual cost of education.
years_until_enrollment : int
Years until the student begins education.
years_of_education : int, optional
Duration of education in years. Default is 4.
education_inflation : float, optional
Annual education cost inflation. Default is 0.05 (5%).
investment_return : float, optional
Expected annual return on 529 investments. Default is 0.07 (7%).
current_529_balance : float, optional
Current 529 plan balance. Default is 0.0.
Returns
-------
dict
Keys: 'total_cost_nominal', 'cost_by_year' (list),
'required_monthly_savings', 'current_balance_fv'.
"""
# Compute inflated cost for each year of education
cost_by_year: list[float] = []
total_cost = 0.0
for i in range(years_of_education):
year_number = years_until_enrollment + i
inflated_cost = annual_cost_today * (1.0 + education_inflation) ** year_number
cost_by_year.append(round(inflated_cost, 2))
total_cost += inflated_cost
# For simplicity, compute required savings to accumulate the total
# by enrollment (conservative: need the full amount at enrollment)
total_pv_at_enrollment = 0.0
r_monthly = investment_return / 12.0
for i in range(years_of_education):
# Discount each year's cost back to enrollment date
months_after_enrollment = i * 12
if r_monthly > 0:
pv_factor = 1.0 / (1.0 + r_monthly) ** months_after_enrollment
else:
pv_factor = 1.0
total_pv_at_enrollment += cost_by_year[i] * pv_factor
required_monthly = SavingsGoals.required_monthly_savings(
total_pv_at_enrollment,
investment_return,
years_until_enrollment,
current_529_balance,
)
fv_current = current_529_balance * (1.0 + r_monthly) ** (years_until_enrollment * 12)
return {
"total_cost_nominal": round(total_cost, 2),
"cost_by_year": cost_by_year,
"total_needed_at_enrollment": round(total_pv_at_enrollment, 2),
"required_monthly_savings": round(required_monthly, 2),
"current_balance_fv": round(fv_current, 2),
}
def _demo() -> None:
# ----------------------------------------------------------------
# Demo: Savings goals computations
# ----------------------------------------------------------------
SG = SavingsGoals
print("=" * 60)
print("Savings Goals - Demo")
print("=" * 60)
# --- Example 1: College Savings (529) ---
print("\n--- Example 1: College Savings (529) ---")
monthly = SG.required_monthly_savings(200_000, 0.07, 18, current_savings=0)
print(f" Goal: $200,000 in 18 years @ 7% annual return")
print(f" Required monthly savings: ${monthly:,.2f}")
# Verify with future value projection
fv = SG.future_value_with_contributions(0, monthly, 0.07, 18)
print(f" Verification (FV of contributions): ${fv:,.2f}")
# --- Example 2: Retirement Accumulation ---
print("\n--- Example 2: Retirement Accumulation ---")
target = SG.retirement_target(80_000, 0.04)
print(f" $80K/yr spending, 4% SWR -> target: ${target:,.0f}")
monthly_ret = SG.required_monthly_savings(target, 0.08, 35, current_savings=50_000)
print(f" Age 30, $50K saved, 35 years @ 8%: ${monthly_ret:,.2f}/mo needed")
with_match = monthly_ret - 200
print(f" With $200/mo employer match: ${with_match:,.2f}/mo personal")
# --- Example 3: Time to Goal ---
print("\n--- Example 3: Time to Goal ---")
years = SG.time_to_goal(100_000, 500, 0.06, current_savings=10_000)
print(f" $100K goal, saving $500/mo @ 6%, starting with $10K")
print(f" Time to goal: {years:.1f} years ({years * 12:.0f} months)")
# --- Example 4: Inflation Adjustment ---
print("\n--- Example 4: Inflation-Adjusted Goal ---")
today_cost = 25_000 # annual college cost today
future_cost = SG.inflation_adjusted_goal(today_cost, 0.05, 18)
print(f" $25K/yr college cost today, 5% education inflation, 18 years")
print(f" Future annual cost: ${future_cost:,.2f}")
total_4yr = sum(
SG.inflation_adjusted_goal(today_cost, 0.05, 18 + i) for i in range(4)
)
print(f" Total 4-year cost: ${total_4yr:,.2f}")
# --- Example 5: Shortfall Analysis ---
print("\n--- Example 5: Contribution Shortfall Analysis ---")
analysis = SG.contribution_shortfall(
future_value=500_000,
annual_rate=0.07,
years=20,
current_savings=30_000,
current_monthly=400,
)
print(f" Goal: $500K in 20 years @ 7%")
print(f" Currently: $30K saved, contributing $400/mo")
print(f" Projected value: ${analysis['projected_value']:,.2f}")
print(f" Target: ${analysis['target']:,.2f}")
print(f" Surplus/shortfall: ${analysis['surplus_or_shortfall']:,.2f}")
print(f" Required monthly: ${analysis['required_monthly']:,.2f}")
print(f" Additional needed: ${analysis['additional_monthly_needed']:,.2f}")
print(f" On track: {analysis['on_track']}")
# --- Example 6: Education Funding Plan ---
print("\n--- Example 6: Education Funding Plan ---")
plan = SG.education_funding_plan(
annual_cost_today=25_000,
years_until_enrollment=18,
years_of_education=4,
education_inflation=0.05,
investment_return=0.07,
current_529_balance=5_000,
)
print(f" Annual cost today: $25K, enrollment in 18 years")
print(f" Total nominal cost: ${plan['total_cost_nominal']:,.2f}")
print(" Year-by-year costs:")
for i, cost in enumerate(plan["cost_by_year"], 1):
print(f" Year {i}: ${cost:,.2f}")
print(f" Needed at enrollment: ${plan['total_needed_at_enrollment']:,.2f}")
print(f" Current $5K grows to: ${plan['current_balance_fv']:,.2f}")
print(f" Required monthly: ${plan['required_monthly_savings']:,.2f}")
# --- Example 7: Savings Rate ---
print("\n--- Example 7: Savings Rate ---")
sr = SG.savings_rate(18_000, 120_000)
print(f" $18K saved on $120K gross income -> savings rate: {sr:.1%}")
# --- Example 8: Real Rate of Return ---
print("\n--- Example 8: Real Rate of Return ---")
real = SG.real_rate_of_return(0.08, 0.03)
print(f" 8% nominal, 3% inflation -> real return: {real:.2%}")
print("\n" + "=" * 60)
print("Demo complete.")
print("=" * 60)
def _verify() -> int:
"""Assert that demo computations match the SKILL.md worked examples."""
SG = SavingsGoals
failures: list[str] = []
def check(label: str, actual: float, expected: float, rel_tol: float = 1e-3) -> None:
ok = math.isclose(actual, expected, rel_tol=rel_tol)
print(f" {'PASS' if ok else 'FAIL'}: {label}: got {actual:,.2f}, expected {expected:,.2f}")
if not ok:
failures.append(label)
print("Verifying against SKILL.md worked examples...")
# Example 1: $200K in 18 years @ 7% from $0 -> $464.34/month
pmt_529 = SG.required_monthly_savings(200_000, 0.07, 18, current_savings=0)
check("Ex1 required monthly savings ($464.34)", pmt_529, 464.34)
fv_check = SG.future_value_with_contributions(0, pmt_529, 0.07, 18)
check("Ex1 FV of contributions ($200,000)", fv_check, 200_000.0, rel_tol=1e-6)
# Example 2: $80K/yr spending @ 4% SWR -> $2M target;
# age 30, $50K saved, 35 years @ 8% -> ~$517/month ($317 after $200 match)
target = SG.retirement_target(80_000, 0.04)
check("Ex2 retirement target ($2,000,000)", target, 2_000_000.0, rel_tol=1e-9)
pmt_ret = SG.required_monthly_savings(target, 0.08, 35, current_savings=50_000)
check("Ex2 required monthly savings ($517)", pmt_ret, 517.0)
check("Ex2 personal after $200 match ($317)", pmt_ret - 200, 317.0, rel_tol=2e-3)
if failures:
print(f"FAIL: {len(failures)} check(s) did not match SKILL.md.")
return 1
print("PASS: all checks match SKILL.md worked examples.")
return 0
def main() -> None:
parser = argparse.ArgumentParser(
description=(
"Savings goals reference implementation: required monthly savings, "
"future value projections, time-to-goal, inflation adjustment, "
"retirement targets, shortfall analysis, and education funding plans."
),
epilog=(
"Main class:\n"
" SavingsGoals -- static methods: required_monthly_savings,\n"
" future_value_with_contributions, time_to_goal,\n"
" inflation_adjusted_goal, real_rate_of_return, retirement_target,\n"
" savings_rate, contribution_shortfall, education_funding_plan\n"
"\n"
"This file is primarily meant to be imported as a module:\n"
" from savings_goals import SavingsGoals\n"
"\n"
"Run with no arguments to print a worked demo."
),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"--verify",
action="store_true",
help="recompute the demo figures and assert they match the SKILL.md worked examples",
)
args = parser.parse_args()
if args.verify:
sys.exit(_verify())
_demo()
if __name__ == "__main__":
main()
Related skills
How it compares
Pick this when the main work is UX and behavior design rather than compliance or broker integrations.
FAQ
What screens does a savings goals feature need?
savings-goals typically results in a goal creation flow, a goal detail screen with progress visualization, and contribution interactions, with nudge rules that can be implemented as notifications or in-app prompts tied to goal events.
What data is required to track a savings goal?
savings-goals requires a goal target, current progress amount, and status metadata such as start date and optional deadline, enabling developers to compute milestones, render UI states, and trigger nudges based on progress changes.