Skip to content
Docs

Command palette

Jump to a page or search the docs.

Changelog

Insights

How M-Insights computes a car's total cost of ownership and cost per km or mile for a period, compares it across the garage's cars, and how proactive insights, last month's summary and the dashboard photo work.

Markdown

What it does

A car's total cost of ownership (TCO) for a period (RF18, US21) is depreciation + running costs + interest and fees (periodTco in @autofolio/domain). Loan principal never counts (RN05): a payment is counted once, as the interest and fees booked to the ledger. "Compare cars" (RF25, US22) puts every car's cost per km or mile for the same period side by side, a car sold in it over the months it was owned: its running costs over its distance, the number M-Veiculo shows, as the M-Insights board draws it (the M3 at $1.12 / mi there, its TCO $1.80 / mi above).

  • Running costs are M-Gastos' "Total spent" for the same period (totalSpent): its six categories, upgrades included, read through each Expense's and Service's current ledger entry (a correction replaces the original; raw ledger rows are never summed).
  • Interest and fees are the car's loan_interest and loan_fee entries dated in the period, as they stand after corrections. M-Financiamento shows the same entries over the Loan's whole life, so a year's are part of that total (the seeded M3: $1,870 of $3,740).
  • Depreciation is the car's value at the period's start minus its value at its end (depreciation). The value on a day is the latest purchase price or Valuation on or before it, else the first one: a car bought during the period starts at its price. A gain in value is negative and lowers the TCO; the app labels it "Estimated appreciation", never a profit (RN04). Without any value it is unknown and left out.
  • Distance is the latest odometer reading minus the last one on or before the period's start, as on M-Veiculo. Without two readings there is no cost per distance.

Periods are calendar months up to today's: 1m (this month), 6m, 12m, or all (the whole ownership). 12m is M-Gastos' 12 months.

Procedures

ProcedureWhoFailures
insights.tco { vehicleId, period, today, distanceUnit }Members of the OrganizationINPUT_VALIDATION, NOT_FOUND (car)
insights.compare { period, today, distanceUnit }Members of the OrganizationINPUT_VALIDATION
insights.overview { vehicleId?, period, today, distanceUnit }Members of the OrganizationINPUT_VALIDATION, NOT_FOUND (car)

tco answers the total and per distance, each component, the distance in km, running costs by category and by month (at most 12). compare answers the cars with a distance in the period, each with its running costs and those per distance and whether it is sold: current cars in M-Garagem's order, then cars sold during the period (their costs and readings end at the sale). Amounts are integer minor units in the car's currency; a cost or Valuation in another currency without a stored rate is left out.

Data and isolation

Reads only, through the request's app_tenant handle with the Organization filter; row-level security alone hides another Organization's cars. Nothing is written or audited, and amounts never go to logs (RNF07).

App

M-Insights (screen-M-Insights, route /insights) opens from M-Patrimonio's Insights button. The subtitle picks a current car; the segmented control (periodo-1m, periodo-6m, periodo-1a, periodo-tudo) the period. The TCO card shows the TCO per distance, the total and the distance with the months' running costs as a line; then the three components with the formula, the category ring (grey ramp, legend with percentages) and "Compare cars" (each car's running costs per distance, a sold one marked "Sold"), whose current cars open their numbers. Its footnote says why that figure is lower than the TCO per distance above: it leaves out depreciation and interest. The screen reads insights.overview, Compare cars and the chosen car's TCO in one request: without a car the server picks the first current one with a cost per distance in the period, else the first current one, and that choice stays when the period changes. autofolio://dev/M-Insights opens the seeded M3.

Proactive insights (AI spec §4)

With the ai.insights flag on, each current car is compared with its own pattern, and only a real change becomes an insight, always with its number and its basis (insights.signals.ts, pure):

KindFound whenShowsPlan
fuel_economy_dropthe last full-to-full tank (RF09) used 15% or more per distance than the car's average over at least two tanks before it, the fill at most 45 days oldthe last tank and the average, in the account's economy unitPlus
spend_spikethis month's costs (M-Gastos' bars) pass twice the average of the six months before, three of them with costsspent so far and the monthly averageFree
insurance_increasean insurance payment is 5% or more above the one 12 months (± 20 days) before it, at most 45 days old; three times or more is a change of cadence, not a priceboth payments and their datesPlus
service_duea service reminder is due soon or overdue (US17's alert)the due date, distance left and paceFree

Free sees the basic kinds, Plus all of them (ADR-0017's tier; PLUS_INSIGHTS). No model computes these numbers (RN03); the insights model row of the AI registry is unused for now.

Last month's summary. The card above the list: the household's running costs in the last calendar month per currency, against the average of the six months before (once three of them had costs), km driven, the biggest category and each car with costs.

Storage. The insight table records one row per kind and key (the fill, the month, the payment, the reminder's due), so the same change is never news twice; only status changes (new → dismissed). The numbers are read from the records each time, never stored. RLS as for Moments: a Member sees only granted cars; the summary row (no car) only owners and admins.

Morning push. The insights job runs in each data region's morning hour (07, 11, 14 UTC, as reminders and Moments): it records what each household's cars hold and pushes the new ones from the last 7 days to the Members who allow push and see that car, once per device, never retried. Plus's kinds reach Plus accounts only; a due service is listed, not pushed (the reminder job already did); the summary reaches owners and admins in the month's first week. Push copy carries percentages, never an amount, plate or VIN. The scan pages through the households and keeps its place for the day in job_cursor, as the Moments job does. Reading the feed writes only the insights it has not recorded before.

ProcedureWhoFailures
insights.feed { today }Members of the OrganizationINPUT_VALIDATION
insights.dismiss { id }owners, admins and editors of the carNOT_FOUND, FORBIDDEN

The app's Insights screen (screen-Insights, route /insight-feed) shows the summary card, "By car" when several cars had costs, then "What changed": each row opens the car's costs or maintenance and has Hide (also a VoiceOver action). Free sees a row to Plus. The garage shows "Insights for you · N to see" while some wait, and Profile always lists it with the flag on. autofolio://dev/Insights opens it on the demo garage: the seeded Porsche's last tank (+35%) and its insurance renewal (+12%), the M3's due services and September's summary.

Dashboard photo (Painel)

M-Veiculo → More → "Odometer from a photo" (screen-Painel, route /odometer-photo, with ai.insights on and a car in the garage): photograph the instrument cluster (or pick a photo), the text is read on the phone (Vision / ML Kit, never metered, nothing uploaded) and readCluster picks the odometer: the number on an ODO/Gesamt line, else the largest whole number that is not the range or a trip (a trip shows tenths); clocks, temperatures and percentages are skipped, miles when the display says so. The range is shown for checking only, other numbers can be picked, and the value stays a draft until Save (RN10). odometer.record { id, vehicleId, km, readOn, reason? } appends the reading with source photo through RN07: a lower one (or one above a later reading) answers odometer_reason_required and the screen asks a reason; saved, it is flagged. It goes through the offline queue (RNF04, the id is made once); a reading the server sends back later opens the screen again from a toast. A sold car answers BAD_REQUEST, another household's NOT_FOUND. autofolio://dev/Painel opens it with a sample cluster read past the M3's last reading.

Key files

Edit on GitHubDocumentation is in English.

We use essential cookies to make our app work. With your consent, we also use optional cookies to improve your experience. Read our Cookie Policy and Privacy Policy.