AnilNiraula commited on
Commit
bbdba7d
Β·
verified Β·
1 Parent(s): 8dc9445

Follow-up context and user-set dividend growth rate

Browse files
app.py CHANGED
@@ -58,7 +58,7 @@ ROUTER_SCHEMA = {"type": "object", "additionalProperties": False, "required": ["
58
  _call("compound_interest", {"principal": _N, "rate": _N, "years": _I, "contribution": _N},
59
  ["principal", "rate", "years"]),
60
  _call("get_current_prices", {"tickers": _TICKERS}, ["tickers"]),
61
- _call("get_dividends", {"ticker": _S, "amount": _N, "years": _I, "gain_pct": _N, "price_growth": _N}, ["ticker"]),
62
  _call("analyze_portfolio"),
63
  _call("search_notes", {"query": _S}, ["query"]),
64
  _call("greet"),
@@ -70,7 +70,7 @@ ROUTER_PROMPT = """Pick tool calls as JSON for a finance assistant. Today: {toda
70
  get_cagr: past annual return of tickers between years. Use Yahoo tickers (Nvidia->NVDA); keep typed symbols. "since 2020" -> end_year {year}.
71
  compound_interest: savings growth; rate in %; 10k=10000; contribution=yearly addition.
72
  get_current_prices: latest price.
73
- get_dividends: dividend yield/payments/growth for one ticker; amount=$ invested, gain_pct=% gain mentioned, price_growth=%/yr if given.
74
  analyze_portfolio: only "my portfolio/holdings".
75
  search_notes: explain finance concepts.
76
  greet: hello / what can you do.
@@ -190,13 +190,23 @@ def _amounts(text):
190
  return out
191
 
192
 
 
 
 
 
 
 
193
  def dividend_args(message, ticker):
194
  gain = re.search(r"(\d+(?:\.\d+)?)\s*%\s*(?:gain|up|profit)|up\s*(\d+(?:\.\d+)?)\s*%", message, re.I)
195
  pg = re.search(r"(\d+(?:\.\d+)?)\s*%\s*(?:/\s*y\w*\s*)?(?:price growth|appreciation|price increase)", message, re.I)
196
  yrs = re.search(r"(\d+)\s*-?\s*(?:years?|yrs?|yr)\b", message, re.I)
197
  rest = re.sub(r"\d+(?:\.\d+)?\s*%|\d+\s*-?\s*(?:years?|yrs?|yr)\b|\b(?:19|20)\d{2}\b", " ", message, flags=re.I)
198
  amts = [a for a in _amounts(rest) if a >= 1]
 
 
199
  call = {"tool": "get_dividends", "ticker": ticker, "years": int(yrs.group(1)) if yrs else 5}
 
 
200
  if amts:
201
  call["amount"] = max(amts)
202
  if gain:
@@ -256,6 +266,76 @@ def fast_calls(message):
256
  return None
257
 
258
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
259
  def _message_numbers(text):
260
  """Numbers literally present in the user's text (10k -> 10000, 1.5m -> 1500000). Used to reject
261
  tool arguments the model made up; this is argument validation, not intent detection."""
@@ -275,7 +355,7 @@ def grounded(call, message, history=None):
275
  need.append(call["contribution"])
276
  return all(v is not None and round(float(v), 4) in nums for v in need)
277
  if t == "get_dividends":
278
- opt = [call.get(k) for k in ("amount", "gain_pct", "price_growth") if call.get(k)]
279
  if call.get("years") not in (None, 5):
280
  opt.append(call["years"])
281
  return all(round(float(v), 4) in nums for v in opt)
@@ -324,7 +404,7 @@ def run_calls(calls, portfolio=None, growth_rate=10):
324
  c.get("contribution", 0)))
325
  elif t == "get_dividends":
326
  res["blocks"].append(tools.dividend_report(c.get("ticker"), c.get("amount"), c.get("years") or 5,
327
- c.get("gain_pct"), c.get("price_growth")))
328
  elif t == "get_current_prices":
329
  res["blocks"].append(tools.prices_report(c.get("tickers", [])))
330
  elif t == "analyze_portfolio":
@@ -391,6 +471,15 @@ def answer_stream(message, history=None, system_prompt=DEFAULT_SYSTEM_PROMPT, ma
391
  info.update(calls=fc, fallback=False, fast=True, declined=False)
392
  yield "\n".join(f"Data is not available for {t}." for t in fc[0]["tickers"]), None
393
  return
 
 
 
 
 
 
 
 
 
394
  if fc: # unambiguous numeric request: run the tool directly, no LLM call (~1 s)
395
  res = run_calls(fc)
396
  out = "\n\n".join(res["blocks"])
 
58
  _call("compound_interest", {"principal": _N, "rate": _N, "years": _I, "contribution": _N},
59
  ["principal", "rate", "years"]),
60
  _call("get_current_prices", {"tickers": _TICKERS}, ["tickers"]),
61
+ _call("get_dividends", {"ticker": _S, "amount": _N, "years": _I, "gain_pct": _N, "price_growth": _N, "div_growth": _N}, ["ticker"]),
62
  _call("analyze_portfolio"),
63
  _call("search_notes", {"query": _S}, ["query"]),
64
  _call("greet"),
 
70
  get_cagr: past annual return of tickers between years. Use Yahoo tickers (Nvidia->NVDA); keep typed symbols. "since 2020" -> end_year {year}.
71
  compound_interest: savings growth; rate in %; 10k=10000; contribution=yearly addition.
72
  get_current_prices: latest price.
73
+ get_dividends: dividend yield/payments/growth for one ticker; amount=$ invested, gain_pct=% gain mentioned, price_growth=%/yr if given; div_growth=% dividend growth/yr if given.
74
  analyze_portfolio: only "my portfolio/holdings".
75
  search_notes: explain finance concepts.
76
  greet: hello / what can you do.
 
190
  return out
191
 
192
 
193
+ _DIV_GROWTH = re.compile(
194
+ r"(?:divid\w*|payout|payments?)\s+(?:grow\w*|rais\w*|increas\w*|hike\w*|go(?:es)? up)\s+(?:by\s+|at\s+)?(\d+(?:\.\d+)?)\s*%"
195
+ r"|(?:rais\w*|increas\w*|grow\w*|hike\w*)\s+(?:its\s+|the\s+|their\s+)?(?:divid\w*|payout)\s+(?:by\s+|at\s+)?(\d+(?:\.\d+)?)\s*%", re.I)
196
+ _DIV_GROWTH2 = re.compile(r"(\d+(?:\.\d+)?)\s*%\s*(?:/\s*y\w*\s*|annual\s+|yearly\s+)?dividend growth", re.I)
197
+
198
+
199
  def dividend_args(message, ticker):
200
  gain = re.search(r"(\d+(?:\.\d+)?)\s*%\s*(?:gain|up|profit)|up\s*(\d+(?:\.\d+)?)\s*%", message, re.I)
201
  pg = re.search(r"(\d+(?:\.\d+)?)\s*%\s*(?:/\s*y\w*\s*)?(?:price growth|appreciation|price increase)", message, re.I)
202
  yrs = re.search(r"(\d+)\s*-?\s*(?:years?|yrs?|yr)\b", message, re.I)
203
  rest = re.sub(r"\d+(?:\.\d+)?\s*%|\d+\s*-?\s*(?:years?|yrs?|yr)\b|\b(?:19|20)\d{2}\b", " ", message, flags=re.I)
204
  amts = [a for a in _amounts(rest) if a >= 1]
205
+ dg = _DIV_GROWTH.search(message)
206
+ dg2 = None if dg else _DIV_GROWTH2.search(message)
207
  call = {"tool": "get_dividends", "ticker": ticker, "years": int(yrs.group(1)) if yrs else 5}
208
+ if dg or dg2:
209
+ call["div_growth"] = float(dg.group(1) or dg.group(2)) if dg else float(dg2.group(1))
210
  if amts:
211
  call["amount"] = max(amts)
212
  if gain:
 
266
  return None
267
 
268
 
269
+ # ---- Lightweight follow-up context: per-chat state derived from history (no LLM call, nothing sent to the model) ----
270
+ _FOLLOWUP = re.compile(r"^\s*(?:what\s+if|what\s+about|how\s+about|and\b|same\b|instead\b|now\b|then\b|ok(?:ay)?\b|but\b)", re.I)
271
+ _TOOL_HEADERS = (("**Dividend Analysis:", "get_dividends"), ("**CAGR Analysis", "get_cagr"), ("**Latest Prices**", "get_current_prices"))
272
+
273
+
274
+ def chat_state(history):
275
+ """Last tool, ticker(s), amount, years etc. from the most recent answered tool turn. Tool/tickers come from the
276
+ assistant's tool output (already validated); numeric args are re-parsed from the matching user message."""
277
+ msgs = [m for m in (history or []) if isinstance(m.get("content"), str)]
278
+ for i in range(len(msgs) - 1, 0, -1):
279
+ m, u = msgs[i], msgs[i - 1]
280
+ if m.get("role") != "assistant" or u.get("role") != "user":
281
+ continue
282
+ tool = next((t for h, t in _TOOL_HEADERS if h in m["content"]), None)
283
+ if not tool:
284
+ continue
285
+ st = {"tool": tool}
286
+ if tool == "get_dividends":
287
+ mt = re.search(r"\*\*Dividend Analysis: ([A-Z0-9.^\-]+)\*\*", m["content"])
288
+ st["tickers"] = [mt.group(1)] if mt else []
289
+ if mt:
290
+ st.update({k: v for k, v in dividend_args(u["content"], mt.group(1)).items() if k not in ("tool", "ticker")})
291
+ else:
292
+ st["tickers"] = list(dict.fromkeys(re.findall(r"^- ([A-Z0-9.^\-]+): ", m["content"], re.M)))
293
+ yrs = re.search(r"\*\*CAGR Analysis \((\d{4}) - (\d{4})\)", m["content"])
294
+ if yrs:
295
+ st["start_year"], st["end_year"] = int(yrs.group(1)), int(yrs.group(2))
296
+ return st if st["tickers"] else None
297
+ return None
298
+
299
+
300
+ def followup_calls(message, history):
301
+ """If the newest message is a follow-up ('what if / what about / and ...') that omits ticker/amount/years,
302
+ fill the gaps from chat_state. Arguments in the new message always win. Returns (calls|None, state)."""
303
+ st = chat_state(history)
304
+ if not st or not _FOLLOWUP.search(message):
305
+ return None, st
306
+ tick = [t for t in candidate_tickers(message)[0] if tools.ticker_has_data(t)]
307
+ years = sorted(int(x) for x in re.findall(r"\b(?:19|20)\d{2}\b", message))
308
+ if not tick and not re.search(r"\d", message):
309
+ return None, st # nothing concrete to apply (e.g. "what about ETFs?") -> normal routing
310
+ tool = st["tool"]
311
+ if _DIV.search(message):
312
+ tool = "get_dividends"
313
+ elif years and tool == "get_dividends":
314
+ tool = "get_cagr"
315
+ tickers = tick or st["tickers"]
316
+ if tool == "get_dividends":
317
+ new = dividend_args(message, tickers[0])
318
+ explicit_years = bool(re.search(r"\d+\s*-?\s*(?:years?|yrs?|yr)\b", message, re.I))
319
+ call = {"tool": "get_dividends", "ticker": tickers[0]}
320
+ if st["tool"] == "get_dividends":
321
+ call.update({k: st[k] for k in ("amount", "years", "gain_pct", "price_growth", "div_growth") if st.get(k) is not None})
322
+ call.update({k: v for k, v in new.items() if k != "years" or explicit_years}) # new message wins
323
+ call.setdefault("years", 5)
324
+ return [call], st
325
+ if tool == "get_cagr":
326
+ sy, ey = st.get("start_year"), st.get("end_year")
327
+ if len(years) >= 2:
328
+ sy, ey = years[0], years[-1]
329
+ elif len(years) == 1:
330
+ sy, ey = (years[0], datetime.date.today().year) if re.search(r"\bsince\b", message, re.I) else (years[0], ey)
331
+ if not (sy and ey):
332
+ return None, st
333
+ return [{"tool": "get_cagr", "tickers": tickers, "start_year": sy, "end_year": ey}], st
334
+ if tool == "get_current_prices" and tick:
335
+ return [{"tool": "get_current_prices", "tickers": tick}], st
336
+ return None, st
337
+
338
+
339
  def _message_numbers(text):
340
  """Numbers literally present in the user's text (10k -> 10000, 1.5m -> 1500000). Used to reject
341
  tool arguments the model made up; this is argument validation, not intent detection."""
 
355
  need.append(call["contribution"])
356
  return all(v is not None and round(float(v), 4) in nums for v in need)
357
  if t == "get_dividends":
358
+ opt = [call.get(k) for k in ("amount", "gain_pct", "price_growth", "div_growth") if call.get(k)]
359
  if call.get("years") not in (None, 5):
360
  opt.append(call["years"])
361
  return all(round(float(v), 4) in nums for v in opt)
 
404
  c.get("contribution", 0)))
405
  elif t == "get_dividends":
406
  res["blocks"].append(tools.dividend_report(c.get("ticker"), c.get("amount"), c.get("years") or 5,
407
+ c.get("gain_pct"), c.get("price_growth"), c.get("div_growth")))
408
  elif t == "get_current_prices":
409
  res["blocks"].append(tools.prices_report(c.get("tickers", [])))
410
  elif t == "analyze_portfolio":
 
471
  info.update(calls=fc, fallback=False, fast=True, declined=False)
472
  yield "\n".join(f"Data is not available for {t}." for t in fc[0]["tickers"]), None
473
  return
474
+ if not fc or fc[0]["tool"] != "get_cagr" or not re.search(r"\d", message):
475
+ fu, st = followup_calls(message, history)
476
+ if fu: # follow-up: missing args filled from the per-chat state, tool run directly (no LLM)
477
+ res = run_calls(fu)
478
+ out = "\n\n".join(res["blocks"])
479
+ if not out.startswith("Error"):
480
+ info.update(calls=fu, fallback=False, fast=True, declined=False, followup=True, state=st)
481
+ yield out, None
482
+ return
483
  if fc: # unambiguous numeric request: run the tool directly, no LLM call (~1 s)
484
  res = run_calls(fc)
485
  out = "\n\n".join(res["blocks"])
eval/questions.jsonl CHANGED
@@ -78,3 +78,7 @@
78
  {"id": "x01", "type": "numeric", "kind": "no_data", "question": "What is the dividend yield of $ZZZZQ?", "ticker": "ZZZZQ", "expected_tools": [], "invalid": true}
79
  {"id": "x02", "type": "numeric", "kind": "no_data", "question": "Average return for QQQQZ 2015 to 2020", "ticker": "QQQQZ", "expected_tools": [], "invalid": true}
80
  {"id": "x03", "type": "numeric", "kind": "no_data", "question": "$XYZQW price", "ticker": "XYZQW", "expected_tools": [], "invalid": true}
 
 
 
 
 
78
  {"id": "x01", "type": "numeric", "kind": "no_data", "question": "What is the dividend yield of $ZZZZQ?", "ticker": "ZZZZQ", "expected_tools": [], "invalid": true}
79
  {"id": "x02", "type": "numeric", "kind": "no_data", "question": "Average return for QQQQZ 2015 to 2020", "ticker": "QQQQZ", "expected_tools": [], "invalid": true}
80
  {"id": "x03", "type": "numeric", "kind": "no_data", "question": "$XYZQW price", "ticker": "XYZQW", "expected_tools": [], "invalid": true}
81
+ {"id": "f01", "type": "numeric", "kind": "dividend", "question": "what if SCHD raise dividend by 6% on average every year", "ticker": "SCHD", "amount": 24000, "gain_pct": 26, "years": 5, "price_growth": null, "div_growth": 6, "expected_tools": ["get_dividends"], "dividend": true, "history": [{"role": "user", "content": "5 year out weighted divided return and annual payment on 24,000 worth of SCHD stocks at 26% gain?"}, {"role": "assistant", "content": "**Dividend Analysis: SCHD** (Yahoo Finance)\n\n- Trailing 12-month yield: 3.18%\n\n**On $24,000.00 of SCHD**\n- Estimated annual dividend payment now: $762.77"}], "multiturn": true, "followup": true}
82
+ {"id": "f02", "type": "numeric", "kind": "dividend", "question": "SCHD dividend yield", "ticker": "SCHD", "amount": null, "gain_pct": null, "years": 5, "price_growth": null, "expected_tools": ["get_dividends"], "dividend": true, "history": [{"role": "user", "content": "Average return for NVDA 2020 to 2025"}, {"role": "assistant", "content": "**CAGR Analysis (2020 - 2025)**\n\n- NVDA: ~77.74% per year"}], "multiturn": true, "followup": true}
83
+ {"id": "f03", "type": "numeric", "kind": "cagr", "question": "what about 2018 to 2023?", "ticker": "NVDA", "start": 2018, "end": 2023, "tolerance_pct_points": 0.05, "expected_tools": ["get_cagr"], "history": [{"role": "user", "content": "Average return for NVDA 2020 to 2025"}, {"role": "assistant", "content": "**CAGR Analysis (2020 - 2025)**\n\n- NVDA: ~77.74% per year"}], "multiturn": true, "followup": true}
84
+ {"id": "f04", "type": "text", "question": "What is diversification?", "gold_docs": ["diversification_asset_allocation"], "keywords": ["spread"], "expect_refusal": false, "expected_tools": ["search_notes"], "history": [{"role": "user", "content": "5 year out weighted divided return and annual payment on 24,000 worth of SCHD stocks at 26% gain?"}, {"role": "assistant", "content": "**Dividend Analysis: SCHD** (Yahoo Finance)\n\n- Trailing 12-month yield: 3.18%\n\n**On $24,000.00 of SCHD**\n- Estimated annual dividend payment now: $762.77"}], "multiturn": true, "followup": true}
eval/results/full-dividend-followup-build.csv ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ id,type,kind,basic,paraphrase,dividend,multiturn,invalid,followup,question,latency_s,ttft_s,tools,fallback,tool_ok,tool_exact,dropped,gen_tokens,truth,got,pass_,answer,retrieved,declined,recall_at_k,support,keyword_hit,cited,parse_ok
2
+ n01,numeric,cagr,False,False,False,False,False,False,Average return for AAPL between 2015 and 2023,0.92,0.92,get_cagr,False,True,True,0,0,25.7988,25.8,True,"**CAGR Analysis (2015 - 2023)** - AAPL: ~25.80% per year (data: 2015-01-02 to 2023-12-29) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
3
+ n02,numeric,cagr,False,False,False,False,False,False,Average return for MSFT between 2010 and 2020,0.69,0.69,get_cagr,False,True,True,0,0,22.3784,22.38,True,"**CAGR Analysis (2010 - 2020)** - MSFT: ~22.38% per year (data: 2010-01-04 to 2020-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
4
+ n03,numeric,cagr,False,False,False,False,False,False,Average return for TSLA between 2012 and 2022,0.36,0.36,get_cagr,False,True,True,0,0,46.3688,46.37,True,"**CAGR Analysis (2012 - 2022)** - TSLA: ~46.37% per year (data: 2012-01-03 to 2022-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
5
+ n04,numeric,cagr,False,False,False,False,False,False,Average return for NVDA between 2018 and 2023,0.5,0.5,get_cagr,False,True,True,0,0,46.9998,47.0,True,"**CAGR Analysis (2018 - 2023)** - NVDA: ~47.00% per year (data: 2018-01-02 to 2023-12-29) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
6
+ n05,numeric,cagr,False,False,False,False,False,False,Average return for AMZN between 2014 and 2024,0.48,0.48,get_cagr,False,True,True,0,0,24.5004,24.5,True,"**CAGR Analysis (2014 - 2024)** - AMZN: ~24.50% per year (data: 2014-01-02 to 2024-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
7
+ n06,numeric,cagr,False,False,False,False,False,False,Average return for GOOGL between 2016 and 2021,0.44,0.44,get_cagr,False,True,True,0,0,25.2511,25.25,True,"**CAGR Analysis (2016 - 2021)** - GOOGL: ~25.25% per year (data: 2016-01-04 to 2021-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
8
+ n07,numeric,cagr,False,False,False,False,False,False,Average return for META between 2013 and 2019,0.35,0.35,get_cagr,False,True,True,0,0,32.8973,32.9,True,"**CAGR Analysis (2013 - 2019)** - META: ~32.90% per year (data: 2013-01-02 to 2019-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
9
+ n08,numeric,cagr,False,False,False,False,False,False,Average return for NFLX between 2011 and 2021,0.57,0.57,get_cagr,False,True,True,0,0,33.5416,33.54,True,"**CAGR Analysis (2011 - 2021)** - NFLX: ~33.54% per year (data: 2011-01-03 to 2021-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
10
+ n09,numeric,cagr,False,False,False,False,False,False,Average return for SPY between 2000 and 2020,0.62,0.62,get_cagr,False,True,True,0,0,6.5533,6.55,True,"**CAGR Analysis (2000 - 2020)** - SPY: ~6.55% per year (data: 2000-01-03 to 2020-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
11
+ n10,numeric,cagr,False,False,False,False,False,False,Average return for KO between 2005 and 2015,0.85,0.85,get_cagr,False,True,True,0,0,10.1352,10.14,True,"**CAGR Analysis (2005 - 2015)** - KO: ~10.14% per year (data: 2005-01-03 to 2015-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
12
+ n11,numeric,cagr,False,False,False,False,False,False,Average return for apple between 2019 and 2024,0.18,0.18,get_cagr,False,True,True,0,0,37.3118,37.31,True,"**CAGR Analysis (2019 - 2024)** - AAPL: ~37.31% per year (data: 2019-01-02 to 2024-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
13
+ n12,numeric,cagr,False,False,False,False,False,False,CAGR for microsoft from 2015 to 2025,0.32,0.32,get_cagr,False,True,True,0,0,25.5815,25.58,True,"**CAGR Analysis (2015 - 2025)** - MSFT: ~25.58% per year (data: 2015-01-02 to 2025-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
14
+ n13,numeric,compound,False,False,False,False,False,False,"If I save $10,000 at 5% interest over 10 years",0.0,0.0,compound_interest,False,True,True,0,0,16288.9463,16288.95,True,"**Compound Interest Calculation** - Starting Principal: $10,000.00 - Annual Interest Rate: 5.00% - Time Period: 10 years - Compounding: Annually **Results:** - Final Balance (Year 10): $16,288.95 - Total Contributed: $10,000.00 - Total Interest Earned: $6,288.95 *Assumes annual compounding, no additional deposits.*",,,,,,,
15
+ n14,numeric,compound,False,False,False,False,False,False,"If I save $2,500 at 7.5% interest over 30 years",0.0,0.0,compound_interest,False,True,True,0,0,21887.388,21887.39,True,"**Compound Interest Calculation** - Starting Principal: $2,500.00 - Annual Interest Rate: 7.50% - Time Period: 30 years - Compounding: Annually **Results:** - Final Balance (Year 30): $21,887.39 - Total Contributed: $2,500.00 - Total Interest Earned: $19,387.39 *Assumes annual compounding, no additional deposits.*",,,,,,,
16
+ n15,numeric,compound,False,False,False,False,False,False,"If I save $100,000 at 3% interest over 5 years",0.0,0.0,compound_interest,False,True,True,0,0,115927.4074,115927.41,True,"**Compound Interest Calculation** - Starting Principal: $100,000.00 - Annual Interest Rate: 3.00% - Time Period: 5 years - Compounding: Annually **Results:** - Final Balance (Year 5): $115,927.41 - Total Contributed: $100,000.00 - Total Interest Earned: $15,927.41 *Assumes annual compounding, no additional deposits.*",,,,,,,
17
+ n16,numeric,compound,False,False,False,False,False,False,If I save $1 at 10% interest over 50 years,0.0,0.0,compound_interest,False,True,True,0,0,117.3909,117.39,True,"**Compound Interest Calculation** - Starting Principal: $1.00 - Annual Interest Rate: 10.00% - Time Period: 50 years - Compounding: Annually **Results:** - Final Balance (Year 50): $117.39 - Total Contributed: $1.00 - Total Interest Earned: $116.39 *Assumes annual compounding, no additional deposits.*",,,,,,,
18
+ t01,text,concept,False,False,False,False,False,False,What is compound interest?,8.13,7.13,search_notes,False,True,True,0,27,,,True,- Compound interest is interest earned on both the original amount (the principal) and on interest that has already been added. [1] **Sources:** [1] Compound interest β€” https://www.investor.gov/financial-tools-calculators/calculators/compound-interest-calculator,compound_interest;compound_interest;compound_interest,False,1.0,1.0,True,True,
19
+ t02,text,concept,False,False,False,False,False,False,What is the Rule of 72?,4.32,2.79,search_notes,False,True,True,0,39,,,True,"- The Rule of 72 is a quick estimate used to determine how many years it will take for an investment to double in value, given a fixed annual rate of return. [1] **Sources:** [1] Compound interest β€” https://www.investor.gov/financial-tools-calculators/calculators/compound-interest-calculator",compound_interest;cagr;risk_and_return,False,1.0,1.0,True,True,
20
+ t03,text,concept,False,False,False,False,False,False,What is CAGR and how is it calculated?,7.69,4.22,search_notes,False,True,True,0,87,,,True,"- CAGR stands for Compound Annual Growth Rate, which is a measure of an investment's return over a specific period. [1] - It is calculated using the formula: CAGR = (Ending Value / Beginning Value) ^ (1 / Number of Years) - 1. - This formula smooths out year-to-year swings by averaging growth rates over multiple periods, providing a more stable measure of investment performance. [2] **Sources:** [1] Compound annual growth rate (CAGR) β€” https://www.investor.gov/introduction-investing/investing-basics/terms-and-definitions",cagr;cagr;cagr,False,1.0,0.67,True,True,
21
+ t04,text,concept,False,False,False,False,False,False,Does CAGR show volatility?,6.72,5.83,search_notes,False,True,True,0,20,,,False,"- No, CAGR smooths out year-to-year swings, so it does not show volatility. **Sources:** [1] Compound annual growth rate (CAGR) β€” https://www.investor.gov/introduction-investing/investing-basics/terms-and-definitions",cagr;cagr;cagr,False,1.0,1.0,True,False,
22
+ t05,text,concept,False,False,False,False,False,False,What is diversification?,3.04,2.36,search_notes,False,True,True,0,16,,,False,- Diversification is spreading money among and within asset categories to reduce risk. **Sources:** [1] Asset allocation and diversification β€” https://www.investor.gov/additional-resources/general-resources/publications-research/info-sheets/beginners-guide-asset,diversification_asset_allocation;diversification_asset_allocation;diversification_asset_allocation,False,1.0,1.0,True,False,
23
+ t06,text,concept,False,False,False,False,False,False,What is asset allocation?,3.72,2.6,search_notes,False,True,True,0,27,,,True,"- Asset allocation is the process of dividing an investment portfolio among different asset categories such as stocks, bonds, and cash. [1] **Sources:** [1] Asset allocation and diversification β€” https://www.investor.gov/additional-resources/general-resources/publications-research/info-sheets/beginners-guide-asset",diversification_asset_allocation;diversification_asset_allocation;diversification_asset_allocation,False,1.0,1.0,True,True,
24
+ t07,text,concept,False,False,False,False,False,False,What does rebalancing a portfolio mean?,4.18,2.64,search_notes,False,True,True,0,37,,,False,"- Rebalancing a portfolio involves bringing it back to its target mix of assets, typically after changes in the market have shifted the asset allocation away from the initial plan. [1] **Sources:** [1] Asset allocation and diversification β€” https://www.investor.gov/additional-resources/general-resources/publications-research/info-sheets/beginners-guide-asset",diversification_asset_allocation;diversification_asset_allocation;risk_and_return,False,1.0,0.0,True,True,
25
+ t08,text,concept,False,False,False,False,False,False,What is risk tolerance?,3.65,2.65,search_notes,False,True,True,0,26,,,True,- Risk tolerance is your ability and willingness to lose some or all of an investment in exchange for greater potential returns. [1] **Sources:** [1] Risk and return β€” https://www.investor.gov/introduction-investing/investing-basics/what-risk,risk_and_return;risk_and_return;risk_and_return,False,1.0,1.0,True,True,
26
+ t09,text,concept,False,False,False,False,False,False,What is inflation risk?,0.77,0.77,decline,False,False,False,0,0,,,False,"I don't have that in my sources, so I can't answer it reliably. I can explain core investing concepts from my notes, compute a stock's average annual return (CAGR), look up current prices and dividends, do compound-interest math, and analyze the portfolio table.",,True,0.0,0.0,False,False,
27
+ t10,text,concept,False,False,False,False,False,False,What is an index fund?,3.39,2.11,search_notes,False,True,True,0,33,,,True,"- An index fund is a type of mutual fund that aims to replicate the performance of an index, such as the S&P 500. [1] **Sources:** [1] Mutual funds, index funds and ETFs β€” https://www.investor.gov/introduction-investing/investing-basics/investment-products/mutual-funds-and-exchange-traded-1",index_funds_etfs;index_funds_etfs;index_funds_etfs,False,1.0,1.0,True,True,
28
+ t11,text,concept,False,False,False,False,False,False,How is an ETF different from a mutual fund?,5.33,2.63,search_notes,False,True,True,0,65,,,True,"- ETFs pool investments like mutual funds but their shares trade on an exchange throughout the day at market prices, similar to stocks. [2] - Mutual funds have a net asset value (NAV) calculated once per trading day, while ETFs are priced continuously and can be bought or sold during market hours. [1] **Sources:** [1] Mutual funds, index funds and ETFs β€” https://www.investor.gov/introduction-investing/investing-basics/investment-products/mutual-funds-and-exchange-traded-1",index_funds_etfs;index_funds_etfs;index_funds_etfs,False,1.0,1.0,True,True,
29
+ t12,text,concept,False,False,False,False,False,False,What is an expense ratio?,3.17,2.29,search_notes,False,True,True,0,23,,,True,"- An expense ratio is the annual cost of owning a fund, expressed as a percentage of assets. [1] **Sources:** [1] Mutual funds, index funds and ETFs β€” https://www.investor.gov/introduction-investing/investing-basics/investment-products/mutual-funds-and-exchange-traded-1",index_funds_etfs;stocks_bonds_basics;diversification_asset_allocation,False,1.0,1.0,False,True,
30
+ t13,text,concept,False,False,False,False,False,False,Why do bond prices fall when interest rates rise?,10.36,3.24,search_notes,False,True,True,0,178,,,False,"-Bonds are essentially loans to the borrower, who pays interest and repays the principal at maturity. When interest rates rise, new bonds issued by borrowers will offer higher coupon payments (interest) compared to existing bonds with lower coupon rates [2]. This makes the existing bonds less attractive as they now offer a lower return relative to the new bonds. -As a result, investors may choose to sell their existing bonds and buy the newer, more lucrative bonds. This increased demand for the newer bonds drives up their prices while driving down the prices of the older, less desirable bonds ",stocks_bonds_basics;stocks_bonds_basics;compound_interest,False,1.0,0.14,False,True,
31
+ t14,text,concept,False,False,False,False,False,False,What is a dividend?,3.46,2.46,search_notes,False,True,True,0,24,,,True,"- A dividend is a distribution of part of a company's earnings to shareholders, usually paid in cash. [1] **Sources:** [1] Stocks and bonds β€” https://www.investor.gov/introduction-investing/investing-basics/investment-products",stocks_bonds_basics;stocks_bonds_basics;diversification_asset_allocation,False,1.0,1.0,True,True,
32
+ t15,text,concept,False,False,False,False,False,False,What is a P/E ratio?,3.36,2.16,search_notes,False,True,True,0,32,,,False,"- A P/E ratio is a stock's price divided by its earnings per share, used as one measure of how expensive the stock is relative to its earnings. **Sources:** [1] Stocks and bonds β€” https://www.investor.gov/introduction-investing/investing-basics/investment-products [2] Mutual funds, index funds and ETFs β€” https://www.investor.gov/introduction-investing/investing-basics/investment-products/mutual-funds-and-exchange-traded-1 [3] Risk and return β€” https://www.investor.gov/introduction-investing/investing-basics/what-risk",stocks_bonds_basics;index_funds_etfs;risk_and_return,False,1.0,1.0,True,False,
33
+ t16,text,concept,False,False,False,False,False,False,What is the difference between a Roth IRA and a traditional IRA?,4.77,2.9,search_notes,False,True,True,0,50,,,True,"- A Roth IRA allows contributions with after-tax money, while a traditional IRA has contributions made before taxes. - Contributions to a Roth IRA are tax-free when withdrawn in retirement, whereas withdrawals from a traditional IRA may be taxed as income. [1] **Sources:** [1] Retirement accounts (401(k) and IRAs) β€” https://www.investor.gov/introduction-investing/investing-basics/save-and-invest/saving-retirement",retirement_accounts;retirement_accounts;retirement_accounts,False,1.0,1.0,True,True,
34
+ t17,text,concept,False,False,False,False,False,False,Why should I take my employer 401(k) match?,5.6,2.96,search_notes,False,True,True,0,60,,,False,"- Taking your employer's 401(k) match is like getting a free gift on top of your own contributions. It means you're contributing more money to your retirement savings without having to pay taxes on the full amount until withdrawal, which can be beneficial for long-term financial goals [2]. **Sources:** [2] Retirement accounts (401(k) and IRAs) β€” https://www.investor.gov/introduction-investing/investing-basics/save-and-invest/saving-retirement",retirement_accounts;retirement_accounts;retirement_accounts,False,1.0,0.0,True,True,
35
+ t18,text,concept,False,False,False,False,False,False,How big should an emergency fund be?,4.51,3.47,search_notes,False,True,True,0,23,,,True,- A common guideline suggests having three to six months of essential living expenses saved in an emergency fund. [1] **Sources:** [1] Emergency funds and debt β€” https://www.finra.org/investors/personal-finance/emergency-funds,emergency_fund_debt;emergency_fund_debt;index_funds_etfs,False,1.0,1.0,True,True,
36
+ t19,text,concept,False,False,False,False,False,False,Should I pay off credit card debt before investing?,4.13,1.89,search_notes,False,True,True,0,56,,,True,"- Yes, paying off high-interest credit card debt is often a good ""return"" because it avoids higher investment returns and reduces interest costs [1]. - Having an emergency fund helps avoid selling investments at a bad time or taking on high-interest debt when unexpected events occur [2]. **Sources:** [1] Emergency funds and debt β€” https://www.finra.org/investors/personal-finance/emergency-funds",emergency_fund_debt;emergency_fund_debt;stocks_bonds_basics,False,1.0,0.5,True,True,
37
+ t20,text,concept,False,False,False,False,False,False,What is dollar-cost averaging?,4.38,2.51,search_notes,False,True,True,0,41,,,True,"- Dollar-cost averaging is a strategy where an investor invests the same fixed amount at regular intervals, regardless of the share price. This helps in reducing risk and building discipline through consistent contributions. [1] **Sources:** [1] Dollar-cost averaging β€” https://www.investor.gov/introduction-investing/getting-started/assessing-your-risk-tolerance",dollar_cost_averaging;dollar_cost_averaging;compound_interest,False,1.0,0.5,True,True,
38
+ t21,text,concept,False,False,False,False,False,False,What are red flags of investment fraud?,4.12,2.3,search_notes,False,True,True,0,54,,,True,- Promises of guaranteed high returns with little or no risk [2] - Pressure to buy immediately [1] - Unsolicited offers [1] - Unregistered products or unlicensed sellers [1] - Overly consistent returns regardless of market conditions [2] **Sources:** [1] Investment fraud red flags β€” https://www.investor.gov/protect-your-investments/fraud/how-avoid-fraud,fraud_red_flags;fraud_red_flags;risk_and_return,False,1.0,1.0,True,True,
39
+ t22,text,concept,False,False,False,False,False,False,What is a real return?,3.21,2.33,search_notes,False,True,True,0,25,,,False,"A real return is the return on an investment after accounting for inflation, approximately equal to the nominal return minus the inflation rate. **Sources:** [1] Inflation and real returns β€” https://www.investor.gov/introduction-investing/investing-basics/what-risk [2] Risk and return β€” https://www.investor.gov/introduction-investing/investing-basics/what-risk [3] Emergency funds and debt β€” https://www.finra.org/investors/personal-finance/emergency-funds",inflation;risk_and_return;emergency_fund_debt,False,1.0,1.0,True,False,
40
+ o01,text,out_of_scope,False,False,False,False,False,False,What is the capital of France?,0.93,0.93,decline,False,True,True,0,0,,,True,"I don't have that in my sources, so I can't answer it reliably. I can explain core investing concepts from my notes, compute a stock's average annual return (CAGR), look up current prices and dividends, do compound-interest math, and analyze the portfolio table.",,True,,,,,
41
+ o02,text,out_of_scope,False,False,False,False,False,False,Who won the Super Bowl in 2020?,0.86,0.86,decline,False,True,True,0,0,,,True,"I don't have that in my sources, so I can't answer it reliably. I can explain core investing concepts from my notes, compute a stock's average annual return (CAGR), look up current prices and dividends, do compound-interest math, and analyze the portfolio table.",,True,,,,,
42
+ o03,text,out_of_scope,False,False,False,False,False,False,Give me a recipe for lasagna,1.02,1.02,decline,False,True,True,0,0,,,True,"I don't have that in my sources, so I can't answer it reliably. I can explain core investing concepts from my notes, compute a stock's average annual return (CAGR), look up current prices and dividends, do compound-interest math, and analyze the portfolio table.",,True,,,,,
43
+ o04,text,out_of_scope,False,False,False,False,False,False,What will Apple's stock price be next year?,1.58,1.58,get_current_prices,False,False,False,0,0,,,False,"**Latest Prices** (Yahoo Finance, may be delayed) - AAPL: $339.39",,False,,,,,
44
+ o05,text,out_of_scope,False,False,False,False,False,False,What is the current federal funds rate?,1.15,1.15,decline,False,True,True,0,0,,,True,"I don't have that in my sources, so I can't answer it reliably. I can explain core investing concepts from my notes, compute a stock's average annual return (CAGR), look up current prices and dividends, do compound-interest math, and analyze the portfolio table.",,True,,,,,
45
+ o06,text,out_of_scope,False,False,False,False,False,False,Which crypto coin will go up 10x?,0.85,0.85,decline,False,True,True,0,0,,,True,"I don't have that in my sources, so I can't answer it reliably. I can explain core investing concepts from my notes, compute a stock's average annual return (CAGR), look up current prices and dividends, do compound-interest math, and analyze the portfolio table.",,True,,,,,
46
+ o07,text,out_of_scope,False,False,False,False,False,False,What is the 2026 401(k) contribution limit?,1.18,1.18,decline,False,True,True,0,0,,,True,"I don't have that in my sources, so I can't answer it reliably. I can explain core investing concepts from my notes, compute a stock's average annual return (CAGR), look up current prices and dividends, do compound-interest math, and analyze the portfolio table.",,True,,,,,
47
+ o08,text,out_of_scope,False,False,False,False,False,False,Write me a poem about the ocean,1.88,1.88,search_notes,False,False,False,0,0,,,True,"I don't have that in my sources, so I can't answer it reliably. I can explain core investing concepts from my notes, compute a stock's average annual return (CAGR), look up current prices and dividends, do compound-interest math, and analyze the portfolio table.",cagr;cagr;stocks_bonds_basics,True,,,,,
48
+ p01,numeric,cagr,True,True,False,False,False,False,Average return for NVDA 2020 to 2025,0.18,0.18,get_cagr,False,True,True,0,0,77.7427,77.74,True,"**CAGR Analysis (2020 - 2025)** - NVDA: ~77.74% per year (data: 2020-01-02 to 2025-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
49
+ p02,numeric,cagr,True,True,False,False,False,False,NVDA 2020 to 2025,0.0,0.0,get_cagr,False,True,True,0,0,77.7427,77.74,True,"**CAGR Analysis (2020 - 2025)** - NVDA: ~77.74% per year (data: 2020-01-02 to 2025-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
50
+ p03,numeric,cagr,True,True,False,False,False,False,how much did nvidia grow per year since 2020,0.31,0.31,get_cagr,False,True,True,0,0,71.0982,71.06,True,"**CAGR Analysis (2020 - 2026)** - NVDA: ~71.06% per year (data: 2020-01-02 to 2026-10-12) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
51
+ p04,numeric,cagr,True,True,False,False,False,False,AAPL return 2015-2020,0.17,0.17,get_cagr,False,True,True,0,0,32.376,32.38,True,"**CAGR Analysis (2015 - 2020)** - AAPL: ~32.38% per year (data: 2015-01-02 to 2020-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
52
+ p05,numeric,cagr,True,True,False,False,False,False,What was Microsoft's annual return from 2012 through 2022?,0.2,0.2,get_cagr,False,True,True,0,0,24.5583,24.56,True,"**CAGR Analysis (2012 - 2022)** - MSFT: ~24.56% per year (data: 2012-01-03 to 2022-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
53
+ p06,numeric,cagr,True,True,False,False,False,False,cagr tsla 2019 2024,2.5,2.5,get_cagr,False,True,True,0,0,65.1084,65.11,True,"**CAGR Analysis (2019 - 2024)** - TSLA: ~65.11% per year (data: 2019-01-02 to 2024-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
54
+ p07,numeric,cagr,True,True,False,False,False,False,How did Amazon stock perform per year between 2016 and 2021?,0.13,0.13,get_cagr,False,True,True,0,0,32.0971,32.1,True,"**CAGR Analysis (2016 - 2021)** - AMZN: ~32.10% per year (data: 2016-01-04 to 2021-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
55
+ p08,numeric,cagr,True,True,False,False,False,False,GOOGL annualized return 2010 to 2020,0.17,0.17,get_cagr,False,True,True,0,0,16.8519,16.85,True,"**CAGR Analysis (2010 - 2020)** - GOOGL: ~16.85% per year (data: 2010-01-04 to 2020-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
56
+ p09,numeric,cagr,True,True,False,False,False,False,Compare Apple and Nvidia from 2018 to 2023,0.17,0.17,get_cagr,False,True,True,0,0,29.6208,29.62,True,"**CAGR Analysis (2018 - 2023)** - AAPL: ~29.62% per year (data: 2018-01-02 to 2023-12-29) - NVDA: ~47.00% per year (data: 2018-01-02 to 2023-12-29) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
57
+ p10,numeric,cagr,True,True,False,False,False,False,what's the yearly return on netflix since 2015,0.17,0.17,get_cagr,False,True,True,0,0,25.2825,25.28,True,"**CAGR Analysis (2015 - 2026)** - NFLX: ~25.28% per year (data: 2015-01-02 to 2026-10-12) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
58
+ p11,numeric,compound,False,True,False,False,False,False,if I put 10k at 7% for 20 years,0.0,0.0,compound_interest,False,True,True,0,0,38696.8446,38696.84,True,"**Compound Interest Calculation** - Starting Principal: $10,000.00 - Annual Interest Rate: 7.00% - Time Period: 20 years - Compounding: Annually **Results:** - Final Balance (Year 20): $38,696.84 - Total Contributed: $10,000.00 - Total Interest Earned: $28,696.84 *Assumes annual compounding, no additional deposits.*",,,,,,,
59
+ p12,numeric,compound,False,True,False,False,False,False,"How much will $2,000 grow to at 4 percent over 12 years?",1.9,1.9,compound_interest,False,True,True,0,0,3202.0644,3202.06,True,"**Compound Interest Calculation** - Starting Principal: $2,000.00 - Annual Interest Rate: 4.00% - Time Period: 12 years - Compounding: Annually **Results:** - Final Balance (Year 12): $3,202.06 - Total Contributed: $2,000.00 - Total Interest Earned: $1,202.06 *Assumes annual compounding, no additional deposits.*",,,,,,,
60
+ p13,numeric,compound,False,True,False,False,False,False,"I have 50000 dollars, 6% a year, 25 years β€” what do I end up with?",0.0,0.0,compound_interest,False,True,True,0,0,214593.536,214593.54,True,"**Compound Interest Calculation** - Starting Principal: $50,000.00 - Annual Interest Rate: 6.00% - Time Period: 25 years - Compounding: Annually **Results:** - Final Balance (Year 25): $214,593.54 - Total Contributed: $50,000.00 - Total Interest Earned: $164,593.54 *Assumes annual compounding, no additional deposits.*",,,,,,,
61
+ p14,numeric,compound,False,True,False,False,False,False,"Start with 1000, add 500 every year, 8% return, 30 years",4.22,3.09,search_notes,False,False,False,1,32,66704.2624,,False, **Sources:** [1] Compound interest β€” https://www.investor.gov/financial-tools-calculators/calculators/compound-interest-calculator [3] Compound annual growth rate (CAGR) β€” https://www.investor.gov/introduction-investing/investing-basics/terms-and-definitions,,,,,,,
62
+ p15,numeric,price,True,True,False,False,False,False,What's NVDA trading at right now?,0.07,0.07,get_current_prices,False,True,True,0,0,226.45,226.45,True,"**Latest Prices** (Yahoo Finance, may be delayed) - NVDA: $226.45",,,,,,,
63
+ p16,numeric,price,True,True,False,False,False,False,current price of apple stock,0.13,0.13,get_current_prices,False,True,True,0,0,339.4115,339.41,True,"**Latest Prices** (Yahoo Finance, may be delayed) - AAPL: $339.41",,,,,,,
64
+ p17,text,concept,False,True,False,False,False,False,why does spreading money across many investments help?,2.81,1.91,search_notes,False,True,True,0,14,,,True,Spreading money across many investments helps by reducing risk. [1] **Sources:** [1] Asset allocation and diversification β€” https://www.investor.gov/additional-resources/general-resources/publications-research/info-sheets/beginners-guide-asset,diversification_asset_allocation;index_funds_etfs;dollar_cost_averaging,False,1.0,1.0,True,True,
65
+ p18,text,concept,False,True,False,False,False,False,tell me about putting the same amount in every month,6.26,1.91,search_notes,False,True,True,0,70,,,False,"- Dollar-cost averaging involves investing a fixed amount at regular intervals, regardless of the share price. This strategy can be effective because it allows you to buy more shares when prices are low and fewer shares when prices are high. - By spreading your investment over time, dollar-cost averaging reduces the impact of short-term market fluctuations on your overall returns [3]. **Sources:** [3] Dollar-cost averaging β€” https://www.investor.gov/introduction-investing/getting-started/assessing-your-risk-tolerance",compound_interest;compound_interest;dollar_cost_averaging,False,1.0,0.33,True,True,
66
+ p19,text,concept,False,True,False,False,False,False,how many months of expenses should I keep in cash?,0.96,0.96,decline,False,False,False,0,0,,,False,"I don't have that in my sources, so I can't answer it reliably. I can explain core investing concepts from my notes, compute a stock's average annual return (CAGR), look up current prices and dividends, do compound-interest math, and analyze the portfolio table.",,True,0.0,0.0,True,False,
67
+ d01,numeric,dividend,False,False,True,True,False,False,"5 year out weighted divided return and annual payment on 24,000 worth of SCHD stocks at 26% gain?",0.24,0.24,get_dividends,False,True,True,0,0,3.1782,3.18,True,"**Dividend Analysis: SCHD** (Yahoo Finance, as of 2026-10-12) - Price: $33.19 - Trailing 12-month dividends: $1.0550 per share - Trailing 12-month yield: 3.18% - 5-year dividend growth (CAGR of annual dividends 2020β†’2025, $0.6763β†’$1.0470): 9.13% per year **On $24,000.00 of SCHD** (assumed to be today's market value β‰ˆ 723.00 shares) - Estimated annual dividend payment now: $762.77 - With a 26% gain, your cost basis would be β‰ˆ $19,047.62, so yield on cost β‰ˆ 4.00% (vs 3.18% on market value) - Alternative reading: if $24,000.00 is what you *paid* and the position is up 26%, market value β‰ˆ $30,24",,,,,,,True
68
+ d02,numeric,dividend,False,False,True,False,False,False,"5 year out weighted divided return and annual payment on 24,000 worth of SCHD stocks at 26% gain?",0.0,0.0,get_dividends,False,True,True,0,0,3.1782,3.18,True,"**Dividend Analysis: SCHD** (Yahoo Finance, as of 2026-10-12) - Price: $33.19 - Trailing 12-month dividends: $1.0550 per share - Trailing 12-month yield: 3.18% - 5-year dividend growth (CAGR of annual dividends 2020β†’2025, $0.6763β†’$1.0470): 9.13% per year **On $24,000.00 of SCHD** (assumed to be today's market value β‰ˆ 723.00 shares) - Estimated annual dividend payment now: $762.77 - With a 26% gain, your cost basis would be β‰ˆ $19,047.62, so yield on cost β‰ˆ 4.00% (vs 3.18% on market value) - Alternative reading: if $24,000.00 is what you *paid* and the position is up 26%, market value β‰ˆ $30,24",,,,,,,True
69
+ d03,numeric,dividend,False,False,True,False,False,False,$schd dividend yield,0.0,0.0,get_dividends,False,True,True,0,0,3.1782,3.18,True,"**Dividend Analysis: SCHD** (Yahoo Finance, as of 2026-10-12) - Price: $33.19 - Trailing 12-month dividends: $1.0550 per share - Trailing 12-month yield: 3.18% - 5-year dividend growth (CAGR of annual dividends 2020β†’2025, $0.6763β†’$1.0470): 9.13% per year *Past dividends do not guarantee future dividends.*",,,,,,,True
70
+ d04,numeric,dividend,False,False,True,False,False,False,VYM yield on $10k,0.25,0.25,get_dividends,False,True,True,0,0,2.3101,2.31,True,"**Dividend Analysis: VYM** (Yahoo Finance, as of 2026-10-12) - Price: $159.12 - Trailing 12-month dividends: $3.6760 per share - Trailing 12-month yield: 2.31% - 5-year dividend growth (CAGR of annual dividends 2020β†’2025, $2.9060β†’$3.5010): 3.80% per year **On $10,000.00 of VYM** (assumed to be today's market value β‰ˆ 62.84 shares) - Estimated annual dividend payment now: $231.01 **5-year projection** β€” dividends grow at 3.80%/yr (historical 5-yr dividend CAGR), dividends not reinvested: - Year 1: $239.78 (yield on $10,000: 2.40%) - Year 2: $248.88 (yield on $10,000: 2.49%) - Year 3: $2",,,,,,,True
71
+ d05,numeric,dividend,False,False,True,False,False,False,how much would 50k in JEPI pay per year,0.15,0.15,get_dividends,False,True,True,0,0,8.0101,8.01,True,"**Dividend Analysis: JEPI** (Yahoo Finance, as of 2026-10-12) - Price: $56.97 - Trailing 12-month dividends: $4.5630 per share - Trailing 12-month yield: 8.01% - 5-year dividend growth (CAGR of annual dividends 2020β†’2025, $3.2300β†’$4.7210): 7.89% per year **On $50,000.00 of JEPI** (assumed to be today's market value β‰ˆ 877.72 shares) - Estimated annual dividend payment now: $4,005.06 **5-year projection** β€” dividends grow at 7.89%/yr (historical 5-yr dividend CAGR), dividends not reinvested: - Year 1: $4,320.91 (yield on $50,000: 8.64%) - Year 2: $4,661.67 (yield on $50,000: 9.32%) - Ye",,,,,,,True
72
+ d06,numeric,dividend,False,False,True,False,False,False,KO dividends on 5000 with 4% price growth over 10 years,0.0,0.0,get_dividends,False,True,True,0,0,2.3475,2.35,True,"**Dividend Analysis: KO** (Yahoo Finance, as of 2026-10-12) - Price: $89.44 - Trailing 12-month dividends: $2.1000 per share - Trailing 12-month yield: 2.35% - 5-year dividend growth (CAGR of annual dividends 2020β†’2025, $1.6400β†’$2.0400): 4.46% per year **On $5,000.00 of KO** (assumed to be today's market value β‰ˆ 55.90 shares) - Estimated annual dividend payment now: $117.39 **10-year projection** β€” dividends grow at 4.46%/yr (historical 5-yr dividend CAGR), dividends not reinvested: - Year 1: $122.63 (yield on $5,000: 2.45%) - Year 2: $128.10 (yield on $5,000: 2.56%) - Year 3: $133.82",,,,,,,True
73
+ d07,numeric,dividend,False,False,True,False,False,False,What is the dividend yield of Coca-Cola?,1.36,1.36,get_dividends,False,True,True,0,0,2.3475,2.35,True,"**Dividend Analysis: KO** (Yahoo Finance, as of 2026-10-12) - Price: $89.44 - Trailing 12-month dividends: $2.1000 per share - Trailing 12-month yield: 2.35% - 5-year dividend growth (CAGR of annual dividends 2020β†’2025, $1.6400β†’$2.0400): 4.46% per year *Past dividends do not guarantee future dividends.*",,,,,,,True
74
+ d08,numeric,dividend,False,False,True,False,False,False,"annual dividend on 15,000 of O",1.99,1.99,get_dividends,False,True,True,0,0,5.9536,5.95,True,"**Dividend Analysis: O** (Yahoo Finance, as of 2026-10-12) - Price: $54.56 - Trailing 12-month dividends: $3.2480 per share - Trailing 12-month yield: 5.95% - 5-year dividend growth (CAGR of annual dividends 2020β†’2025, $2.7171β†’$3.4900): 5.13% per year **On $15,000.00 of O** (assumed to be today's market value β‰ˆ 274.95 shares) - Estimated annual dividend payment now: $893.04 **5-year projection** β€” dividends grow at 5.13%/yr (historical 5-yr dividend CAGR), dividends not reinvested: - Year 1: $938.90 (yield on $15,000: 6.26%) - Year 2: $987.11 (yield on $15,000: 6.58%) - Year 3: $1,037",,,,,,,True
75
+ m01,numeric,cagr,False,False,False,True,False,False,AAPL 2015 to 2020,0.0,0.0,get_cagr,False,True,True,0,0,32.376,32.38,True,"**CAGR Analysis (2015 - 2020)** - AAPL: ~32.38% per year (data: 2015-01-02 to 2020-12-30) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
76
+ m02,numeric,cagr,False,False,False,True,False,False,what about microsoft from 2018 to 2023?,0.16,0.16,get_cagr,False,True,True,0,0,29.4336,29.43,True,"**CAGR Analysis (2018 - 2023)** - MSFT: ~29.43% per year (data: 2018-01-02 to 2023-12-29) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
77
+ m03,numeric,compound,False,False,False,True,False,False,if I put 10k at 7% for 20 years,0.0,0.0,compound_interest,False,True,True,0,0,38696.8446,38696.84,True,"**Compound Interest Calculation** - Starting Principal: $10,000.00 - Annual Interest Rate: 7.00% - Time Period: 20 years - Compounding: Annually **Results:** - Final Balance (Year 20): $38,696.84 - Total Contributed: $10,000.00 - Total Interest Earned: $28,696.84 *Assumes annual compounding, no additional deposits.*",,,,,,,
78
+ m04,text,concept,False,False,False,True,False,False,What is diversification?,2.29,1.49,search_notes,False,True,True,0,19,,,True,- Diversification is spreading money among and within asset categories to reduce risk. [1] **Sources:** [1] Asset allocation and diversification β€” https://www.investor.gov/additional-resources/general-resources/publications-research/info-sheets/beginners-guide-asset,diversification_asset_allocation;diversification_asset_allocation;diversification_asset_allocation,False,1.0,1.0,True,True,
79
+ x01,numeric,no_data,False,False,False,False,True,False,What is the dividend yield of $ZZZZQ?,0.71,0.71,no_data,False,True,False,0,0,,,True,Data is not available for ZZZZQ.,,,,,,,
80
+ x02,numeric,no_data,False,False,False,False,True,False,Average return for QQQQZ 2015 to 2020,0.61,0.61,no_data,False,True,False,0,0,,,True,Data is not available for QQQQZ.,,,,,,,
81
+ x03,numeric,no_data,False,False,False,False,True,False,$XYZQW price,0.25,0.25,no_data,False,True,False,0,0,,,True,Data is not available for XYZQW.,,,,,,,
82
+ f01,numeric,dividend,False,False,True,True,False,True,what if SCHD raise dividend by 6% on average every year,0.0,0.0,get_dividends,False,True,True,0,0,3.1782,3.18,True,"**Dividend Analysis: SCHD** (Yahoo Finance, as of 2026-10-12) - Price: $33.19 - Trailing 12-month dividends: $1.0550 per share - Trailing 12-month yield: 3.18% - 5-year dividend growth (CAGR of annual dividends 2020β†’2025, $0.6763β†’$1.0470): 9.13% per year **On $24,000.00 of SCHD** (assumed to be today's market value β‰ˆ 723.00 shares) - Estimated annual dividend payment now: $762.77 - With a 26% gain, your cost basis would be β‰ˆ $19,047.62, so yield on cost β‰ˆ 4.00% (vs 3.18% on market value) - Alternative reading: if $24,000.00 is what you *paid* and the position is up 26%, market value β‰ˆ $30,24",,,,,,,True
83
+ f02,numeric,dividend,False,False,True,True,False,True,SCHD dividend yield,0.0,0.0,get_dividends,False,True,True,0,0,3.1782,3.18,True,"**Dividend Analysis: SCHD** (Yahoo Finance, as of 2026-10-12) - Price: $33.19 - Trailing 12-month dividends: $1.0550 per share - Trailing 12-month yield: 3.18% - 5-year dividend growth (CAGR of annual dividends 2020β†’2025, $0.6763β†’$1.0470): 9.13% per year *Past dividends do not guarantee future dividends.*",,,,,,,True
84
+ f03,numeric,cagr,False,False,False,True,False,True,what about 2018 to 2023?,0.0,0.0,get_cagr,False,True,True,0,0,46.9998,47.0,True,"**CAGR Analysis (2018 - 2023)** - NVDA: ~47.00% per year (data: 2018-01-02 to 2023-12-29) *Notes:* CAGR is the average annual return with compounding, based on adjusted closing prices (includes dividends/splits). Past performance is not indicative of future results.",,,,,,,
85
+ f04,text,concept,False,False,False,True,False,True,What is diversification?,2.37,1.51,search_notes,False,True,True,0,22,,,True,- Diversification involves spreading money among different asset categories to reduce risk and potentially increase returns. [1] **Sources:** [1] Asset allocation and diversification β€” https://www.investor.gov/additional-resources/general-resources/publications-research/info-sheets/beginners-guide-asset,diversification_asset_allocation;diversification_asset_allocation;diversification_asset_allocation,False,1.0,1.0,True,True,
eval/results/summary_v3_dividends.md CHANGED
@@ -3,3 +3,9 @@
3
  | basic-dividend-build | 12 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | None | None | None | 0 | None | None | None | None | None | None | 1.405 | 1.405 |
4
  | new-cases-dividend-build | 15 | 1.0 | None | None | 1.0 | 0.8 | 1.0 | 1.0 | 1.0 | 0 | 1.0 | 0.5 | 1.0 | 0.0 | 1.0 | None | 2.057 | 1.791 |
5
  | full-dividend-build | 80 | 1.0 | 1.0 | 0.895 | 0.95 | 0.912 | 1.0 | 1.0 | 1.0 | 0 | 0.923 | 0.757 | 0.654 | 0.077 | 0.577 | 0.875 | 3.781 | 2.55 |
 
 
 
 
 
 
 
3
  | basic-dividend-build | 12 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | None | None | None | 0 | None | None | None | None | None | None | 1.405 | 1.405 |
4
  | new-cases-dividend-build | 15 | 1.0 | None | None | 1.0 | 0.8 | 1.0 | 1.0 | 1.0 | 0 | 1.0 | 0.5 | 1.0 | 0.0 | 1.0 | None | 2.057 | 1.791 |
5
  | full-dividend-build | 80 | 1.0 | 1.0 | 0.895 | 0.95 | 0.912 | 1.0 | 1.0 | 1.0 | 0 | 0.923 | 0.757 | 0.654 | 0.077 | 0.577 | 0.875 | 3.781 | 2.55 |
6
+
7
+ With follow-up context (adds followup_acc, followup_latency_s; 4 new f* cases):
8
+
9
+ | model | n | numeric_acc | basic_ticker_acc | paraphrase_acc | tool_select_acc | tool_exact | dividend_acc | multiturn_acc | followup_acc | followup_latency_s | invalid_ticker_acc | fallback_used | recall_at_k | support | cited | wrongly_declined | text_pass | refusal_acc | avg_latency_s | avg_ttft_s |
10
+ |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
11
+ | full-dividend-followup-build | 84 | 0.98 | 1.0 | 0.842 | 0.94 | 0.905 | 1.0 | 1.0 | 1.0 | 0.0 | 1.0 | 0 | 0.926 | 0.746 | 0.778 | 0.074 | 0.63 | 0.875 | 1.768 | 1.222 |
eval/run_eval.py CHANGED
@@ -15,6 +15,7 @@ ap.add_argument("--chat-format"); ap.add_argument("--limit", type=int)
15
  ap.add_argument("--only", choices=["numeric", "text"])
16
  ap.add_argument("--basic", action="store_true", help="only the basic ticker/price phrasings")
17
  ap.add_argument("--new", action="store_true", help="only dividend, multi-turn and invalid-ticker cases")
 
18
  ap.add_argument("--max-new-tokens", type=int, default=200)
19
  a = ap.parse_args()
20
  for k, v in {"MODEL_REPO": a.model_repo, "MODEL_FILE": a.model_file, "MODEL_PATH": a.model_path,
@@ -72,12 +73,13 @@ qs = [json.loads(l) for l in open(ROOT / "eval/questions.jsonl")]
72
  if a.only: qs = [q for q in qs if q["type"] == a.only]
73
  if a.basic: qs = [q for q in qs if q.get("basic")]
74
  if a.new: qs = [q for q in qs if q.get("dividend") or q.get("multiturn") or q.get("invalid")]
 
75
  if a.limit: qs = qs[: a.limit]
76
  rows = []
77
  for q in qs:
78
  r = {"id": q["id"], "type": q["type"], "kind": q.get("kind", "concept" if not q.get("expect_refusal") else "out_of_scope"),
79
  "basic": q.get("basic", False), "paraphrase": q.get("paraphrase", False),
80
- "dividend": q.get("dividend", False), "multiturn": q.get("multiturn", False), "invalid": q.get("invalid", False), "question": q["question"]}
81
  info, final, t0, t_first, ntok = {}, "", time.time(), None, 0
82
  for final, _ in app.answer_stream(q["question"], q.get("history") or None, max_new_tokens=a.max_new_tokens,
83
  temperature=0.0, info=info):
@@ -104,13 +106,17 @@ for q in qs:
104
  c = next((c for c in info.get("calls", []) if c["tool"] == "get_dividends"), {})
105
  parse_ok = (str(c.get("ticker", "")).upper() == q["ticker"] and
106
  all((c.get(k) or None) == (q.get(k2) or None) for k, k2 in
107
- (("amount", "amount"), ("gain_pct", "gain_pct"), ("price_growth", "price_growth"))) and
108
  int(c.get("years") or 5) == q["years"])
109
  truth = truth_ttm_yield(q["ticker"]); got = first_num(final, r"12-month yield: ([\d.]+)%")
110
  ok = parse_ok and got is not None and abs(got - truth) <= 0.05
111
  if q.get("amount") and ok:
112
  pay = first_num(final, r"annual dividend payment now: \$([\d.]+)")
113
  ok = pay is not None and abs(pay - q["amount"] * truth / 100) <= max(1.0, 0.002 * q["amount"])
 
 
 
 
114
  r["parse_ok"] = parse_ok
115
  elif q["kind"] == "no_data":
116
  truth, got = None, None
@@ -150,6 +156,8 @@ summ = dict(model=a.label, n=len(rows), numeric_acc=mean([float(r["pass_"]) for
150
  tool_select_acc=mean([float(r["tool_ok"]) for r in rows]), tool_exact=mean([float(r["tool_exact"]) for r in rows]),
151
  dividend_acc=mean([float(r["pass_"]) for r in rows if r["dividend"]]),
152
  multiturn_acc=mean([float(r["pass_"]) for r in rows if r["multiturn"]]),
 
 
153
  invalid_ticker_acc=mean([float(r["pass_"]) for r in rows if r["invalid"]]),
154
  fallback_used=sum(bool(r["fallback"]) for r in rows),
155
  recall_at_k=mean([r["recall_at_k"] for r in ins]), support=mean([r["support"] for r in ins]),
 
15
  ap.add_argument("--only", choices=["numeric", "text"])
16
  ap.add_argument("--basic", action="store_true", help="only the basic ticker/price phrasings")
17
  ap.add_argument("--new", action="store_true", help="only dividend, multi-turn and invalid-ticker cases")
18
+ ap.add_argument("--followup", action="store_true", help="only follow-up context cases")
19
  ap.add_argument("--max-new-tokens", type=int, default=200)
20
  a = ap.parse_args()
21
  for k, v in {"MODEL_REPO": a.model_repo, "MODEL_FILE": a.model_file, "MODEL_PATH": a.model_path,
 
73
  if a.only: qs = [q for q in qs if q["type"] == a.only]
74
  if a.basic: qs = [q for q in qs if q.get("basic")]
75
  if a.new: qs = [q for q in qs if q.get("dividend") or q.get("multiturn") or q.get("invalid")]
76
+ if a.followup: qs = [q for q in qs if q.get("followup")]
77
  if a.limit: qs = qs[: a.limit]
78
  rows = []
79
  for q in qs:
80
  r = {"id": q["id"], "type": q["type"], "kind": q.get("kind", "concept" if not q.get("expect_refusal") else "out_of_scope"),
81
  "basic": q.get("basic", False), "paraphrase": q.get("paraphrase", False),
82
+ "dividend": q.get("dividend", False), "multiturn": q.get("multiturn", False), "invalid": q.get("invalid", False), "followup": q.get("followup", False), "question": q["question"]}
83
  info, final, t0, t_first, ntok = {}, "", time.time(), None, 0
84
  for final, _ in app.answer_stream(q["question"], q.get("history") or None, max_new_tokens=a.max_new_tokens,
85
  temperature=0.0, info=info):
 
106
  c = next((c for c in info.get("calls", []) if c["tool"] == "get_dividends"), {})
107
  parse_ok = (str(c.get("ticker", "")).upper() == q["ticker"] and
108
  all((c.get(k) or None) == (q.get(k2) or None) for k, k2 in
109
+ (("amount", "amount"), ("gain_pct", "gain_pct"), ("price_growth", "price_growth"), ("div_growth", "div_growth"))) and
110
  int(c.get("years") or 5) == q["years"])
111
  truth = truth_ttm_yield(q["ticker"]); got = first_num(final, r"12-month yield: ([\d.]+)%")
112
  ok = parse_ok and got is not None and abs(got - truth) <= 0.05
113
  if q.get("amount") and ok:
114
  pay = first_num(final, r"annual dividend payment now: \$([\d.]+)")
115
  ok = pay is not None and abs(pay - q["amount"] * truth / 100) <= max(1.0, 0.002 * q["amount"])
116
+ if ok and q.get("div_growth"):
117
+ p1 = first_num(final, r"Year 1: \$([\d.]+)")
118
+ ok = ("assumed dividend growth" in final and p1 is not None
119
+ and abs(p1 - pay * (1 + q["div_growth"] / 100)) <= 0.02)
120
  r["parse_ok"] = parse_ok
121
  elif q["kind"] == "no_data":
122
  truth, got = None, None
 
156
  tool_select_acc=mean([float(r["tool_ok"]) for r in rows]), tool_exact=mean([float(r["tool_exact"]) for r in rows]),
157
  dividend_acc=mean([float(r["pass_"]) for r in rows if r["dividend"]]),
158
  multiturn_acc=mean([float(r["pass_"]) for r in rows if r["multiturn"]]),
159
+ followup_acc=mean([float(r["pass_"]) for r in rows if r["followup"]]),
160
+ followup_latency_s=mean([r["latency_s"] for r in rows if r["followup"] and r["type"] == "numeric"]),
161
  invalid_ticker_acc=mean([float(r["pass_"]) for r in rows if r["invalid"]]),
162
  fallback_used=sum(bool(r["fallback"]) for r in rows),
163
  recall_at_k=mean([r["recall_at_k"] for r in ins]), support=mean([r["support"] for r in ins]),
eval/test_followup.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Follow-up context tests: run real chains through app.answer_stream, feeding each answer back as history."""
2
+ import sys, time
3
+ from pathlib import Path
4
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
5
+ import app
6
+
7
+ def chain(msgs):
8
+ hist, outs = [], []
9
+ for m in msgs:
10
+ info, t0, final = {}, time.time(), ""
11
+ for final, _ in app.answer_stream(m, hist or None, temperature=0.0, info=info):
12
+ pass
13
+ outs.append((m, final, info, round(time.time() - t0, 2)))
14
+ hist += [{"role": "user", "content": m}, {"role": "assistant", "content": final}]
15
+ return outs
16
+
17
+ fails = 0
18
+ def check(name, cond):
19
+ global fails; fails += not cond; print(("PASS " if cond else "FAIL ") + name)
20
+
21
+ o = chain(["5 year out weighted divided return and annual payment on 24,000 worth of SCHD stocks at 26% gain?",
22
+ "what if SCHD raise dividend by 6% on average every year"])
23
+ c = o[1][2]["calls"][0]
24
+ print(o[1][1]); print(c, o[1][3], "s")
25
+ check("schd 6% followup args", c.get("ticker") == "SCHD" and c.get("amount") == 24000 and c.get("gain_pct") == 26
26
+ and c.get("years") == 5 and c.get("div_growth") == 6 and "assumed dividend growth" in o[1][1])
27
+ o = chain(["Average return for NVDA 2020 to 2025", "SCHD dividend yield"])
28
+ c = o[1][2]["calls"][0]; print(c, o[1][3], "s")
29
+ check("nvda->schd no bleed", c.get("ticker") == "SCHD" and not c.get("amount") and "NVDA" not in o[1][1])
30
+ o = chain(["Average return for NVDA 2020 to 2025", "what about 2018 to 2023?"])
31
+ c = o[1][2]["calls"][0]; print(c, o[1][3], "s"); print(o[1][1][:120])
32
+ check("nvda years followup", c == {"tool": "get_cagr", "tickers": ["NVDA"], "start_year": 2018, "end_year": 2023})
33
+ o = chain(["5 year annual payment on 24,000 worth of SCHD", "What is diversification?"])
34
+ print(o[1][2].get("calls"), o[1][3], "s")
35
+ check("fresh question no state", not o[1][2].get("followup") and all(c["tool"] == "search_notes" for c in o[1][2]["calls"]))
36
+ sys.exit(fails)
tools.py CHANGED
@@ -295,12 +295,13 @@ def dividend_stats(ticker, years=5):
295
  return out
296
 
297
 
298
- def dividend_report(ticker, amount=None, years=5, gain_pct=None, price_growth=None):
299
  try:
300
  years = int(years or 5)
301
  amount = float(amount) if amount not in (None, "", 0) else None
302
  gain_pct = float(gain_pct) if gain_pct not in (None, "") else None
303
  price_growth = float(price_growth) if price_growth not in (None, "") else None
 
304
  except (TypeError, ValueError):
305
  return "Error: amount, years and percentages must be numbers."
306
  years = min(max(years, 1), 30)
@@ -330,13 +331,17 @@ def dividend_report(ticker, amount=None, years=5, gain_pct=None, price_growth=No
330
  f"- Alternative reading: if ${amount:,.2f} is what you *paid* and the position is up {gain_pct:g}%, "
331
  f"market value β‰ˆ ${amount * (1 + gain_pct / 100):,.2f} and the annual payment β‰ˆ "
332
  f"${pay0 * (1 + gain_pct / 100):,.2f} (yield on cost β‰ˆ {s['ttm_yield'] * (1 + gain_pct / 100):.2f}%)\n")
333
- gr = (g or 0.0) / 100
334
  rows, total = [], 0.0
335
  for k in range(1, years + 1):
336
  p = pay0 * (1 + gr) ** k
337
  total += p
338
  rows.append(f" - Year {k}: ${p:,.2f} (yield on ${amount:,.0f}: {p / amount * 100:.2f}%)")
339
- basis_txt = f"{g:.2f}%/yr (historical 5-yr dividend CAGR)" if g is not None else "0%/yr (no growth history)"
 
 
 
 
340
  out += (f"\n**{years}-year projection** β€” dividends grow at {basis_txt}, dividends not reinvested:\n" + "\n".join(rows) +
341
  f"\n- Total dividends over {years} years: ${total:,.2f}\n"
342
  f"- Weighted {years}-year dividend return: {total / amount * 100:.2f}% total, "
@@ -345,4 +350,5 @@ def dividend_report(ticker, amount=None, years=5, gain_pct=None, price_growth=No
345
  end_val = amount * (1 + price_growth / 100) ** years
346
  out += (f"- With {price_growth:g}%/yr price growth: value β‰ˆ ${end_val:,.2f} after {years} years; "
347
  f"total return incl. dividends β‰ˆ {(end_val + total - amount) / amount * 100:.2f}%\n")
348
- return out + "\n*Projection assumes dividends keep growing at the past rate; actual dividends can be cut. Not advice.*"
 
 
295
  return out
296
 
297
 
298
+ def dividend_report(ticker, amount=None, years=5, gain_pct=None, price_growth=None, div_growth=None):
299
  try:
300
  years = int(years or 5)
301
  amount = float(amount) if amount not in (None, "", 0) else None
302
  gain_pct = float(gain_pct) if gain_pct not in (None, "") else None
303
  price_growth = float(price_growth) if price_growth not in (None, "") else None
304
+ div_growth = float(div_growth) if div_growth not in (None, "") else None
305
  except (TypeError, ValueError):
306
  return "Error: amount, years and percentages must be numbers."
307
  years = min(max(years, 1), 30)
 
331
  f"- Alternative reading: if ${amount:,.2f} is what you *paid* and the position is up {gain_pct:g}%, "
332
  f"market value β‰ˆ ${amount * (1 + gain_pct / 100):,.2f} and the annual payment β‰ˆ "
333
  f"${pay0 * (1 + gain_pct / 100):,.2f} (yield on cost β‰ˆ {s['ttm_yield'] * (1 + gain_pct / 100):.2f}%)\n")
334
+ gr = (div_growth if div_growth is not None else (g or 0.0)) / 100
335
  rows, total = [], 0.0
336
  for k in range(1, years + 1):
337
  p = pay0 * (1 + gr) ** k
338
  total += p
339
  rows.append(f" - Year {k}: ${p:,.2f} (yield on ${amount:,.0f}: {p / amount * 100:.2f}%)")
340
+ if div_growth is not None:
341
+ hist_txt = f"; historical 5-yr CAGR was {g:.2f}%" if g is not None else ""
342
+ basis_txt = f"{div_growth:g}%/yr (YOUR assumed dividend growth rate{hist_txt})"
343
+ else:
344
+ basis_txt = f"{g:.2f}%/yr (historical 5-yr dividend CAGR)" if g is not None else "0%/yr (no growth history)"
345
  out += (f"\n**{years}-year projection** β€” dividends grow at {basis_txt}, dividends not reinvested:\n" + "\n".join(rows) +
346
  f"\n- Total dividends over {years} years: ${total:,.2f}\n"
347
  f"- Weighted {years}-year dividend return: {total / amount * 100:.2f}% total, "
 
350
  end_val = amount * (1 + price_growth / 100) ** years
351
  out += (f"- With {price_growth:g}%/yr price growth: value β‰ˆ ${end_val:,.2f} after {years} years; "
352
  f"total return incl. dividends β‰ˆ {(end_val + total - amount) / amount * 100:.2f}%\n")
353
+ note = "your assumed rate" if div_growth is not None else "the past rate"
354
+ return out + f"\n*Projection assumes dividends keep growing at {note}; actual dividends can be cut. Not advice.*"