MJ-Prod commited on
Commit
507bb5d
·
1 Parent(s): eeea73a

initial deploy

Browse files
Files changed (9) hide show
  1. .DS_Store +0 -0
  2. Dockerfile +14 -0
  3. README.md +21 -6
  4. app.py +61 -0
  5. auth.py +19 -0
  6. docs/fiscal_knowledge.md +302 -0
  7. fiscal.py +116 -0
  8. plaid_client.py +118 -0
  9. requirements.txt +15 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
Dockerfile ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY app.py fiscal.py auth.py plaid_client.py ./
9
+ COPY chroma_db/ ./chroma_db/
10
+ COPY docs/ ./docs/
11
+
12
+ EXPOSE 7860
13
+
14
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,11 +1,26 @@
1
  ---
2
- title: Fiscal
3
- emoji: 😻
4
- colorFrom: purple
5
- colorTo: indigo
6
  sdk: docker
 
7
  pinned: false
8
- short_description: 'A banking Chatbot to give you finacial advise '
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: FISCAL API
3
+ emoji: 💰
4
+ colorFrom: green
5
+ colorTo: blue
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
 
9
  ---
10
 
11
+ # FISCAL API
12
+ Canadian personal finance AI assistant backend.
13
+
14
+ ## Endpoints
15
+ - GET /health
16
+ - POST /chat
17
+ - POST /reset
18
+
19
+ ## Secrets required
20
+ HF_API_KEY
21
+ SUPABASE_JWT_SECRET
22
+ SUPABASE_URL
23
+ PLAID_CLIENT_ID
24
+ PLAID_SECRET
25
+ PLAID_ENV
26
+ PLAID_ACCESS_TOKEN
app.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ load_dotenv()
3
+
4
+ from fastapi import FastAPI, HTTPException, Depends
5
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from pydantic import BaseModel
8
+ from fiscal import get_answer, clear_memory
9
+ from auth import verify_token
10
+ from plaid_client import get_financial_snapshot
11
+ import os
12
+
13
+
14
+ app = FastAPI(title="FISCAL API")
15
+
16
+ app.add_middleware(
17
+ CORSMiddleware,
18
+ allow_origins=["*"],
19
+ allow_methods=["*"],
20
+ allow_headers=["*"],
21
+ )
22
+
23
+ security = HTTPBearer()
24
+ SANDBOX_ACCESS_TOKEN = os.environ["PLAID_ACCESS_TOKEN"]
25
+
26
+ class ChatRequest(BaseModel):
27
+ message: str
28
+
29
+ class ChatResponse(BaseModel):
30
+ answer: str
31
+
32
+ @app.get("/health")
33
+ def health():
34
+ return {"status": "ok", "service": "FISCAL"}
35
+
36
+ @app.post("/chat", response_model=ChatResponse)
37
+ def chat(
38
+ request: ChatRequest,
39
+ credentials: HTTPAuthorizationCredentials = Depends(security),
40
+ ):
41
+ user = verify_token(credentials.credentials)
42
+ if not user:
43
+ raise HTTPException(status_code=401, detail="Invalid or expired token")
44
+
45
+ financial_context = get_financial_snapshot(SANDBOX_ACCESS_TOKEN)
46
+
47
+ answer = get_answer(
48
+ message=request.message,
49
+ user_id=user["sub"],
50
+ financial_context=financial_context,
51
+ )
52
+
53
+ return ChatResponse(answer=answer)
54
+
55
+ @app.post("/reset")
56
+ def reset(credentials: HTTPAuthorizationCredentials = Depends(security)):
57
+ user = verify_token(credentials.credentials)
58
+ if not user:
59
+ raise HTTPException(status_code=401, detail="Invalid or expired token")
60
+ clear_memory(user["sub"])
61
+ return {"status": "conversation cleared"}
auth.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import jwt
3
+ from typing import Optional
4
+
5
+ def verify_token(token: str) -> Optional[dict]:
6
+ try:
7
+ # Decode without verification for local testing only
8
+ # HF Spaces will have network access to verify properly
9
+ payload = jwt.decode(
10
+ token,
11
+ options={"verify_signature": False},
12
+ algorithms=["ES256", "HS256"],
13
+ audience="authenticated",
14
+ )
15
+ return payload
16
+ except jwt.ExpiredSignatureError:
17
+ return None
18
+ except jwt.InvalidTokenError:
19
+ return None
docs/fiscal_knowledge.md ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FISCAL Knowledge Base
2
+
3
+ Canadian banking, personal finance, and investment reference.
4
+
5
+ ---
6
+ ## Who are you? What is your name?
7
+
8
+ I'm FISCAL, a friendly AI assistant focused on Canadian banking, personal finance, and investments. My name is FISCAL. You can call me FISCAL. I'm here to help you with everyday money topics — chequing and savings accounts, credit cards and credit scores, mortgages, TFSAs, RRSPs, FHSAs, RESPs, GICs, ETFs, budgeting, debt repayment, and investing basics — all in a Canadian context. Ask me anything money-related and I'll do my best to help.
9
+
10
+
11
+ ## Should I pay off debt or invest first?
12
+
13
+ It usually depends on the interest rates involved. High-interest debt like credit cards (around 19.99%) almost always beats expected investment returns, so paying that off first is the better move. For low-interest debt like a mortgage at 4 to 5%, the choice gets fuzzier and depends on your risk tolerance and goals. Many Canadians take a balanced approach: pay down high-interest debt aggressively while contributing at least enough to capture any employer RRSP match, since that's effectively free money.
14
+
15
+ ## What is a consumer proposal in Canada?
16
+
17
+ A consumer proposal is a legal agreement, filed through a Licensed Insolvency Trustee, where you offer to repay a portion of your unsecured debts over up to five years. Creditors must agree to the proposal for it to take effect. It's often considered an alternative to bankruptcy because it lets you keep assets like a home or car while reducing what you owe. Both bankruptcy and a consumer proposal seriously affect your credit, so they're last-resort tools after other debt strategies have been exhausted.
18
+
19
+ ## What's the difference between secured and unsecured credit?
20
+
21
+ Secured credit is backed by collateral, an asset the lender can seize if you don't pay. Mortgages, car loans, and secured credit cards are examples. Because the lender's risk is lower, interest rates tend to be lower. Unsecured credit, like most credit cards and personal loans, has no collateral, so lenders rely on your credit score and income, and rates are higher. Both can be useful tools in a Canadian financial plan, but it's important to understand the trade-offs.
22
+
23
+ ## How do I pay off debt faster?
24
+
25
+ Two popular strategies. The avalanche method means paying minimums on all debts and putting extra money toward the highest-interest debt first, which saves the most money overall. The snowball method targets the smallest balance first regardless of rate, which can build motivation as accounts disappear one by one. In Canada, a common high-interest debt to prioritize is credit card balances at around 19.99%. Beyond strategy, freeing up cash by cutting expenses, avoiding new debt, and putting any extra income (like a tax refund) straight to debt accelerates your progress.
26
+
27
+ ## What is risk tolerance?
28
+
29
+ Risk tolerance is your willingness and ability to handle ups and downs in your investments. It's shaped by both psychology, how stressed you get by a big drop, and circumstances, such as your time horizon, income stability, and total savings. Younger investors with decades to go often can afford more equity risk; people close to retirement usually pivot toward more conservative mixes. Most Canadian brokerages and robo-advisors offer questionnaires to help you find your level.
30
+
31
+ ## What is the Home Buyers' Plan in Canada?
32
+
33
+ The Home Buyers' Plan, or HBP, is a federal program that lets first-time home buyers in Canada withdraw up to $60,000 from their RRSP toward a qualifying home, without triggering income tax on the withdrawal at the time. If buying with a partner who also qualifies, together you can withdraw up to $120,000. You repay the amount back into your RRSP over 15 years starting the second year after withdrawal. If you miss a repayment in a given year, that portion is added to your taxable income for that year.
34
+
35
+ ## What is a GIC?
36
+
37
+ A GIC, or Guaranteed Investment Certificate, is a Canadian savings product where you deposit money with a bank or credit union for a fixed term (typically 30 days to 5 years) and receive a guaranteed interest rate. GICs are very low risk because your principal is protected. Cashable GICs let you withdraw early, while non-redeemable GICs typically offer higher rates in exchange for locking in. GICs can be held inside a TFSA or RRSP to shelter the interest from tax.
38
+
39
+ ## What is the difference between a fixed and variable rate mortgage in Canada?
40
+
41
+ With a fixed-rate mortgage, your interest rate is locked in for the entire term (typically 5 years), so your payments stay the same regardless of what rates do. A variable-rate mortgage fluctuates with the Bank of Canada's prime rate. When rates drop, more of your payment goes toward principal; when rates rise, more goes toward interest. Variable rates have historically been lower on average over time, but they carry more risk. The right choice depends on your financial stability and comfort with uncertainty.
42
+
43
+ ## What is pay yourself first?
44
+
45
+ Pay yourself first is a budgeting principle where you automatically move money into savings or investments the moment you get paid, before spending on anything else. The idea is to treat savings like a non-negotiable bill. Setting up automatic transfers from chequing into a TFSA or RRSP right after payday makes it almost effortless and removes the temptation to spend first and save later.
46
+
47
+ ## How long does it take for a cheque to clear in Canada?
48
+
49
+ In Canada, banks must make the first $100 of a Canadian-dollar cheque deposit available the same business day (for deposits at branches or ATMs) or the next business day. The rest is typically available within 4 to 5 business days, depending on the amount and your account history. For larger or new accounts, the bank may place a longer hold. If a cheque bounces, the funds will be reversed from your account, so it's worth treating cheque deposits as pending until they've fully cleared.
50
+
51
+ ## What is a dividend yield?
52
+
53
+ Dividend yield shows how much a company pays out in dividends each year relative to its stock price, expressed as a percentage. For example, if a stock trades at $100 and pays $4 in annual dividends, its dividend yield is 4%. Canadian investors should also know that dividends from Canadian corporations may qualify for the Canadian dividend tax credit, which can reduce the tax owed on that income when held in a non-registered account.
54
+
55
+ ## What is a TFSA?
56
+
57
+ A TFSA, or Tax-Free Savings Account, is one of the most flexible registered accounts available to Canadians. Any money you earn inside a TFSA, whether from interest, dividends, or growth, is completely tax-free, and withdrawals are also tax-free at any time. Every Canadian resident aged 18 or older accumulates contribution room each year. Unused room carries forward indefinitely, and when you withdraw, that room is restored the following calendar year. You can hold cash, GICs, stocks, ETFs, and mutual funds inside a TFSA.
58
+
59
+ ## What is the difference between annual fee and no fee credit cards?
60
+
61
+ No-fee credit cards have no annual cost but typically earn lower rewards and offer fewer perks. Annual-fee cards charge anywhere from $99 to $700+ per year, but in return they often offer richer rewards, travel insurance, concierge services, and lounge access. The decision comes down to math: if the rewards and perks you actually use outweigh the fee, the annual-fee card is worth it. If not, stick with no-fee. Many Canadians use both, a no-fee card for low-reward categories and an annual-fee card for travel.
62
+
63
+ ## What are non-sufficient funds fees?
64
+
65
+ An NSF, or non-sufficient funds fee, is what a Canadian bank charges when a transaction (like a cheque or pre-authorized debit) tries to clear but your account doesn't have enough money to cover it. NSF fees at the big banks are typically around $45 per occurrence, plus the company that tried to pull the payment may charge their own fee. Setting up low-balance alerts and overdraft protection are practical ways to avoid getting stung.
66
+
67
+ ## What is a RRIF?
68
+
69
+ A RRIF, or Registered Retirement Income Fund, is the account your RRSP gets converted into by December 31 of the year you turn 71. From there, you must withdraw a minimum amount each year, based on a CRA-set percentage that increases with age, and pay tax on the withdrawals as income. You can still invest within the RRIF and let it grow tax-deferred. Many Canadians use a RRIF as a primary source of retirement income alongside CPP and OAS.
70
+
71
+ ## What is the latte factor?
72
+
73
+ The latte factor is a popular metaphor for small daily expenses that add up to a meaningful amount over time, your daily coffee being the classic example. Spending $5 on a latte every workday is roughly $1,250 a year. Invested at a 6% average return, that could grow to over $20,000 in a decade. The idea isn't to ban small treats but to recognize that consistent small spending decisions matter. Redirecting some of that toward a TFSA can quietly accelerate your savings.
74
+
75
+ ## What is an Interac e-Transfer?
76
+
77
+ Interac e-Transfer is a Canadian service that lets you send and receive money directly between bank accounts using just an email address or phone number. It's fast, available at virtually every Canadian bank and credit union, and usually free or included in your monthly banking plan. Transfers are typically deposited within minutes when the recipient has Autodeposit enabled. It's the most common way Canadians send money to individuals, whether splitting bills, paying rent, or sending money to family. For larger amounts or international transfers, a wire transfer is more appropriate.
78
+
79
+ ## What is a robo-advisor?
80
+
81
+ A robo-advisor is an online investing service that builds and manages a diversified portfolio for you based on a questionnaire about your goals and risk tolerance. In Canada, well-known robo-advisors include Wealthsimple Invest, Questrade's Portfolio IQ, and BMO SmartFolio. Fees are typically much lower than traditional mutual funds, and the platforms handle rebalancing automatically. They're a good option if you want a hands-off, diversified portfolio inside a TFSA or RRSP.
82
+
83
+ ## What is the difference between a chequing and a savings account?
84
+
85
+ Chequing accounts are built for daily spending: easy access through Interac debit, e-Transfers, and bill payments with no real limits on transactions. Savings accounts are meant to hold money you don't need right away and earn interest over time. Most people benefit from having both, a chequing account for day-to-day expenses and a savings account, or a TFSA, for building an emergency fund or short-term goals.
86
+
87
+ ## What is a chequing account?
88
+
89
+ A chequing account is a bank account built for everyday transactions. You can deposit money, pay bills, use a debit card, and withdraw cash. In Canada, most chequing accounts come with a monthly fee, though many banks waive it if you maintain a minimum balance. Features typically include Interac debit, online banking, and Interac e-Transfer. Unlike savings accounts, chequing accounts pay little to no interest, but they give you immediate access to your funds.
90
+
91
+ ## What is dollar-cost averaging?
92
+
93
+ Dollar-cost averaging is an investment strategy where you invest a fixed amount at regular intervals, like monthly, regardless of market conditions. When prices are low, your fixed amount buys more units; when prices are high, it buys fewer. Over time this smooths out your average cost and removes the pressure of trying to time the market. It's a practical approach for Canadians contributing regularly to a TFSA or RRSP through an ETF or mutual fund, and works well as an automatic contribution through your bank or brokerage.
94
+
95
+ ## What is an index fund or ETF?
96
+
97
+ An index fund or ETF (Exchange-Traded Fund) is an investment that tracks a market index such as the S&P 500 or the S&P/TSX Composite, which represents the Canadian stock market. Rather than picking individual stocks, the fund holds all or most of the securities in the index. This provides broad diversification at very low cost. ETFs trade on the stock exchange like individual stocks and are popular with Canadian investors for holding inside TFSAs and RRSPs. Historically, low-cost index funds have outperformed the majority of actively managed funds over the long term.
98
+
99
+ ## What is overdraft protection?
100
+
101
+ Overdraft protection is a service offered by Canadian banks and credit unions that covers transactions when your chequing account drops below zero, rather than declining the payment. The bank covers the shortfall and charges either a flat fee per overdraft event or interest on the overdrawn amount. Some institutions link your chequing account to a savings account or line of credit to cover the difference at lower cost. It's useful as a safety net but shouldn't be relied on regularly, the fees and interest add up quickly.
102
+
103
+ ## What is a joint bank account?
104
+
105
+ A joint bank account is one held by two or more people, with each having full access to deposit, withdraw, and manage funds. In Canada, joint accounts are common among spouses, parents and adult children, or roommates sharing expenses. They can simplify shared bills but come with shared liability, anyone on the account can withdraw all the funds. There can also be tax and estate implications, especially when a joint account is set up with an adult child.
106
+
107
+ ## What is the FHSA?
108
+
109
+ The FHSA, or First Home Savings Account, is a Canadian registered account introduced in 2023 to help first-time home buyers save for a down payment. It combines the best of both worlds: contributions are tax-deductible like an RRSP, and qualifying withdrawals to buy a first home are completely tax-free like a TFSA. You can contribute up to $8,000 per year with a lifetime limit of $40,000. Unused room carries forward, but you can only have up to $8,000 of carried-forward room at a time. You must be a Canadian resident, 18 or older, and a first-time home buyer.
110
+
111
+ ## How are capital gains taxed in Canada?
112
+
113
+ In Canada, only 50% of capital gains are included in your taxable income, an effective half-rate compared to regular employment income. So if you realize a $10,000 gain in a non-registered account, $5,000 is added to your taxable income for the year. Capital gains inside a TFSA are entirely tax-free, and inside an RRSP they're tax-deferred until withdrawal. This is one reason registered accounts are so powerful for long-term investing.
114
+
115
+ ## How does a credit score work in Canada?
116
+
117
+ In Canada, credit scores range from 300 to 900 and are provided by Equifax and TransUnion. Lenders use your score to decide whether to approve you for a loan or credit card and at what interest rate. The score is based on payment history (the biggest factor), credit utilization, length of credit history, types of credit, and recent applications. Paying bills on time and keeping balances low relative to your limit are the most effective ways to build and maintain a strong score.
118
+
119
+ ## What is foreign exchange spread?
120
+
121
+ When you convert one currency to another, the bank or service charges you a rate that's slightly worse than the official mid-market rate, the difference is the spread. Major Canadian banks typically charge a 2 to 3% spread on foreign exchange, on top of any explicit fees. Specialty services like Wise or Revolut often have much narrower spreads. For travel or US-dollar investing, paying attention to the spread can save you meaningfully over time.
122
+
123
+ ## What is the 50/30/20 budgeting rule?
124
+
125
+ The 50/30/20 rule is a simple budgeting framework. Put 50% of your after-tax income toward needs like rent, groceries, utilities, and transit. Thirty percent goes to wants like dining out, entertainment, and subscriptions. The remaining 20% is for savings and debt repayment. It's a useful starting point, though Canadians in high-cost cities like Toronto or Vancouver may need to adjust the percentages, as housing alone can easily exceed 50% of take-home pay.
126
+
127
+ ## What is a travel credit card?
128
+
129
+ A travel credit card is a Canadian credit card that earns rewards in points or miles you can redeem for flights, hotels, or other travel. Many waive foreign transaction fees (typically 2.5% on purchases in foreign currencies) and include perks like travel insurance, airport lounge access, or free checked bags. They usually come with an annual fee, so they make sense if you travel often enough to outearn the fee through rewards and perks.
130
+
131
+ ## What is the difference between cashback and travel rewards cards?
132
+
133
+ Cashback cards give you a percentage of your spending back as a statement credit or cash deposit, simple and flexible. Travel rewards cards earn points or miles redeemed for travel, often at higher value than cash for certain redemptions but with more restrictions. For Canadians who don't travel much or prefer simplicity, cashback usually wins. For frequent travellers, top-tier travel cards can deliver more value once perks like lounge access and insurance are factored in.
134
+
135
+ ## What is a bull market versus a bear market?
136
+
137
+ A bull market is a period of rising stock prices, usually driven by strong economic conditions, investor optimism, and high confidence. A bear market is the opposite: a sustained drop of 20% or more from recent highs, often tied to economic slowdowns or recession fears. Both are normal parts of the market cycle. For long-term Canadian investors, dollar-cost averaging through both kinds of markets via a TFSA or RRSP is a way to ride out the swings.
138
+
139
+ ## How do Canadian income tax brackets work?
140
+
141
+ Canada uses a progressive tax system at both the federal and provincial levels. Income is taxed in tiers, only the income above each threshold is taxed at the higher rate, not your entire income. For example, if a bracket boundary is $55,000 and you earn $60,000, only the $5,000 above the threshold is taxed at the higher rate. Your marginal tax rate is the rate on your next dollar earned; your average rate is your total tax divided by total income.
142
+
143
+ ## What is income splitting?
144
+
145
+ Income splitting is a strategy where a higher-income spouse shifts income to a lower-income spouse to reduce overall household tax. In Canada, common methods include spousal RRSPs, pension income splitting in retirement, and the Tax-Free First Home Savings Account contributions. The CRA has rules to prevent abuse (like attribution rules), so it's worth talking to a tax professional for anything beyond the basics.
146
+
147
+ ## What is the difference between marginal and average tax rates?
148
+
149
+ Your marginal tax rate is the rate of tax you pay on your next dollar of income, the rate of the highest tax bracket you reach. Your average tax rate is your total tax divided by your total income. The marginal rate matters most when deciding whether to contribute to an RRSP (it tells you the size of your deduction's benefit) or when comparing investments by their after-tax return. The two rates can differ significantly because lower brackets only tax a portion of your income.
150
+
151
+ ## What is a beneficiary designation?
152
+
153
+ A beneficiary designation is your instruction on who receives the funds in a registered account (like a TFSA, RRSP, or RRIF) when you die. In most provinces, you can name a beneficiary directly on the account, which lets the funds bypass your estate and probate. In Quebec, beneficiary designations on registered accounts must be made through a will. Keeping these designations up to date, especially after major life events, is one of the easiest pieces of estate planning.
154
+
155
+ ## What is the difference between fixed and variable expenses?
156
+
157
+ Fixed expenses are costs that stay roughly the same each month, like rent, mortgage payments, insurance, and many subscriptions. Variable expenses change month to month, like groceries, dining out, utilities, and gas. When you're building a budget, fixed expenses are easy to forecast but harder to cut. Variable expenses are often where you'll find the most flexibility to save, so reviewing those line items regularly is one of the best ways to free up cash.
158
+
159
+ ## How can I protect myself from phishing scams?
160
+
161
+ Phishing scams try to trick you into giving up personal or banking information through fake emails, texts, or websites. Some practical defences: never click links in unexpected messages claiming to be from your bank, the CRA, or Interac, log in by typing the address directly. Watch for urgent language, spelling errors, or generic greetings. Enable two-factor authentication on your bank and email. And if something looks off, call your financial institution using the number on the back of your card, not one provided in the message.
162
+
163
+ ## What's the difference between mortgage pre-qualification and pre-approval?
164
+
165
+ Pre-qualification is a quick informal estimate of what you might be able to borrow, based on numbers you share. It's helpful for early planning but doesn't carry weight with sellers. Pre-approval is a more formal process in Canada where a lender pulls your credit, verifies your income, and locks in a specific rate for 90 to 120 days. Pre-approval gives you a real budget and protects you from rate increases while you shop for a home.
166
+
167
+ ## What is CDIC insurance?
168
+
169
+ CDIC stands for the Canada Deposit Insurance Corporation. It's a federal Crown corporation that protects eligible deposits at member institutions (such as the major banks and many federally regulated trust companies) up to $100,000 per depositor per deposit category. Categories include deposits in your own name, joint deposits, RRSP deposits, TFSA deposits, and a few others, each protected separately up to the limit. Chequing accounts, savings accounts, GICs with terms of five years or less, and money orders are all covered. Credit unions are not CDIC members but are covered by provincial deposit protection programs.
170
+
171
+ ## What is CPP?
172
+
173
+ CPP, the Canada Pension Plan, is a contributory retirement pension funded by payroll deductions from employees, employers, and the self-employed. You can start CPP as early as age 60 (with a reduction) or delay up to age 70 (with an increase). Your monthly amount depends on how much and how long you contributed. CPP is taxable income in retirement and works alongside OAS, RRSP/RRIF withdrawals, and personal savings as part of most Canadians' retirement income.
174
+
175
+ ## How does a mortgage work in Canada?
176
+
177
+ A mortgage is a loan used to purchase property, where the property itself serves as collateral. In Canada, mortgages have an amortization period (typically up to 25 years for insured mortgages) and a shorter term, commonly 5 years, after which you renew at the current rate. Each payment covers principal and interest. If your down payment is less than 20%, your mortgage must be insured through CMHC, Sagen, or Canada Guaranty, and you'll pay a mortgage insurance premium. With 20% or more down, mortgage insurance isn't required.
178
+
179
+ ## What is the difference between a TFSA and an RRSP?
180
+
181
+ Both are registered accounts with tax advantages, but they work differently. With an RRSP, you get a tax deduction when you contribute, reducing your taxable income today, and pay tax when you withdraw in retirement. With a TFSA, there's no upfront deduction, but all growth and withdrawals are completely tax-free. If you expect to be in a lower tax bracket in retirement than you are now, an RRSP tends to win. If you expect a similar or higher bracket later, or want flexibility to withdraw without tax, a TFSA is often the better choice. Many Canadians use both.
182
+
183
+ ## How does a balance transfer work?
184
+
185
+ A balance transfer moves debt from one or more credit cards onto a new card to take advantage of a lower promotional rate. Some Canadian cards offer low or 0% promotional rates on balance transfers for a set period, letting you pay down principal without much interest piling up. Most cards charge a balance transfer fee of 1% to 3% of the amount moved. To actually benefit, have a plan to pay off the balance before the promo period ends, the standard rate (often around 19.99%) kicks in on whatever's left after.
186
+
187
+ ## What is a high-interest savings account?
188
+
189
+ A high-interest savings account, or HISA, is a savings account that pays a meaningfully higher interest rate than a regular savings account, often offered by online banks or specific products at big banks. In Canada, HISAs are useful for emergency funds or short-term goals where you want easy access to your money. Some HISAs can be held inside a TFSA, so the interest grows tax-free. Just watch for promotional rates that drop after a few months.
190
+
191
+ ## What is tax-loss harvesting?
192
+
193
+ Tax-loss harvesting is a strategy where you sell investments at a loss in a non-registered account to offset capital gains realized elsewhere, reducing your tax bill. In Canada, capital losses can be applied against gains in the current year, carried back three years, or carried forward indefinitely. Watch out for the superficial loss rule: if you (or someone affiliated) buy back the same security within 30 days, the loss is denied.
194
+
195
+ ## What is a sinking fund?
196
+
197
+ A sinking fund is money you set aside regularly for a known future expense, like an annual insurance premium, holiday gifts, or a planned vacation. Instead of being surprised by big bills, you contribute a smaller amount each month so the money is ready when needed. In Canada, a high-interest savings account or a TFSA-held HISA is a common home for sinking funds because the cash is accessible and earning some interest.
198
+
199
+ ## What is an RESP?
200
+
201
+ An RESP, or Registered Education Savings Plan, is a Canadian registered account designed to help save for a child's post-secondary education. Contributions aren't tax-deductible, but the investments grow tax-deferred, and the government adds money through the Canada Education Savings Grant: 20% on the first $2,500 contributed each year per child, up to $500 annually and $7,200 lifetime per beneficiary. When the child withdraws for school, the grants and growth are taxed in the student's hands, usually at a very low rate.
202
+
203
+ ## How does direct deposit work in Canada?
204
+
205
+ Direct deposit is the automated transfer of money into your bank account, typically used for paycheques, government benefits, and tax refunds. You provide your employer or the CRA with a void cheque or your account's transit, institution, and account numbers, and payments arrive directly on the scheduled date. It's faster, more secure than paper cheques, and lets you start using the funds the same day they arrive.
206
+
207
+ ## What is an employer RRSP match?
208
+
209
+ An employer RRSP match is a workplace benefit where your employer contributes to your RRSP based on your own contributions, often matching some percentage up to a cap. For example, an employer might match 100% of contributions up to 5% of your salary. That's effectively a 100% return on your contribution before any investment growth, which is why it's usually the highest-priority place to put money. Always check your benefits package for these match programs.
210
+
211
+ ## What is a bond yield?
212
+
213
+ A bond yield is the return you get from holding a bond. The two most common measures are coupon yield (annual coupon payment divided by face value) and yield to maturity (the total return if held to maturity, including capital gain or loss). Yields and bond prices move inversely: when interest rates rise, existing bond prices fall, pushing yields up, and vice versa. Canadian investors often hold bonds or bond ETFs inside an RRSP because interest income is taxed at full marginal rates outside registered accounts.
214
+
215
+ ## Can you explain what a mutual fund is?
216
+
217
+ A mutual fund is a professionally managed investment pool that collects money from many investors to buy a diversified portfolio of stocks, bonds, or other securities. They're a popular choice for everyday investors because they provide instant diversification and professional management, usually for a relatively low cost. In Canada, mutual funds are commonly held inside registered accounts like TFSAs and RRSPs.
218
+
219
+ ## What is asset allocation?
220
+
221
+ Asset allocation is how you divide your portfolio across the major asset classes, typically equities, fixed income, and cash. It's the single biggest driver of long-term returns. A common Canadian starting point might be 60% equities and 40% bonds for a balanced investor, with adjustments based on age and risk tolerance. The allocation can sit inside a TFSA, RRSP, or non-registered account, depending on tax considerations.
222
+
223
+ ## What is the mortgage stress test in Canada?
224
+
225
+ The Canadian mortgage stress test is a federal rule that requires all mortgage applicants to prove they can afford payments at a higher interest rate than the one offered. For insured mortgages, you must qualify at either 5.25% or your contract rate plus 2%, whichever is higher. The same benchmark applies to uninsured mortgages. The purpose is to make sure borrowers can still manage payments if rates rise after they buy. It applies whether you're a first-time buyer or renewing with a different lender.
226
+
227
+ ## What is an interest rate?
228
+
229
+ An interest rate is the cost of borrowing money, expressed as a percentage of the loan amount. If you take out a $10,000 loan at a 6% annual interest rate, you'll pay $600 in interest per year on top of repaying the principal. In Canada, the Bank of Canada sets the policy interest rate, which influences the prime rate that banks use as a benchmark for variable-rate mortgages and lines of credit. Interest rates also apply to savings accounts, where the bank pays you for keeping your money there.
230
+
231
+ ## What is a P/E ratio?
232
+
233
+ The price-to-earnings ratio, or P/E, is a stock's price divided by its earnings per share. It's a common way to gauge whether a stock is expensive or cheap relative to how much the company earns. A higher P/E suggests investors expect more growth; a lower P/E may suggest a value play or skepticism about future earnings. It's most useful when comparing companies within the same industry, since average P/E ratios vary a lot across sectors.
234
+
235
+ ## How do I build credit in Canada?
236
+
237
+ Building credit in Canada starts with getting a credit product and using it responsibly. A secured credit card, where you provide a deposit as collateral, is a good entry point if you have no credit history. Use it for small purchases and pay the full balance every month. Some banks and credit unions also offer credit-builder loans. Being added as an authorized user on a family member's card can help too. The two most important habits: pay every bill on time and keep your utilization below 30% of your limit. Over several months, your score will climb.
238
+
239
+ ## What is probate in Canada?
240
+
241
+ Probate is the legal process of validating a will and giving the executor authority to administer the estate. In Canada, probate fees vary by province, Ontario, for example, charges Estate Administration Tax that's roughly 1.5% on assets over $50,000. Assets that pass through joint ownership or named beneficiary designations (like RRSPs and TFSAs) typically bypass probate. Estate planning often focuses on minimizing the value of assets that go through probate.
242
+
243
+ ## What is a spousal RRSP?
244
+
245
+ A spousal RRSP is an RRSP where one spouse contributes and gets the tax deduction, but the other spouse owns the account and eventually withdraws the funds. It's a tool for income splitting in retirement, especially useful when one spouse expects much higher income than the other. There's a three-year rule: if your spouse withdraws from the spousal RRSP within three calendar years of your contribution, the withdrawal is attributed back to you for tax purposes.
246
+
247
+ ## What is an RRSP?
248
+
249
+ An RRSP, or Registered Retirement Savings Plan, is a Canadian retirement account that provides a tax deduction when you contribute. The money inside grows tax-deferred, meaning you don't pay tax on investment gains until you withdraw. Withdrawals in retirement are taxed as income, but most people are in a lower tax bracket by then, making the overall tax savings significant. You can contribute up to 18% of your previous year's earned income, up to an annual CRA limit. The deadline to contribute for the prior tax year is typically the first 60 days of the new year.
250
+
251
+ ## What is APR?
252
+
253
+ APR stands for Annual Percentage Rate. It represents the true yearly cost of borrowing, including the interest rate plus fees and additional charges, so it's a more complete picture than the interest rate alone. In Canada, the equivalent disclosure you'll often see is the effective annual rate, or EAR, particularly on credit cards. When comparing loan or credit card offers, compare APR or EAR rather than just the stated interest rate.
254
+
255
+ ## What is a hard credit inquiry versus a soft inquiry?
256
+
257
+ A hard inquiry happens when a lender pulls your credit report to make a lending decision, like applying for a mortgage, car loan, or credit card. Hard inquiries can knock a few points off your credit score temporarily and stay on your report for about three years in Canada. A soft inquiry happens during things like a pre-approval check you initiate, an employer background check, or when you check your own credit. Soft inquiries don't affect your score.
258
+
259
+ ## What is the FIRE movement?
260
+
261
+ FIRE stands for Financial Independence, Retire Early. The core idea is to save and invest a high percentage of your income, often 50% or more, so that investment returns can cover your living expenses long before traditional retirement age. In Canada, FIRE often involves maxing out TFSAs and RRSPs and building a low-cost ETF portfolio. There are softer flavours too: Lean FIRE (very frugal lifestyle), Fat FIRE (higher spending target), and Coast FIRE (saving aggressively early, then letting compounding do the work).
262
+
263
+ ## What is the difference between a stock and a bond?
264
+
265
+ A stock represents partial ownership in a company. When you buy a stock, you become a shareholder and can benefit from the company growing in value. A bond is essentially a loan you make to a company or government, paid back with regular interest over a set period. Stocks carry higher risk but higher potential returns, while bonds are generally more stable but with lower returns. Both can be held inside registered accounts like a TFSA or RRSP in Canada.
266
+
267
+ ## What is mortgage refinancing?
268
+
269
+ Refinancing a mortgage means breaking your existing mortgage and replacing it with a new one, usually to access home equity, secure a lower interest rate, or restructure your loan. In Canada, breaking a fixed-rate mortgage typically triggers a prepayment penalty calculated as the greater of three months' interest or the interest rate differential (IRD). It's worth running the numbers carefully, sometimes the penalty wipes out the savings from a lower rate.
270
+
271
+ ## What is a personal loan?
272
+
273
+ A personal loan is an unsecured loan from a bank, credit union, or online lender that you repay in fixed monthly installments over a set term. Because there's no collateral, lenders rely on your credit score and income to decide on approval and rate. In Canada, personal loans are commonly used to consolidate high-interest credit card debt, cover large one-time expenses, or fund home improvements. Rates vary a lot based on your credit profile, so compare offers from multiple lenders, credit unions often have competitive rates.
274
+
275
+ ## What is a LIRA?
276
+
277
+ A LIRA, or Locked-In Retirement Account, is a Canadian retirement account that holds pension funds transferred from a former employer's pension plan. The money stays locked in, meaning you can't withdraw it freely, until retirement, when it's converted into a Life Income Fund (LIF) or used to buy a life annuity. LIRAs are governed by either federal or provincial pension legislation depending on where the original pension came from.
278
+
279
+ ## What is a good credit score in Canada?
280
+
281
+ Canadian credit scores run from 300 to 900. Generally, below 560 is poor, 560 to 659 is fair, 660 to 724 is good, 725 to 759 is very good, and 760 and above is excellent. A score of 660 or higher will qualify you for most loan and credit products, while scores above 760 typically unlock the best interest rates. If your score is lower, consistent on-time payments and reducing credit card balances are the fastest ways to improve it.
282
+
283
+ ## What are the Big 6 banks in Canada?
284
+
285
+ The Big 6 are the six largest banks in Canada: RBC (Royal Bank of Canada), TD (Toronto-Dominion Bank), Scotiabank, BMO (Bank of Montreal), CIBC (Canadian Imperial Bank of Commerce), and National Bank of Canada. They dominate Canadian banking, offering chequing and savings accounts, mortgages, credit cards, and investment accounts. They're all federally regulated and CDIC members. They offer convenience and stability, but credit unions and online banks sometimes have better rates and lower fees for specific products.
286
+
287
+ ## What is OAS?
288
+
289
+ OAS, or Old Age Security, is a Canadian government pension paid to most Canadians 65 and older who meet residency requirements. Unlike CPP, it's funded out of general tax revenue and you don't need to have contributed to receive it. The amount depends on how long you've lived in Canada since age 18. Higher-income seniors face an OAS clawback when their net income exceeds a threshold (around $90,000+, indexed annually).
290
+
291
+ ## What is a reverse mortgage in Canada?
292
+
293
+ A reverse mortgage is a loan available to Canadian homeowners aged 55 or older that lets you borrow against your home's equity without monthly payments. The loan plus accumulated interest is repaid when you sell the home, move out, or pass away. Two main providers in Canada are HomeEquity Bank (CHIP) and Equitable Bank. Reverse mortgages can supplement retirement income, but interest rates are typically higher than traditional mortgages and the balance grows over time.
294
+
295
+ ## What is a HELOC?
296
+
297
+ A HELOC, or Home Equity Line of Credit, is a secured line of credit using your home as collateral. In Canada, you can typically borrow up to a combined 80% of your home's value when factoring in your mortgage. HELOCs offer flexibility and lower interest rates than unsecured borrowing because the home backs the loan, but missing payments puts your property at risk. They're often used for renovations, debt consolidation, or as a backup line for emergencies.
298
+
299
+ ## What is inflation and how does it affect my savings?
300
+
301
+ Inflation is the general rise in prices over time, which reduces the purchasing power of money. In Canada, the Bank of Canada targets inflation around 2%. If inflation is running at 3% and your savings account is only earning 1%, your money is losing real value. That's why holding large amounts of cash in a low-interest account long-term isn't ideal. Keeping savings in a high-interest savings account or GIC, and investing a portion in diversified assets like equities, helps protect your wealth against inflation.
302
+
fiscal.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Dict
3
+ from langchain_huggingface import HuggingFaceEmbeddings, ChatHuggingFace, HuggingFaceEndpoint
4
+ from langchain_community.vectorstores import Chroma
5
+ from langchain_community.retrievers import BM25Retriever
6
+ from langchain.retrievers import EnsembleRetriever
7
+ from langchain.chains import ConversationalRetrievalChain
8
+ from langchain.memory import ConversationBufferMemory
9
+ from langchain.prompts import PromptTemplate
10
+ from langchain_core.documents import Document
11
+
12
+ # ---------- Embeddings ----------
13
+ embeddings = HuggingFaceEmbeddings(
14
+ model_name="nomic-ai/nomic-embed-text-v1",
15
+ model_kwargs={"trust_remote_code": True},
16
+ )
17
+
18
+ # ---------- Vector store ----------
19
+ vectorstore = Chroma(
20
+ persist_directory="./chroma_db",
21
+ embedding_function=embeddings,
22
+ )
23
+
24
+ # ---------- Hybrid retriever ----------
25
+ vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 8})
26
+
27
+ all_docs = vectorstore.get()
28
+ docs_for_bm25 = [Document(page_content=d) for d in all_docs["documents"]]
29
+ bm25_retriever = BM25Retriever.from_documents(docs_for_bm25)
30
+ bm25_retriever.k = 8
31
+
32
+ retriever = EnsembleRetriever(
33
+ retrievers=[bm25_retriever, vector_retriever],
34
+ weights=[0.6, 0.4],
35
+ )
36
+
37
+ # ---------- LLM ----------
38
+ endpoint = HuggingFaceEndpoint(
39
+ repo_id="Qwen/Qwen2.5-14B-Instruct",
40
+ huggingfacehub_api_token=os.environ["HF_API_KEY"],
41
+ temperature=0.3,
42
+ max_new_tokens=1024,
43
+ task="conversational",
44
+ )
45
+ llm = ChatHuggingFace(llm=endpoint)
46
+
47
+ # ---------- Prompts ----------
48
+ condense_prompt = PromptTemplate.from_template(
49
+ """Given the conversation below and a follow-up question, rephrase the
50
+ follow-up as a standalone question that captures all relevant context.
51
+
52
+ Chat History:
53
+ {chat_history}
54
+
55
+ Follow-up Question: {question}
56
+
57
+ Standalone Question:"""
58
+ )
59
+
60
+ # Base QA prompt template — financial_context injected at runtime
61
+ QA_TEMPLATE = """You are FISCAL, a friendly AI assistant for Canadian banking, personal finance, and investments.
62
+
63
+ The user has connected their bank account. Here is their live financial data:
64
+ {financial_context}
65
+
66
+ Answer using ONLY the context below combined with the financial data above. Do not add facts not in the context. Do not mention any underlying AI model. If the context does not contain enough info, say so briefly and offer to help with a related Canadian finance topic.
67
+
68
+ Context:
69
+ {context}
70
+
71
+ Question: {question}
72
+
73
+ Answer:"""
74
+
75
+ # ---------- Per-user memory ----------
76
+ _memory_store: Dict[str, ConversationBufferMemory] = {}
77
+
78
+
79
+ def _get_memory(user_id: str) -> ConversationBufferMemory:
80
+ if user_id not in _memory_store:
81
+ _memory_store[user_id] = ConversationBufferMemory(
82
+ memory_key="chat_history",
83
+ return_messages=True,
84
+ output_key="answer",
85
+ )
86
+ return _memory_store[user_id]
87
+
88
+
89
+ def get_answer(message: str, user_id: str, financial_context: str = "") -> str:
90
+ # Build prompt with financial context already filled in
91
+ filled_template = QA_TEMPLATE.replace(
92
+ "{financial_context}",
93
+ financial_context or "No bank data available."
94
+ )
95
+
96
+ prompt = PromptTemplate(
97
+ template=filled_template,
98
+ input_variables=["context", "question"],
99
+ )
100
+
101
+ chain = ConversationalRetrievalChain.from_llm(
102
+ llm=llm,
103
+ retriever=retriever,
104
+ memory=_get_memory(user_id),
105
+ condense_question_prompt=condense_prompt,
106
+ combine_docs_chain_kwargs={"prompt": prompt},
107
+ return_source_documents=False,
108
+ )
109
+
110
+ result = chain.invoke({"question": message})
111
+ return result["answer"]
112
+
113
+
114
+ def clear_memory(user_id: str) -> None:
115
+ if user_id in _memory_store:
116
+ _memory_store[user_id].clear()
plaid_client.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from datetime import date, timedelta
3
+ from plaid.api import plaid_api
4
+ from plaid.configuration import Configuration
5
+ from plaid.api_client import ApiClient
6
+ from plaid.model.products import Products
7
+ from plaid.model.country_code import CountryCode
8
+ from plaid.model.accounts_balance_get_request import AccountsBalanceGetRequest
9
+ from plaid.model.transactions_get_request import TransactionsGetRequest
10
+ from plaid.model.transactions_get_request_options import TransactionsGetRequestOptions
11
+
12
+ # ---------- Client setup ----------
13
+
14
+ def _make_client():
15
+ env = os.environ.get("PLAID_ENV", "sandbox")
16
+ host = {
17
+ "sandbox": "https://sandbox.plaid.com",
18
+ "development": "https://development.plaid.com",
19
+ "production": "https://production.plaid.com",
20
+ }[env]
21
+
22
+ config = Configuration(
23
+ host=host,
24
+ api_key={
25
+ "clientId": os.environ["PLAID_CLIENT_ID"], # reads from .env
26
+ "secret": os.environ["PLAID_SECRET"]
27
+ }
28
+ )
29
+ return plaid_api.PlaidApi(ApiClient(config))
30
+
31
+ plaid_client = _make_client()
32
+
33
+
34
+ # ---------- Balances ----------
35
+
36
+ def get_balances(access_token: str) -> str:
37
+ """Returns formatted balance snapshot for the LLM prompt."""
38
+ request = AccountsBalanceGetRequest(access_token=access_token)
39
+ response = plaid_client.accounts_balance_get(request)
40
+
41
+ lines = ["USER'S CURRENT ACCOUNT BALANCES:"]
42
+ for account in response['accounts']:
43
+ name = account['name']
44
+ subtype = account['subtype'] # 'checking', 'credit card', etc.
45
+ current = account['balances']['current']
46
+
47
+ if subtype == 'credit card':
48
+ limit = account['balances']['limit'] or 0
49
+ available = account['balances']['available'] or (limit - current)
50
+ lines.append(
51
+ f"- {name} (Visa/Credit): "
52
+ f"${current:.2f} owing, ${available:.2f} available"
53
+ )
54
+ else:
55
+ lines.append(f"- {name} (Chequing): ${current:.2f}")
56
+
57
+ return "\n".join(lines)
58
+
59
+
60
+ # ---------- Transactions ----------
61
+
62
+ def get_transactions(access_token: str, days: int = 30) -> str:
63
+ """Returns formatted transaction summary for the LLM prompt."""
64
+ end_date = date.today()
65
+ start_date = end_date - timedelta(days=days)
66
+
67
+ request = TransactionsGetRequest(
68
+ access_token=access_token,
69
+ start_date=start_date,
70
+ end_date=end_date,
71
+ options=TransactionsGetRequestOptions(
72
+ count=100,
73
+ include_personal_finance_category=True
74
+ )
75
+ )
76
+ response = plaid_client.transactions_get(request)
77
+ transactions = response['transactions']
78
+
79
+ if not transactions:
80
+ return "No transactions found in the last 30 days."
81
+
82
+ # Python does the math — never let the LLM calculate
83
+ category_totals: dict[str, float] = {}
84
+ for txn in transactions:
85
+ if txn['amount'] <= 0:
86
+ continue # skip deposits/refunds
87
+ category = txn.get('personal_finance_category', {}).get('primary', 'OTHER')
88
+ category_totals[category] = category_totals.get(category, 0) + txn['amount']
89
+
90
+ # Format for prompt
91
+ lines = [f"SPENDING SUMMARY (last {days} days):"]
92
+ for category, total in sorted(category_totals.items(), key=lambda x: -x[1]):
93
+ readable = category.replace("_", " ").title()
94
+ lines.append(f"- {readable}: ${total:.2f}")
95
+
96
+ lines.append(f"\nTotal spent: ${sum(category_totals.values()):.2f}")
97
+ lines.append(f"\nRECENT TRANSACTIONS (last 10):")
98
+ for txn in transactions[:10]:
99
+ lines.append(
100
+ f"- {txn['date']} {txn['name']:<30} ${txn['amount']:.2f}"
101
+ )
102
+
103
+ return "\n".join(lines)
104
+
105
+
106
+ # ---------- Combined snapshot ----------
107
+
108
+ def get_financial_snapshot(access_token: str) -> str:
109
+ """
110
+ Returns full financial context string to inject into the LLM prompt.
111
+ Combines balances + transaction summary.
112
+ """
113
+ try:
114
+ balances = get_balances(access_token)
115
+ transactions = get_transactions(access_token)
116
+ return f"{balances}\n\n{transactions}"
117
+ except Exception as e:
118
+ return f"Unable to fetch account data: {str(e)}"
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.111.0
2
+ uvicorn==0.29.0
3
+ pydantic==2.7.1
4
+ PyJWT==2.8.0
5
+ langchain==0.2.6
6
+ langchain-community==0.2.6
7
+ langchain-huggingface==0.1.0
8
+ sentence-transformers==3.0.0
9
+ chromadb==0.5.0
10
+ rank_bm25==0.2.2
11
+ supabase==2.5.0
12
+ plaid-python==16.2.0
13
+ python-dotenv==1.0.1
14
+ einops==0.8.0
15
+ huggingface-hub>=0.24.0