What This Is
I built a projection engine in Airtable to answer one question: given a set of rules describing how money moves between accounts, where does every account land month by month over the next several years? The interesting part was never the arithmetic. It was the architecture required to make a hand-authored rule set produce a forecast that was reliable and could be regenerated on demand.
The system is two separate Airtable bases, and the separation is deliberate. One base is the source of truth for account data and stays manually maintained. The second base is the projection engine. It pulls a read-only copy of the account data through a one-way sync and never writes back. That boundary is load-bearing. The engine can be wiped and rebuilt from scratch without any risk to the records it reads from.
The Conceptual Knot
The hard problem was circular. The amount of cash free to direct toward a target in a given month, whether that target is a debt account, an investment position, or a savings fund, depends on the state of every account that month. That state is itself the product of every rule applied in all the prior months. So the rules and the projection are mutually dependent. You cannot sit down and author the complete rule set in advance, because the correct rule for a later period depends on the projected balances the earlier periods produce.
There is no clean closed-form way to express that. A rule that allocates whatever is left after fixed obligations only resolves once the projection in front of it has already run. The shape of the problem is a bootstrap. Each period's rules are written against the model state the previous periods generated.
A second constraint sat underneath it. The generation pipeline runs in three stages, and the stages are not chained. They execute manually, in a fixed order, and the order is not optional. Run the balance-chaining stage before the rule-pricing stage and it carries stale numbers forward across the entire horizon, producing a forecast that looks complete and is quietly wrong.
How I Built It
I solved the circular dependency by making the authoring a loop. Set the rules for the nearest period. Run the model. Read the projected available cash it surfaces for the months ahead. Use that to author the next period's rules. Run again. Repeat, period by period, until the rules covered the full horizon out to the early 2030s. It was deliberately manual and iterative, and that was the point. Each pass fed the next with information that did not exist until the previous pass had run. Because every period got its own rules, a single allocation's evolution over time lives in the model as a series of time-bounded rules rather than one static figure.
Underneath the authoring loop is a three-stage executor, run in strict order.
Stage one builds the grid. A single script generates one row per modeled account per qualifying month, the skeleton the rest of the pipeline fills in. I designed it to be idempotent. It reads the rows that already exist, builds a set of composite keys, and only creates the pairs that are missing. A partial failure, or adding an account later, never corrupts the grid. Re-running only ever adds what is absent.
Stage two prices each row. The second script reads every active rule, indexes the rules by the account they target, and for each grid row sums the contributions from the rules whose effective window includes that month. The subtlety is frequency. A rule can fire one-time, bi-weekly, semi-monthly, monthly, quarterly, or annually, and all of them have to resolve to a single monthly-equivalent figure. Bi-weekly is the one that catches people. Twenty-six paychecks a year is not two a month. I annualize the cadence and re-spread it, so a bi-weekly rule contributes about 2.167 times its amount per month rather than two. Any single calendar month is an average, not a literal count.
Stage three chains the balances. The third script walks each account forward in time. It seeds a running balance from the account's synced starting value, then for every month sets the starting balance, applies growth or interest, and carries the ending balance into the next month. The math is per type. Cash and investment accounts treat a contribution as additive. Credit and debt accounts treat the same positive number as a payment that reduces the balance, so one rule means opposite things depending on what it targets. Growth rates needed careful parsing too, because the source stored them in two different formats, a decimal fraction in one table and a percentage string in another. The pipeline has to read each correctly or the projection silently drifts.
A few decisions held the whole thing together. The one-way sync kept the engine disposable and the source data safe. Gating generation to current and future months meant the model never wasted rows recomputing a settled past. A single flag on each account decided what entered the projection at all, which let closed accounts stay out of the forecast without erasing their history. And making every stage either idempotent or fully overwriting meant re-running the pipeline after any change was always safe, as long as the three stages ran in order.
What It Produces
The result is a regenerable forecast. Every modeled account carries a month-by-month balance chain across a ten-year grid, with each month rolling its own contributions, growth, and interest up into an annotated timeline. Testing a scenario is a matter of editing rules and re-running the three stages. The engine turns a question about strategy into a concrete, navigable projection.
What I take from it is mostly about engineering discipline under a deceptively simple surface. The arithmetic is trivial. The difficulty lived in the dependencies: a circular authoring problem solved by iteration, a pipeline whose stages had to run in a fixed order, and a handful of assumptions, like rate formats, sign conventions, and which accounts are even in scope, that fail silently rather than loudly. Building it well meant designing for re-runs, defending against the silent failures, and accepting that the right way to author the rules was a patient loop rather than a single clever pass.


