Coach skills (E7)¶
Nine analyses that sit on top of the finance tools (see coach.md). Each one is built the same way:
- Deterministic helper(s) in Python (
src/coach/skills/) where a computation is needed - numbers come from code, never from the model. - New finance MCP tools exposing the helper through the same choke point as every other tool: free text wrapped as
untrusted_text, memory ids pseudonymised, the final privacy assertion (redactor ⊇ guard). Read-only, exceptquestions_propose(creates a sealed PROPOSAL of questions, likememory_propose). - A
PromptSpec(agent/prompt.py,SKILLS) that the web app (quick prompts of the Coach page) anduv run coach coach ask --skill <id>run through the normal runner. - A Claude Code skill (
.claude/skills/<id>/SKILL.md) for interactive use in this repository.
| Skill | Story | Tool(s) | Runs in web / CLI | Web search |
|---|---|---|---|---|
monthly-review |
E7-3 | monthly_review (+ explain_spike) |
yes | no |
explain-spike |
E7-4 | explain_spike |
yes | no |
subscription-audit |
E7-5 | subscription_audit, questions_propose |
yes | no |
contract-check |
E7-6 | cancellability (+ coach memory doc add/extract) |
yes (rules); documents: CLI | no |
find-cheaper |
E7-7, E8-4 | savings_estimate, subscriptions_inventory, alternatives_record, add_insight |
no: Claude Code only | yes |
mortgage-check |
E7-8 | mortgage_check, questions_propose |
yes (no market data of its own) | Claude Code only |
what-if |
E7-9 | what_if |
yes | no |
tax-helper |
E7-10 | tax_candidates |
yes | no |
onboarding-interview |
E7-11 | onboarding_status, questions_propose, memory_propose |
yes + coach onboarding + Set up page |
no |
Rules every skill follows¶
- Numbers come from code. The model quotes amounts, percentages and dates exactly as a tool returned them; the answer's numbers are checked against the tool results (unverified ones are flagged).
- Model-facing output is redacted (coarse by default): accounts and people as pseudonyms, transactions as hashed
h_refs, merchants generalised, third-party text asuntrusted_text, memory ids asliability-1,contract-1,kid-1. The skills compute on the real dataset and rewrite only their free-text, reference and account fields (skills/redact.py) with the household redactor; the tool choke point still wraps, pseudonymises and asserts. Tests: every new tool on a synthetic household with names, an employer, a school and a town, plus a hostile merchant name. - Proposals only. A skill never writes memory:
memory_propose/questions_proposecreate sealed proposals that the user accepts themselves (web: Memory > Proposals; terminal:uv run coach memory proposals). Claude Code skills never runmemory accept, never pass--yes/--force/--insecure, never readmemory/,data/or the database directly, never edit.claude/settings.json. - No investment-product advice; budgeting and education only; tax and legal output is "verify with your contract / on
impots.gouv.fr / Agenzia delle Entrate", FR and IT wording where relevant (
memory/preferences.mdrules apply when visible). - Web search only in the interactive Claude Code skills
find-cheaperandmortgage-check, with generic non-personal queries (no name, address, account data or identifying amount); every price and rate is shown with its source and date, and anything older than 30 days is flagged possibly outdated. The web/CLI runtime has no web tool (itsclaude -pis started withWebSearchandWebFetchdenied). Those two skills readuv run coach privacy status --jsonFIRST and refuse to search whenweb_search_skillsis false ([privacy] local_only/offline, E11-4);classify enrichis a separate, opt-in path ([privacy] web_enrich, docs/privacy.md). - Disclaimers (contract, loan, savings, tax, letters, the closing "general information, not financial advice") come from one module,
coach/disclaimers.py(FR / IT / EN); every text a model writes is labelled AI-generated and checked for investment-product recommendations (coach/compliance.py, E11-5). - Everything degrades: with missing data a skill says exactly what it needs and offers question proposals instead of guessing.
monthly-review (E7-3)¶
- Purpose: one closed month against the usual, the biggest movers, budgets, cash flow, outlook, then exactly three concrete actions.
- Inputs:
month(YYYY-MM, default the last closed month). - Tools:
monthly_review. Usual of a category = the average net spending, one-offs / capital /exclude_from_averagesleft out, over the last 12 months BEFORE the reviewed month that every major account carrying the category fully covers; fewer than 3 months = low confidence; none = the category is listed apart without a comparison. Returns the cash flow (income, spending, saved, debt service and estimated principal, drawn from savings accounts / unconnected accounts),against_usual,movers(|difference| >= 20 EUR, with evidence: top transactions,rec_series,anm_anomalies,chg_price changes),one_offslisted apart,budgetsas they stood at month end, the forecast flags as of today, and coverage notes. The prompt asks for at most 250 words andadd_insight(kindreview). - Safety: read-only; the prompt may store one insight; no memory proposal.
- Limits: the forecast part looks ahead from today, not from the reviewed month; a month not fully covered by an account is flagged and its figures are partial; movers need a baseline.
explain-spike (E7-4)¶
- Purpose: why was a category / group / account high in a month.
- Inputs:
category(leaf or group, e.g.food) ORaccount(pseudonym),month. - Tools:
explain_spike: totals against usual, the spending split by class -one_off(tagged),recurring(a detected series),new_merchant(first payment ever that month),habitual-, each merchant's change against its own usual (delta = month - usual; the class sums add up to the excess to the cent), categories, the top transactions (redacted refs), the same month last year only when every account carrying the spending covers it, coverage notes. - Safety: read-only. Limits: the classes depend on the recurring detection; no baseline without an earlier covered month.
subscription-audit (E7-5)¶
- Purpose: all active recurring costs grouped (streaming / media, software, telecom, memberships, insurance, energy / utilities), monthly and yearly cost, price rises, duplicates and overlaps, contracts on file, usage, ranked review candidates.
- Inputs:
limit. - Tools:
subscription_audit;subscriptions_inventoryfor one service in detail (contract status, the usage the household recorded, cancellation rules, stored alternatives, the latest decision: E8, see subscriptions.md);questions_proposefor the unknown usage (the questions carry the real service name only on the user's machine, and never name a possible person);cancellabilityandfind-cheaperfor the next step. - Signals:
usage_unknown(almost always: usage is not in bank data, so it is ASKED),overlap(several providers of a discretionary kind; telecom lines are informational),duplicate_amount(same amount, same day +-1, or one merchant paid from two accounts),price_increase,marked_to_cancel(the contract sayskeep: falseand it is still paid),compare_offers(telecom, insurance, energy),no_contract. - Expected yearly savings ranges are FIXED SHARES of the yearly cost: discretionary 30-100 %, telecom 10-40 %, insurance 5-25 %,
energy / utilities 5-15 %, a possible duplicate 0 to its whole cost, a marked-to-cancel service its whole cost; a price rise raises the
upper bound to its yearly effect. They are not market quotes;
find-cheaperreplaces them with dated quotes. Candidates are ranked by upper bound; the sum of the ranges is an upper-bound picture, the actions are alternatives. - Safety: never advises cancelling ("worth reviewing"). Limits: loans, rent, taxes, school fees are not audited.
contract-check (E7-6)¶
- Purpose: fill
contracts/*.yamlfrom a contract PDF and say whether / when / how a contract can be cancelled. - Flow (CLI):
coach memory doc add FILE --kind contract --for ID->coach memory doc extract DOC --into contract ID(DRY RUN: it prints exactly what would be sent, redacted) -> the user reads it ->--send, which the user's permission rules ask about, and only with the user's yes -> a PROPOSAL with the source snippet of each field -> the user accepts it themselves. - E8: the same rules answer every row of the subscriptions inventory (
cancellation), the calendar's notice deadlines and the local cancellation letters (coach subs letter); a contract file can be drafted from a series withcontracts_draft(proposals) orcoach subs draft-contracts; see subscriptions.md. - Tool:
cancellability(contract|series|kind+ dates;country,include_rules). Output:can_cancel_now(true / false / null),earliest_effective_date, notice, method,conditions,unknown,rules(name + law), the next anniversary / contractual notice deadline, and "verify with your contract". A series with no contract file only knows the first payment seen, so a young contract is never claimed (the answer isnull). - Rules table (
skills/cancel.py, law names, no URLs). FR: Loi Hamon (home and car insurance cancellable after one year, effect one month after the request), Loi Chatel for insurance renewals (two months before the anniversary, late notice = free cancellation), mutuelle infra-annual resiliation (loi 2019-733: after one year at any time, group contracts excluded), Loi Lemoine (borrower insurance at any time, answer in 10 working days), telecom (Loi Chatel: 24 months max, 25 % of the remainder after 12 months), energy (no fee, the new supplier handles the switch), "résiliation en 3 clics" (loi 2022-1158), Loi Chatel tacit renewal of service contracts (L215-1). IT: Legge Bersani for telecom and energy (no penalties, 30 days max), RC auto without tacit renewal (DL 179/2012), other insurance (disdetta; Codice civile art. 1899), insurance linked to a mortgage (DL 1/2012 art. 28, IVASS 40/2018), Codice del consumo for subscriptions. The CLIuv run coach contract check [ID]prints the same answer for the user with real provider names. - Limits: general rules, not legal advice; a scanned PDF has no text layer (no OCR);
group_contractandtacit_renewalmust be given.
find-cheaper (E7-7) - Claude Code only¶
- Purpose: cheaper alternatives for one recurring service, from official / neutral comparators first (FR: energie-info.fr comparator, ARCEP-related and Que Choisir comparators; IT: ARERA Portale Offerte, AGCOM, IVASS), as a dated table with sources.
- Tool:
savings_estimate(current_monthly, alternative_monthly, switching_costs, months, quote_date): monthly saving, yearly saving, net overmonthsafter switching costs, break-even months, and a warning whenquote_dateis older than 30 days or missing. Exact integer-cent arithmetic; a promo price is computed twice (promo months, then full price). - Safety: generic queries only (category + cost bucket + features); sources with URLs and dates; never switches or contacts a
provider; the finding is stored with
add_insight(kindfinding) with source URLs and dates in the body. - E8-4 store: each alternative found is recorded with
alternatives_record(servicereffromsubscriptions_inventory, provider, offer, monthly price, httpssource_url,retrieved_at): it appears in the subscription's alternatives table andcoach subs alternatives list, with the savings computed by code; older than 30 days it is "outdated, re-check" and never the current best. - Limits: prices change; a figure older than 30 days is possibly outdated.
mortgage-check (E7-8)¶
- Purpose: amortization, remaining capital and interest, renegotiation / rachat (FR) or surroga (IT), borrower-insurance delegation.
- Tool:
mortgage_check(liability?, market_rate_pct?, market_rate_date?, mode?, bank_fees?, guarantee_fees?, other_fees?, penalty?, alternative_insurance_monthly?, current_insurance_monthly?, insurance_fees?, country?). amortization: annuity, first instalment one month afterstart_date, the schedule, interest paid and to come, interest by year, whether the declared instalment matches. The remaining capital is the declared one while it is at most 120 days old, otherwise the computed one; a missing rate is back-solved from capital + instalment + months when possible and flaggedapproximate.renegotiation_estimate: same remaining capital and months, new payment, interest saved, costs (IRA, bank, guarantee, other), net saving, break-even months, verdict, and the broker rule of thumb (>= 0.7 point, >= 70,000 EUR, >= 10 years) as a flag only. FR rachat: IRA = the LOWER of six months of interest and 3 % of the capital remaining due (Code de la consommation L313-47); same-bank renegotiation: no IRA; IT surroga: no penalty and no bank / notary costs by law (art. 120-quater TUB). A contract penalty amount overrides the cap.insurance_delegation_estimate: (current - alternative) x remaining months, net of fees (Loi Lemoine FR; IT: bring your own policy, IVASS).- E9:
state.scheduleis the loan's own amortization schedule (loans/schedule.py: first due date,payment_day, a deferral, the insurance as a flat premium or a rate on the initial / outstanding capital, a variable rate at its current rate flaggedapproximate); when it is computable the remaining capital and the months left come from it.loans_overviewadds the payments seen, the alerts and the inferred suggestions (always markedinferred: to confirm with the user, never presented as recorded). The user's own calculators:coach loans scenario prepay|renegotiate|insuranceand the loan page (see loans.md). - Degradation: with missing fields the tool lists them in
what_i_need(never guessed) andquestions_propose(liabilities=[id])creates the question proposal. The market rate must be GIVEN (with its date); the tool and the web/CLI runtime never look anything up; the interactive skill may search a generic observatory average and feeds it in. - Safety: "Estimate only: consult your bank or a broker"; no product, lender or insurer recommended.
what-if (E7-9)¶
- Purpose: baseline vs scenario on the forecast. Tool:
what_if(scenario), strict JSON schema, 1 to 6 changes,days30-365. - Changes:
cancel_recurring(removes the series' future occurrences from the forecast),adjust_category(percent of the category's coverage-aware monthly average, spread evenly),set_category_level(to a target),add_monthly/remove_monthly,one_off(+direction: in),prepay_loan(E9-3: exact before / after schedules on the loan's own amortization schedule: the capital due on the prepayment date (a prepayment dated on an instalment day is applied before it), the instalments left, deferral and insurance respected;keep: paymentshortens the loan,keep: termlowers the instalment; without a computable schedule the old capital / rate / term route, and without a rate a zero-interest approximation flaggedapproximate; a possible IRA is reported apart),change_income(percent of the active salary series or a fixed monthly delta). - Output: baseline and scenario (start, minimum balance and date, end balance, balance at 90 days, first negative / at-risk day,
monthly savings),
delta(includingyearly_impact= recurring effects x 12 + one-offs in the next 12 months), per-change effects with evidence and notes. Limits: the baseline is the E4 forecast (balance snapshots required for the forecast part; the monthly and yearly effects work without).
tax-helper (E7-10)¶
- Purpose: candidates that may open a reduction, credit or deduction for an INCOME year (
year, default the current year from October, else the previous one) andcountry(householdcountryinhousehold.yaml, FR by default; IT supported). Reminders only, nothing is filed. - Tool:
tax_candidates. Amounts come from the transactions of the calendar year by CATEGORY or by TAG; each candidate has the rule, rate, ceilings used, estimated range,documents_to_keep,missing_info, the source (law), coverage notes, plusno_data_forwith how to make a payment visible. Disclaimer: "verify on impots.gouv.fr / Agenzia delle Entrate". - FR:
fr-dons(categorycharity.donations: 66 %, 75 % of the first 1,000 EUR for organisations helping people in difficulty; the 20 %-of-income ceiling is not computed),fr-emploi-domicile(tagsemploi-domicile/cesu/services-personne: 50 % within 12,000 EUR + 1,500 per dependent child, max 15,000),fr-garde-enfants(kids.childcare: 50 % within 3,500 EUR per child under 6 on 1 January, from the birth years ofhousehold.yaml),fr-scolarite(flat 61 / 153 / 183 EUR per child by the stage guessed from the age),fr-pinel(assets withscheme: pinelorpinel_commitment_years: annual reduction from price, purchase year and commitment; reduced 2023-2024 rates vs classic),fr-revenus-fonciers(E15-4: one candidate per rental property: gross rents, the micro-foncier abatement and the reel deductions with the loan interest from the amortization schedule, the lower candidate, documents; the Pinel candidate is computed from the price and the total rate the owner declared on the property when both are there, else from the table above; see rental.md),fr-pension-alimentaire(tag),fr-per(tagsper,plan-epargne-retraite; deductions: the saving depends on your marginal rate, not computed, not a recommendation). - IT 730:
it-spese-mediche(health.pharmacy|doctors|optician: 19 % above the 129.11 EUR franchise),it-interessi-mutuo(mortgage liabilities: 19 % of the interest up to 4,000 EUR, interest of the year from the amortization),it-ristrutturazioni(housing.renovationor tagristrutturazione: 50 / 36 / 30 % by year and use, 96,000 EUR ceiling, 10 yearly instalments),it-istruzione(kids.school,education.courses: 19 % up to 800 EUR per student),it-assicurazioni(insurance.lifeor tags: 19 % up to 530 EUR),it-erogazioni-liberali(charity.donations: 26-35 % up to 30,000 EUR). - Limits: rates and ceilings are those known when the module was written; every candidate states what it used, so a change shows up against the official page. Months of the year not covered by the accounts make an amount a lower bound.
onboarding-interview (E7-11)¶
- Purpose: complete what the coach does not know: household (members, birth years, country, privacy declarations - employers, towns,
schools), account owners and purposes, loans (the fields
mortgage-checkneeds), contracts, preferences, budgets, open questions. - Tool:
onboarding_status(stepsdone/partial/todo, what is missing, next actions with the exact commands). - Three ways: the Claude Code skill (answers become
memory_propose/questions_proposeproposals);uv run coach onboarding statusanduv run coach onboarding run(an INTERACTIVE interview that refuses to run without a terminal: every answer is a validated memory edit that is previewed as a diff and written after a typed yes, sourcecli; account owner / purpose throughcoach accounts set; it reuses the questions generator and finishes withcoach memory check); and the web page "Set up" (checklist fromGET /api/v1/onboarding, previewed writes throughPUT /onboarding/household,POST /onboarding/preferences, and the existingPUT /memory/{kind}/{id}andPATCH /accounts/{uid}forms, sourceui). - Limits: in coarse privacy mode memory ids appear as pseudonyms to the model, so it tells the user the exact change rather than proposing against a real id it cannot see.
Reference¶
- Tools and schemas: coach.md. Memory files and proposals: memory.md. Analytics behind the numbers: analytics.md.
- Tests:
tests/test_skills_math.py(loans, savings, cancellability),test_skills_analysis.py(review, audit, what-if, tax),test_skills_tools.py(tools, privacy, proposals),test_skills_prompts.py(PromptSpecs, CLI, fake claude),test_skills_files.py(the SKILL.md files),test_skills_cli.py(onboarding, contract check),test_api_skills.py(web).