// ----------------------------------------------------------------------------- // House-Ka — Homes-for-sale aggregator (Canada outside Québec, Ontario first) // Author: Simon-Pierre Boucher — contact@spboucher.ai // pages/Rates.tsx : /rates — Mortgage Intelligence. // Market view (best/median/variations), per-bank comparator with rate kind // (posted vs special) and freshness, history, prime rates, source health. // No invented rates: everything comes from the institutions' official // pages, with provenance. // ----------------------------------------------------------------------------- import { useEffect, useState } from "react"; import { MortgageBest, MortgageIntelligence, MortgageProviderHealth, fetchMortgageBest, fetchMortgageIntelligence, fetchMortgageProviders, fmtRate, } from "../api"; import RateHistory from "../components/RateHistory"; const TERMS: [number, string][] = [ [12, "1 year"], [24, "2 years"], [36, "3 years"], [48, "4 years"], [60, "5 years"], [84, "7 years"], [120, "10 years"], ]; const KIND_EN: Record = { posted: "posted", special: "special offer" }; const INSURED_EN: Record = { insured: "insured", insurable: "insurable", uninsured: "uninsured", unknown: "", }; const termLabel = (m: number) => TERMS.find(([t]) => t === m)?.[1] ?? `${m} months`; const freshness = (min: number) => min < 60 ? `${min} min ago` : min < 48 * 60 ? `${Math.round(min / 60)} h ago` : `${Math.round(min / 1440)} d ago`; const varTxt = (v: number | null) => v == null ? "—" : v === 0 ? "stable" : `${v > 0 ? "▲ +" : "▼ "}${v.toFixed(2)} pt`; export default function RatesPage() { const [intel, setIntel] = useState(null); const [rateType, setRateType] = useState<"fixed" | "variable">("fixed"); const [term, setTerm] = useState(60); const [best, setBest] = useState(null); const [health, setHealth] = useState([]); useEffect(() => { document.title = "Mortgage rates in Canada — live comparator | House-Ka"; fetchMortgageIntelligence().then(setIntel).catch(() => setIntel(null)); fetchMortgageProviders().then((r) => setHealth(r.providers)).catch(() => {}); }, []); useEffect(() => { setBest(null); fetchMortgageBest(rateType, term).then(setBest).catch(() => setBest(null)); }, [rateType, term]); return (

Live mortgage rates

The rates actually published by the big Canadian institutions (banks, Desjardins, virtual lenders and monolines), collected continuously by House-Ka. Every rate shows its kind (posted or special offer), its official source and its freshness — never an invented rate, never a stale one without a warning.

{intel && intel.products.length > 0 && (

Market overview

{intel.products.map((p) => ( ))}
{intel.prime_rates.length > 0 && (

Prime rates:{" "} {intel.prime_rates.map((p, i) => ( {i > 0 && " · "} {p.institution} {fmtRate(p.rate)} ))}

)}
)}

Compare the institutions

{best == null ? (

No current rate for this product — try another term.

) : ( <>
{best.per_institution.map((r, i) => ( ))}
InstitutionProductRateKindFreshnessSource
{r.institution}{i === 0 && best} {r.product_name} {fmtRate(r.rate)}{r.apr != null ? ` (APR ${fmtRate(r.apr)})` : ""} {KIND_EN[r.kind] ?? r.kind} {INSURED_EN[r.insured_status] ? ` · ${INSURED_EN[r.insured_status]}` : ""} {freshness(r.age_minutes)} {r.source_url && ( official ↗ )}

One comparable product per institution (the special offer wins over the posted rate). Incomparable products — insured vs uninsured, posted vs special — are never mixed in the same ranking without saying so.

)}

Trend — {rateType} {termLabel(term)}

{health.length > 0 && (

Source freshness

{health.map((h) => ( {h.institution} ))}

Green: recent successful collection · yellow: data kept but aging · red: source in error. When a collection fails, the last valid rates stay displayed with their date.

)}

Indicative information only, without guarantee — actual conditions depend on your file. House-Ka is neither a lender nor a mortgage broker.

); }