Ascenda Wiki
Ascenda · mechanism

The score is a stock, not a grade

A single number in the home blob, and the one decision everything else follows from: it is built over time and never resets. One bad month barely dents it. A good streak lifts it slowly.

0–100 range 50 at signup 8 floor 65/35 pace vs savings
Draft. Nothing here is implemented yet — today the blob reads a hardcoded mock. This page explains the formula we intend to build; the decision record it came from is the full document, including the open questions.
the model

Two layers, one of which never resets

The number is not an accounting figure. It is a reputation-like signal about financial behaviour over time, and it drives three things: the figure in the blob, the blob’s mood, and its colour.

Splitting it in two is what makes that possible. Cycle Health answers “how am I doing this period” and resets every cycle. The Score never resets — it drifts toward whatever Cycle Health reports, slowly.

  • HCycle Health, 0–1 — internal input, recomputed as data arrivesresets per cycle
  • SScore, 0–100 — the blob number, drifts toward Hnever resets
There is deliberately no momentum term. If every cycle’s health is good, the score stays high on its own — that is the reward for consistency. Nobody should lose points because this month was marginally worse than a very good last month.
constraint

The data arrives in lumps

There is no Open Finance integration yet. People import statements weekly or monthly, which means the last few days are almost always empty. Any “spending in the last seven days” signal would be measuring statement lag rather than behaviour, so the model uses none.

What survives batch import is dates. Transactions carry real ones, so spend-to-date against expected-by-date stays valid even when a whole month arrived in a single afternoon.

the fix

Spending is not linear

The naive check is expected = budget × daysElapsed / daysTotal, which assumes money leaks out evenly. Rent does not leak. It hits on day 1 and never again.

On day 18 the linear model claims Moradia should sit at R$1.320. Rent was paid in full on day 1, so it sits at R$2.200 — and the check screams 67% over pace at somebody who is perfectly fine, across 40% of their budget.

So expected is shaped per category. Two shapes in v1, and classifying costs nothing: the essentiality algorithm already reads frequency, recurrence and rigidity. Rigid, recurring, once a month is committed. Frequent, variable, spread out is flow.

100% 0 day 1 day 30 committed — rent, subscriptions, financing owed whole from day 1, posted or not · never extrapolated flow — groceries, dining, transport, leisure accrues over time · projected as spent × daysTotal/daysElapsed day 18
At day 18 the two shapes disagree by forty points of budget. Reading rent against the dashed line is what produced the false alarm; reading it against its own shape reports variance zero.
cycle health

Pace and Savings

Two terms, weighted 65/35 — process and consequence.

H = clamp( 0.65 · Pace + 0.35 · Savings, 0, 1 )

Pace asks whether you are net under the line right now. Variance is computed per category and rolled up on the net, and the first case is the one that matters.

variance  = spent − expected(day)          − surplus, + over
netVariance = sum of every variance

Case A — netVariance ≤ 0
  Pace = 1   full stop, zero penalty

Case B — netVariance > 0
  discShare = discretionary share of the positive variance
  k_blend   = 2.5 · discShare + 1.2 · essShare
  Pace      = clamp( 1 − k_blend · netVariance / total budget, 0, 1 )
Case A is a design position, not an optimisation. The budget cap already encodes the savings goal, so being net under means the goal is being met — and penalising the shape of a success is auditor behaviour. Essentiality only bites once you are genuinely over, and then a discretionary overspend stings about twice as hard as an essential shock.

Savings asks where the month lands against the goal, projected mid-cycle so there is no delayed feedback. It is not Pace restated: savings is the thin gap between income and budget, so it is leveraged. In the worked example below, spending 7% over budget erases 39% of the savings goal. Pace measures process; Savings measures what that process costs. The gap between them is the information.

projectedSpend = committed budgets                     never extrapolated
               + flow spend × daysTotal/daysElapsed
Savings        = clamp( projectedSavings / plannedSavings, 0, 1 )
two channels

Score on the net, detect on the category

Every category is still computed in full — expected, spent, variance, essentiality. That data powers the UI and the coach. What changes is only how it rolls up into the one number.

score

Net, and calm

Over on Lazer, under on Mercado: they cancel and the number does not move. Compensation is automatic, because overspending one category while saving another is good money management, not a failure.

insight

Per category, and always

The same event still surfaces — you went over on Lazer, but you made it up on Mercado. Awareness without punishment, framed as praise for the trade-off. It never touches the score.

persistence

How a month becomes a score

target = H × 100
S_new  = clamp( S_old + alpha(n) · rate · (target − S_old), 8, 100 )

alpha(n) = max( 0.08, 1 / (n + 3) )     n = cycles of evidence fed
rate     = clamp( newDaysCovered / daysTotal, 0, 1 )

rate is the fraction of genuinely new information, which solves the early-month problem for free: on day 2 there is almost nothing new, so the score barely moves however noisy the health reading is.

alpha(n) is the interesting one. With alpha = 1/n exactly, the score is the running average of every cycle health ever recorded — the update rule and the average are the same thing. It is not an arbitrary decay curve; it is literally your average cycle health, weighted by evidence.

Two adjustments to that ideal. A starting offset of three gives the initial 50 the weight of about three months, so one imported month cannot slam a new user to 95. And a floor of 0.08 stops the score freezing: pure 1/n reaches about 0.006 by month 180, at which point somebody who reformed could never climb back — which destroys the entire coaching value.

  • 1st n = 0  ·  alpha = 0.333 — forming - big moves
  • 2nd n = 1  ·  alpha = 0.250 — still forming
  • 3rd n = 2  ·  alpha = 0.200
  • 6th n = 5  ·  alpha = 0.125 — settling
  • 12th n = 11  ·  alpha = 0.083 — nearly at the floor - one year
  • 24th+ n = 23+  ·  alpha = 0.080 — mature, stable forever
The same perfect month moves a second-cycle user +10.5 points and a fifteen-year veteran +1.8. Six times the movement, because the newcomer is still writing their story. And n counts cycles of evidence fed, not calendar age — otherwise inertia could be earned by doing nothing.
worked example

Marina, day 18 of 30

Income R$6.500, budget R$5.480, so the savings goal is R$1.020. Her persisted score is 62, and her last import covered through day 4.

per-category variance shape-aware · day 18
  • fixed Moradia — budget 2200, expected 2200, spent 2200 0
  • fixed Assinaturas — budget 180, expected 180, spent 145 -35
  • flow Mercado — budget 1200, expected 720, spent 760 +40
  • flow Alimentacao — budget 800, expected 480, spent 590 +110
  • flow Transporte — budget 500, expected 300, spent 280 -20
  • flow Lazer — budget 600, expected 360, spent 470 +110
Rolled up
Net variance
+205
Pace
0.914
Savings
0.608
Cycle health
0.807
Rate
(18 − 4)/30 = 0.467
Score
62 → 63
Surpluses absorbed R$55 of the R$260 gross overspend, so only the net R$205 is penalised — and against a R$5.480 budget that is a small slip, treated like one. Moradia, which the linear model would have called 67% over, reports variance zero.
She is projected 7% over budget, and that erases 39% of her savings goal. The leverage is exactly what Pace alone cannot see.

Her blob ticks 62 to 63. The month is “worth” an 81, but she has history now and only half a month of new data arrived.

The payoff is the bad month. If the next cycle collapsed to a target of 20 with a full month of data, the score would fall to about 57 — a disaster costs roughly five points, not forty-two. Two good months pull it back, and by month 24 the same disaster would cost about 3.4. Hard to build, hard to lose, always recoverable.

guardrails

The edges

  • floorThe score is clamped at 8 — the blob is never fully deadhonest, not shaming
  • emptyNo data at all is the neutral 50, not zeroblank slate
  • earlyrate damps movement when little new data arrivedno special case
  • displayThe shown number eases toward the new onenever snaps
  • guardZero budget, zero days elapsed, non-positive planned savings, zero positive variancedivide by zero

Still open. Six questions the record leaves unanswered — whether the starting offset and the alpha floor survive contact with real scenarios, whether rate should count days or amounts, whether recovery should be slower than damage, whether 65/35 is the right balance, what to do with a committed charge that has not posted by mid-cycle, and whether evidence should decay after a long gap. They are listed in full in the record.