← Back to Site Map

📊 Leadership Dashboard

Standard Operating Procedure — Complete System Guide
Version 2.9 • 16 Apr 2026 • Owner: Kevin Brittain

⚡ Quick Reference

Data Source
Airtable — 9 tables
Base: appnqjDpqDniH3IRl
Key Tables
Transactions, Tenancies, Tenants, Rental Units, Costs, Accounts, Categories, Sub-Categories, Businesses
Authentication
Airtable PAT — stored in localStorage key _dlr_pat
Auto-Refresh
Every 15 minutes
(defers while user is active)
Cache
24-hour stale-while-revalidate
localStorage key _dlr_dashcache_v1
Owner
Kevin Brittain

🏗 1. System Architecture

🌐 Component Overview

The Leadership Dashboard is a client-side HTML/JavaScript application. It runs entirely in the browser, connecting directly to Airtable via its REST API and to Gmail via a deployed Google Apps Script web app. There is no back-end server.

📊
Airtable
Tables read and written via REST API + PAT
➡️
💻
Dashboard
Static HTML/JS
Client-side processing
⬅️
📧
Gmail
Apps Script web app
Invoice extraction

On load, the dashboard fetches 9 Airtable tables in parallel: Accounts, Fixed Costs, Tenancies, Transactions, Rental Units, Tenants, Categories, Sub-Categories, and Businesses. It also fires non-blocking fetches for invoices, Gmail sync, and Fintable status.

Those nine are not the whole list. The same load also reads Strategic Projects (tblHrpTMd5LNYn8v1, js/dashboard.js:185), Tasks and Prospects for the KPI compute (:487, :458), Team Members and AI Agents for the agent cards, and AI Recon Audit for the accuracy score. Opening the page also writes to Airtable, in two places: the Strategic KPI automation below, and the recon audit log in section 6.

🧮 Strategic KPI Automation (this writes to Airtable)

Some Strategic Projects carry their own KPI compute code. It is written by hand and stored in the KPI Compute Code field on the project record in Airtable. Opening the dashboard runs that code in your browser and saves the answer back to Airtable. The page is not read-only.

  1. loadDashboard() calls loadStrategicKpis() as its first action (js/dashboard.js:929), which reads the Strategic Projects table (:110, :185).
  2. Every open project that has compute code goes to runAutomatedKpis() (:514). Projects with a close date are skipped, so a closed quarter cannot drift away from its snapshot (:519).
  3. The code runs in the browser through new Function() (:429). A safety check blocks obvious dangerous wording first (:410), and the note beside it in the code is honest that this is a speed bump rather than a wall. Only put compute code in that field if you wrote it yourself.
  4. The answer is saved straight back onto the project record (:611-617): KPI Current, KPI Last Updated, KPI Last Updated By, and a KPI Detail JSON blob holding the workings, capped at about 95KB.
⚠️
Every load and every auto-refresh writes. The 15-minute smart refresh calls loadDashboard() again, so the write repeats all day while the tab is open. The save is fire-and-forget: if Airtable rejects it, the only sign is a line in the browser console (:618), and nothing on screen tells you it failed. If the compute itself fails, the project row shows a red Compute failed badge (:709) with the reason on hover.

🔑 2. Access & Authentication

🚪 Opening the Dashboard
  1. Open app.operationsdirector.co.uk in any browser.
  2. On first load, the login screen appears requesting your Airtable Personal Access Token (PAT).
  3. Enter your PAT and press Enter or click Connect. The dashboard loads all data from Airtable.
  4. Your PAT is saved to localStorage (key _dlr_pat) and persists across browser sessions until you log out or clear storage.
🔒
Persistent login: The PAT is stored in localStorage (not sessionStorage), so you stay logged in even after closing the browser. To log out, use the logout button or clear your browser’s local storage.
⚠️
Security: Your PAT is sent only to api.airtable.com. No third-party server is involved.

⚡ 3. Instant Load Cache

🔄 Stale-While-Revalidate

The dashboard uses a stale-while-revalidate caching strategy to provide instant page loads. On every successful data load, the full dataset is saved to localStorage under the key _dlr_dashcache_v1.

BehaviourWhat Happens
Cache hit (under 24 hours old) The dashboard renders immediately from cached data. A blue Refreshing… badge appears (with the cache age, e.g. “showing data from 3h ago”). A fresh fetch runs in the background; when complete, the display silently updates and the badge disappears.
Cache miss or older than 24 hours The loading overlay is shown while data fetches from scratch. No stale data is displayed.
Background refresh fails The badge turns red with “Couldn’t refresh — showing saved data”. The cached data remains visible.
Auth failure Cache is cleared and the login screen is shown.
💾
Cache age is displayed as a human-readable label: “just now”, “X min ago”, “Xh ago”, or “Xd ago”. The formatAge function rounds minutes, hours, and days. Cache is managed by loadDashCache, saveDashCache, and clearDashCache in js/dashboard.js.

📋 4. Platform Navigation

🗂 Sidebar Navigation

The Operations Hub uses a fixed left sidebar for navigation. Each page also has a standalone URL for deep-linking and sharing.

PagePurposeStandalone URL
Leadership DashboardFinancial overview, portfolio, cash flow, AI commentaryindex.html#overview
CFVsCash flow void detection, chase tracking, commentsindex.html#cfv
Accounts Payable VariableGmail invoices, AI matching, mark-as-paid, business assignmentindex.html#invoices
Profit & LossTransaction-based P&L reportingindex.html#pnl
Inbound CommsEmail triage, AI labels, follow-up managementfollow-up.html
Property ComplianceGSC, EICR, insurance status trackingcompliance.html
Site Map & LinksPage registry, version tracking, SOP sync statusindex.html#sitemap
🔄
Page switching: Clicking a sidebar item triggers switchTab(), which updates the URL hash and fires a data refresh for that page.
📱
Mobile: On screens ≤768px the sidebar collapses to a hamburger menu (☰). Tap outside to close it.

💷 5. Financial Overview

🏦 KPI Cards

The financial overview section shows five KPI cards. All figures are calculated from live Airtable data.

CardHow It’s CalculatedNotes
Opening Balance Sum of accGBP from Santander record (rec3LiEiifomEHlvy) + TNT Mgt Zempler record (recsR9QhRKYwgV8oP) Shown as a single £ total with per-account breakdown in subtitle
Monthly Income Two figures shown as a range: amber = In Payment only; green = In Payment + CFV Actioned. Only tenancies where tenant status is active are included. Click to expand — shows per-tenancy breakdown sorted by due day
Monthly Costs Sum of costExpected for all active fixed costs (where costInactive is not set) Click to expand — shows per-cost breakdown sorted by due day
Monthly Operating Cushion Monthly income minus monthly property fixed costs (active costs whose Business is Real Estate). Personal and Operations Director costs are not subtracted; they stay on the Monthly Costs card. Two figures: amber = In Payment income minus property costs; green = (In Payment + CFV Actioned) minus property costs Range reflects uncertainty from CFV Actioned tenancies. Distinct from gross profit (revenue − COGS) and operating profit (revenue − fixed − variable costs).
Operating Cushion Margin Operating Cushion ÷ Monthly Income × 100, shown as a % range (amber to green) Calculated independently for low and high operating cushion figures
ℹ️
Expandable cards: Click any KPI card to expand it and see the full breakdown list. Cards use the expandableCard() helper from js/shared.js. The chevron (▶) rotates when open.
💰 Budget Targets (from js/config.js)
ConstantValuePurpose
WAGES_TARGET_GBP£650/monthWages spend budget
MAINT_TARGET_GBP£1,700/monthMaintenance spend budget
UTILITIES_TARGET_GBP£200/monthUtilities budget
COMPLIANCE_TARGET_GBP£1,000/month (£250 from 1 January 2027)Compliance certificates budget
CFV_TARGET_GBP£750/monthNon-payment allowance
CLEAR_PROFIT_TARGET£10,000/monthThe income floor: your income from property after all variable costs
variableCostReserve£4,300/monthThe property variable budget: RE_VARIABLE_BUDGET_GBP, the sum of the five lines above
requiredOperatingCushion£14,300/monthThe cushion the income floor needs: the floor + the variable budget (calculated). A floor, not the Q4 target
🏠 Q4 2026 Real Estate KPIs

Eight cards, worked out on every load. Nobody types a number. The rules are in js/re-kpis.js (tests: tests/re-kpis.test.js); the named units, tenants and targets are RE_Q4 in js/config.js.

CardHow it is worked out
Income from property (plan)Property cushion (plan) less the £4,300 variable budget.
Income from property (cash)Property cushion (cash) less every other property payment in the same month (payments tagged Real Estate with no cost record: wages, maintenance, utilities, compliance, other).
Property cushion (plan)Rent of tenancies that read In Payment, less active costs whose Business is Real Estate.
Property cushion (cash)Rental Income received, less payments tagged Real Estate that are linked to a cost record. The last full calendar month, with the rolling 31 days and the Q3 average beside it. A rolling 31 days can take in two of a monthly payment that lands at both ends (on 14 Oct 2026, two mortgages paid on 14 Sep and again on 12 and 13 Oct, £1,411), so the headline is the last full month (Kevin, 14 Oct 2026). Both cash cards are held to the same 31 December marker as their plan card: the gap between plan and cash is the leak.
Named units with a signed tenant inEach named unit counts when a tenancy that has started and not ended is linked to it. One unit is set to count only once someone other than the tenant moving out of it lives there.
New rent in payment from the named tenantsRent of the named tenants and units, counted only while the tenancy reads In Payment. A replacement tenant anywhere else is never counted.
Personal net cash flowThe Wealth page's own figure (buildMonthlyCashflow) for the last full month, with the 3-month average beside it.
Self-managed properties fully compliantA property counts when gas (or the No Gas tick box on the property), the electrical inspection and the insurance are each in date and have a file attached.

When a card stops updating. A card shows a red Not updating badge and the reason when its data did not load, the bank feed is more than 4 days old, no rent is in the window, or a named unit or tenant is missing. An amber Check badge means the figure will move (payments not yet filed) or needs a look; the reason is written beside the badge. The bar on a card always measures progress to the committed target, and the card says so. The health bar check Q4 real estate KPIs updating turns red with it. The three project KPIs are also saved to their projects; the daily data check q4-real-estate-kpis-are-current fails if any of them has not been saved for 2 days.

🔍 6. Unreconciled Transactions & AI Accuracy

📋 Reconciliation Bar

Above the financial KPI cards, the reconBar section displays two cards side by side:

  1. Unreconciled Transactions — expandable card showing the count and list of all transactions where txReconciled is false and txAccountAlias matches one of the monitored accounts (Santander or TNT Mgt Zempler). Each row shows date, label, and amount.
  2. Run Reconciliation button — inside the expanded card, a blue Run Reconciliation button calls triggerReconciliation(this), which runs the AI matching engine from js/reconciliation.js. A status span (id="reconStatus") shows progress.
  3. AI Reconciliation Accuracy — shown as a separate KPI card (only rendered if the accuracy log has at least one entry). Displays the percentage of reconciliation matches confirmed as accurate, colour-coded: ≥90% green, 75–89% amber, <75% red.
💾
The accuracy log is stored in the Airtable AI Recon Audit table (TABLES.reconAudit), one row per approved transaction where the AI made a suggestion. getReconAccuracyStats() returns a cached copy for an instant paint; refreshReconAccuracyStats() runs in the background on every dashboard load and pulls rows from the last 31 days only (server-side filterByFormula, so the payload stays small). Each write calls pruneStaleAudit() which deletes rows older than 35 days — this keeps the table ~1 month of data so reads never slow down. On first load after this change ships, migrateLocalReconLog() copies any historical entries from the legacy _recon_accuracy_log localStorage key into Airtable and then clears it.

🏠 7. Portfolio & Tenancy Metrics

🏘 Portfolio Metrics

From the Rental Units table (tblM3mZCR5kiEdWMj):

  • Total Rental Units: Count of all records. Expandable: grouped by property name (via unitPropName lookup field).
  • Occupied Units: Units where unitStatus does NOT contain “void”.
  • Void Units: Units where unitStatus contains “void” (case-insensitive). Expandable: shows unit display name.
  • Occupancy Rate: Occupied ÷ Total × 100%. Progress bar coloured green (≥90%), amber (≥80%), or red (<80%).
👥 Tenancy Status Breakdown

From the Tenancies table, filtered to active tenant status (tenStatus + isTenancyActive):

  • In Payment — tenPayStatus = In Payment. Sorted by tenDueDay asc. Expandable with surname, ref, rent, due day.
  • CFV — tenPayStatus = CFV. Amber text.
  • CFV Actioned — tenPayStatus = CFV Actioned. Amber text.
  • Paid Tenancy Rate: In Payment ÷ All Active × 100%. Green ≥80%, amber ≥60%, red <60%.
⚠️ CFV Alert Banner

After the tenancy cards, the dashboard checks for potential CFVs not yet flagged in Airtable. The logic:

  1. For each tenancy with tenPayStatus = “In Payment” and active tenant status and rent > 0:
  2. Calculate daysOver = days since the current month’s due day (0 if not yet due).
  3. If daysOver ≥ CFV_TOLERANCE_DAYS (= 2 days) AND tenPaidThisMonth is falsy AND no cfv_dismissed_[tenancyId] key in localStorage…
  4. …the tenancy counts as a potential CFV.

If any potential CFVs are detected, an amber banner is inserted below the tenancy cards: “N potential CFVs detected — click to review”. Clicking the banner calls switchTab('cfv') to navigate directly to the CFV tab.

⚠️
A dismissed CFV (cfv_dismissed_[id] key set in localStorage) is excluded from the banner count. The CFV tab itself has separate dismiss logic. To clear a dismissal, remove the key from localStorage or wait for it to be cleared on “In Payment” status reload.

📈 8. 31-Day Operational Metrics

🎯 Budget vs Actual (Last 31 Days)

This section compares actual spending over the last 31 days against monthly budget targets. Transactions are filtered to the 31-day window using txDate and matched to sub-categories by sub-category record IDs in the REC constants.

KPI CardData SourceBudgetTraffic Light
Rental Income (31d) Transactions with sub-category = REC.subRentalInc (recI8yCstyDP1Nd4b) No budget target (shown as actual) Green (income)
Maintenance Spend (31d) Transactions with sub-category = REC.subMaint (recWomXYQ3XTgMdrr). Absolute value of txReportAmount. £1,700 (MAINT_TARGET_GBP) Green < budget, amber ≤ budget × 1.1, red > budget × 1.1
Wages Spend (31d) Transactions with sub-category in REC.subOpexLabour or REC.subCOGSLabour. Absolute value. £650 (WAGES_TARGET_GBP) Same traffic light logic as maintenance
CFV Exposure Sum of tenRent for all tenancies with status CFV or CFV Actioned (active tenants only) £750 (CFV_TARGET_GBP) Same traffic light logic
Cushion for the £10,000 income floor Property cushion from In Payment tenancies (their rent less property fixed costs) vs the £14,300 the income floor needs. Rent from CFV Actioned tenancies is not counted: it is not being paid. This is a floor, not the Q4 target, which sits on the Property cushion (plan) card. £14,300 (requiredOperatingCushion: the £10,000 floor + the £4,300 variable budget) Green if at or above the floor, amber if below
📊
Progress bars with target marker: Each budget card shows a coloured progress bar. A vertical black line marks the budget target at its proportional position (budget ÷ 2×budget = 50%). The bar extends to represent actual spend against a scale of 0–2×budget. This makes it easy to see how far over or under budget you are.
💡
Income floor breakdown: Expanding the card shows: wages budget (£650), maintenance budget (£1,700), utilities budget (£200), compliance budget (£1,000), non-payment allowance (£750), variable budget (£4,300), income floor (£10,000), the cushion the floor needs (£14,300), the cushion now from In Payment tenancies, and the cushion if the CFV Actioned tenancies pay too. Below £14,300, your income from property is under £10,000.

📅 9. 31-Day Cash Flow Forecast

📈 Forecast Logic

Built by buildCashFlow() in js/cashflow.js. Projects daily inflows and outflows for 31 days from today’s opening balance.

🏠Active TenanciesRent schedules
➡️
📅Project Due Dates+Fixed costs
➡️
🔍Match vs ReconciledAvoid double-count
➡️
📊Daily Forecast31-day view
A

Income Projection

Inflows
  1. Active tenancies (In Payment + CFV Actioned, active tenant status) are retrieved with their tenRent, tenDueDay, and tenPayFreq.
  2. Tenants whose tenantPayType contains “Universal Credit” are flagged as UC via the tenancyIsUC map.
  3. For each day in the 31-day window, the forecast places rent on the appropriate due date based on payment frequency.
  4. Projected income is cross-referenced against reconciled transactions to avoid counting payments that have already arrived.
B

Cost Projection

Outflows
  1. Active fixed costs (tblx5kvhzNEI5TFlS) are loaded with costDueDay, costFrequency, and costDueDateNext.
  2. Each cost’s next due date is calculated from its frequency (weekly, monthly, quarterly, annually).
  3. Costs falling within the 31-day window are placed on the appropriate day.
  4. Projected costs are cross-referenced against reconciled transactions to prevent double-counting.
📊 Forecast Table Columns

The table has seven columns. There is no Net column (js/cashflow.js:719-726).

ColumnDescription
DateCalendar date for the row
OpeningBalance at the start of the day, with any earlier recommended withdrawals already taken off
InMoney in for the day, leaving out anything already reconciled or unticked
OutMoney out for the day, including the weekly commitments described below
ClosingOpening + In − Out − Withdraw. Carries forward as the next day’s Opening
WithdrawCash the withdrawal advisor says is safe to take that day
AccountWhich accounts the day’s items sit on

All the money columns are read from the same worked-through series (waProjected, js/cashflow.js:712-714), not from the raw Airtable rows, so each row reconciles exactly: Opening + In − Out − Withdraw = Closing. The table prints that same line under its totals row (:770).

💷 Withdrawal Advisor & Weekly Commitments

The forecast is not a plain projection of Airtable data. Two things are applied on top of it before the table is drawn.

  1. Weekly commitments. Wages of £150 (Roy, the £650 a month wages budget) are added to the Out column every Friday by default (waDefaultCommitments()). It is not held in Airtable. It lives in this browser only, under the localStorage key _wa_settings (:1192). Edit the amounts and days in the controls above the table. A different browser, or a cleared cache, falls back to those defaults.
  2. Recommended withdrawals. The advisor walks the 31 days and, at least three days apart, marks an amount that is safe to take out, in £50 steps (:517-552). It only takes what still leaves the safety floor intact on that day and on every day after it, after holding back the rest of the variable budget: £3,650 a month for maintenance, compliance, utilities and the non-payment allowance, spread evenly over the 31 days (wages are already in the weekly commitments). That money stays in the Closing column; only the withdrawals are smaller (Kevin, 14 Oct 2026: it read £23,950 when £20,300 left the budget covered). Once marked, the withdrawal is subtracted from every later Opening and Closing (:546-549), so the Closing balance you are reading already assumes you took that cash. The green Safe to withdraw (31-day cycle) figure above the table is the total of them (:629).

Where the safety floor comes from: the largest single expected payment in, times 30%, scaled by how late payments have actually been running (analysePaymentLag(), :502-508), rounded up to the nearest £50 and never below £500. You can type over it in the floor box, which is also stored in _wa_settings.

ℹ️
Why the Opening Balance card and row 1 can disagree. The Opening Balance KPI card always shows the live Airtable figure (:429), split between Santander and TNT Zempler when the two parts genuinely add up to the total (:519-524). Row 1 of the table shows the balance box above the table, which you can type over (:456). Once edited, your figure drives the whole forecast. A mismatch warning only appears when the two are more than £200 apart (:579), so a smaller gap passes with nothing said.

🤖 10. AI Commentary & Credit Card Plan

💬 AI Commentary Topics

The AI commentary section (id="aiCommentary") is generated in renderDashboard() using live KPI data. It covers six titled sections:

  • Financial Health: Confirmed monthly income (In Payment), best-case total (+ CFV Actioned), fixed costs, gross margin range, and whether the upper range is healthy (≥40%).
  • Income Floor: The cushion from In Payment tenancies vs the £14,300 the £10,000 income floor needs, how far above or below it is, the figure if the CFV Actioned tenancies pay too, and the Q4 target.
  • Operational Performance (31-Day): Actual rental income, maintenance status (vs the £1,700 budget), wages status (vs the £650 budget).
  • Occupancy & Voids: Occupancy rate, void count, estimated cost per void per month (monthly income ÷ occupied units), and ROI of filling voids.
  • CFV Risk: CFV exposure vs the £750 non-payment allowance, count of unactioned CFVs and value at risk.
  • Quick Wins: Prioritised action list: fill voids (1), action CFVs (2), reduce maintenance if over budget (3), plus a note about mortgage clusters on days 1–6.
💳 Credit Card Payment Strategy

Below the AI commentary the dashboard works out how to split one payment across the cards. It is a single-payment allocator, not a weekly schedule. There is no Friday timetable and no £750 buffer any more. The card list is built inline in renderDashboard() (js/dashboard.js:1950-1990) and the split is redrawn by recalcCCStrategy() (:2046).

CardWhat counts as owedPayment due day
American ExpressThe balance if it is positive, otherwise nothing (:1958)28th
Santander Credit CardThe balance if it is positive, otherwise nothing (:1959). The old £5,500 limit-minus-available sum is gone.14th
Lloyds Credit CardThe size of the balance ignoring the minus sign (:1957)21st

Any card with nothing owed drops out of the list entirely (:1966).

How the split is worked out

  1. The Payment box starts at what can go to the cards each month: personal net cash flow (Wealth page rules, 3-month average) times your Debt pot share (Income Buckets, 70% in October 2026), rounded down. You can type any amount over it and the split redraws as you type.
  2. Cards are sorted by estimated APR, dearest first (:2049). This is the avalanche method: pay the minimum on everything, then put all the rest against the dearest card.
  3. Every card gets its minimum first (:2054). If your figure is below the total of the minimums, the total of the minimums is used instead (:2052).
  4. Whatever is left goes to the dearest card until it is cleared, then to the next (:2055-2059).
  5. The summary line estimates next month’s interest on whatever is still owed after the payment (:2077-2085).
⚠️
The APR is a guess from your own transactions, not a rate from the bank. The dashboard reads the last three months of transactions on each card and picks out any whose description contains the word INTEREST (:1978-1988). It averages those, divides by the balance and multiplies by twelve. If it finds none, the card shows 0% (no interest detected) and sorts last, even if it is really the dearest card you hold. The minimum payment follows the same fork: 1% of the balance plus the average interest where interest was found, otherwise a flat 2.5% of the balance, and never less than £25 (:1989).
💡
“Months to clear” runs on your Debt pot. Under the cash flow forecast, the Credit Card Balances panel divides each balance by what goes to the cards each month: personal net cash flow (the Wealth page's figure, 3-month average, after your living costs) times the Debt pot share (ReKpis.debtPaydown, drawn by renderCreditCardSummary()). Until 14 Oct 2026 it divided by the property surplus before the variable budget and your living costs, and said £24,165 of cards would clear in about 2 months. If the Wealth figures or the Debt pot share cannot be read, the panel says so and gives no time.

🔧 11. Forecast What-If Tick Boxes

🧮 Including and excluding forecast items

The standalone Balance Calculator has gone, along with its own opening balance box and its Select All, Deselect All, Inflows Only and Outflows Only buttons. The what-if tool now sits inside the cash flow forecast itself: open any day in the table and every expected item has a tick box beside it (js/cashflow.js:670-673 for money in, :681-684 for money out).

  1. Ticked means the item is still expected and counts towards the forecast. Untick it to take it out.
  2. Unticking redraws the table, the totals and the chart straight away (toggleCFExclusion(), :1148).
  3. Items already reconciled in Airtable are shown struck through with a Reconciled badge and have no tick box. They are already in the bank, so there is nothing to model.
  4. The amber dashed What-If line on the chart shows the balance with your unticked items removed, and also takes off the wages budget spread evenly across the 31 days (buildWhatIfLine(), :1159-1179). It only appears once something is unticked.
⚠️
Unticks survive closing the browser. They are saved in localStorage under the key _cf_exclusions (:1131, :1155), not in sessionStorage, so a reload does not clear them. An untick is only dropped once its date has passed (:1137-1140). Anything you untick today keeps real income or real costs out of the forecast, quietly, for as long as its date is still in the future. If the forecast looks too good or too bad, open the days and look for unticked rows, or clear the _cf_exclusions key.

🔍 12. Reconciliation Matching Logic

🚨
Critical system: The reconciliation engine prevents double-counting of transactions already settled in the bank. It is triggered by the Run Reconciliation button in the Unreconciled Transactions card.
📆 Date Matching Window
ParameterValuePurpose
Look-back5 days before expected dateCatches early payments
Look-ahead5 days after expected dateCatches late payments
↑

Inflow Matching

Income

Priority 1: Linked tenancy

If the transaction’s Tenancy link points at this tenancy, that settles it and nothing else is checked. A transaction linked to a different tenancy is barred from clearing this one (js/cashflow.js:132-142, :155).

Priority 2: Strong match

Two or more keywords from the tenant name or reference appear in the transaction description. Highest confidence.

Priority 2: Solid match

One keyword plus an amount within 5p of the expected rent.

Priority 2: Acceptable match

One keyword plus an amount within 30% of expected rent, which covers letting agent deductions.

There is no amount-only match any more. Every path needs at least one word in common (:161-174). A matching amount on its own is not evidence the same tenant paid: several tenancies share the same rent figure, and matching on amount was clearing the wrong tenancy’s forecast (:128-131, :175).

↓

Outflow Matching

Costs

All Three Conditions Required

  • Keyword: At least one keyword from cost name appears in description
  • Amount: Within 33% of expected cost, or within £1 (whichever is more generous)
  • Sub-category: Transaction’s Airtable sub-category is compatible with the cost’s category

🔄 13. Smart Refresh System

⏰ Refresh Configuration
ParameterValueDescription
Refresh IntervalREFRESH_INTERVAL = 15 minutesTimer set after each successful data load via setInterval(smartRefresh, REFRESH_INTERVAL)
Idle ThresholdIDLE_THRESHOLD = 15 minutesUser must be idle for 15 minutes before an auto-refresh fires
Activity Trackingclick, keydown, input eventsEach event updates lastUserActivity timestamp
Deferred RefreshIf user is activeSets refreshPending = true and schedules an idle check; fires as soon as user has been idle 15 min
🛡️
Non-disruptive: If you are actively ticking items in the cash flow forecast, reviewing invoices, or typing comments, the refresh defers. It waits until you have been idle for 15 minutes, then fires loadDashboard().

⚙️ 14. Google Apps Script Setup

🚀 Deployment
  1. Open Google Apps Script at script.google.com and open the existing project (or create new).
  2. The script reads Gmail threads labelled 3. to pay, extracts invoice fields (payee, amount, due date, reference), and handles markPaid actions.
  3. Deploy as a Web app: Execute as Me, Who has access: Anyone.
  4. Copy the Web app URL and paste into GMAIL_SCRIPT_URL in js/config.js.
  5. Verify by loading the Invoices tab — invoices should appear with a live badge.
📧
The current deployed URL is already in js/config.js. Only update it if you re-deploy the script to a new URL.

🛠️ 15. Troubleshooting

🔧 General Issues
Dashboard shows login screen on every visit
Your PAT should persist in localStorage. If it keeps disappearing, check that your browser is not set to clear storage on close. Enter the PAT again and it will persist across sessions.
Blue “Refreshing…” badge stays on screen
This means the background fetch is still running or failed silently. If it turns red (“Couldn’t refresh — showing saved data”), check your internet connection or Airtable API status. Reload the page to force a fresh attempt.
Monthly income shows £0 or incorrect figure
Check that tenancies in Airtable have tenPayStatus = “In Payment” or “CFV Actioned”, and that their linked Tenants record has an active status. Both conditions must be met for a tenancy to appear in income calculations.
Required gross profit target appears as wrong value
The target is computed as CLEAR_PROFIT_TARGET + RE_VARIABLE_BUDGET_GBP = £10,000 + £4,300 = £14,300. If the displayed value differs, check js/config.js for the budget constants.
A reconciled payment still appears in the forecast
Three things can stop it clearing. Date: the matching engine uses a ±5-day window, so a payment more than 5 days early or late will not match. Wording: there must be at least one word in common between the tenant name or reference and the transaction description. A matching amount on its own is never enough (js/cashflow.js:161-174). Links: if the transaction is linked to a different tenancy in Airtable, it is barred from clearing this one (:155). Check the date, the description and the Tenancy link on the transaction.
CFV alert banner shows but I know the tenant has paid
The banner is driven by tenPaidThisMonth (a formula field in Airtable) and the 2-day tolerance. If the formula is not updating, refresh Airtable or check the formula definition. The banner disappears as soon as tenPaidThisMonth is truthy or the payment status moves off “In Payment”.
Refresh interrupts work
The smart refresh defers until 15 minutes of idle time. Ensure you have clicked, typed, or interacted with the page within the last 15 minutes to keep it active. If the issue persists, check browser console for JavaScript errors in markUserActive().
📖
Other SOPs: For Invoices see sop-invoices.html. For CFVs see sop-cfvs.html. All SOPs are listed on the Site Map.

🤖 16. AI Team & Labour Saved

Since 9 Oct 2026 every AI card sits in its own AI Team section, below the 31-Day Operational Metrics. Since 15 Oct 2026 it opens with one panel that replaced the Agent Work, Defects, Bottleneck and Fix of a Fix cards. The same day it was made simpler, so you can see at a glance whether the AI team works and whether it improves week on week. The Mac works all of it out (scripts/ai_team_health.py, the Estate Status row ai-team-verdict, every 10 minutes); the page only draws it.

PartWhat it shows
The answer (top right)Green "Working well", amber "Getting better", amber "Not getting better", red "Getting worse" or grey "Can't tell", with one line saying why.
Things needing a fixOne number: the real problems right now, against last week's number. "At least" when a check could not be read, and the check is named.
Weekly barsOne bar a week (Monday to Sunday), oldest on the left, up to 8 weeks. Shorter is better. Until there are two weeks it says when the bars start (15 Oct 2026).
Three lightsWork is flowing, Something is broken (or Nothing is broken), It can keep going. A red or amber light names what is wrong in a few words. Tap a light to open its checks.

The checks behind each light (tap a check to see its rule, the tasks or jobs behind it, and where the number came from):

LightChecks
Work is flowingEmails are getting cleared · Responses are getting written · Everything's moving · No bottlenecks · Approved work gets carried out
Something is brokenEvery agent is working · No job is broken · No bugs or errors · The data is accurate · Nothing's getting missed
It can keep goingAgents are on top of their work · Your queue is under control · Drafts are right first time · Enough allowance to finish the week

What counts as a thing needing a fix: tasks stuck with nothing blocking them, every blocked task (a stuck task that gets blocked still counts, so the number never falls with nothing fixed), approved work not done in 24 hours, idle agents, failing or blocked jobs, wrong data checks, things missed, emails over 2 days old, email tasks waiting for a draft, approval cards over 2 days old, and 1 for any Claude allowance pause this week. The open code-problem backlog and the two rates (work done against new, drafts right first time) never add to it; they turn their light amber or red instead. A task, job or email that two checks both count is counted once.

The answer, in order: red "Getting worse" when a light is red that was not red at the end of last week (or whose colour last week is not on record), or there are more things to fix than last week; grey "Can't tell" when more than two checks cannot be read or the number is "at least"; green "Working well" when no light is red, no light got worse since last week, every check was read and there are 5 things or fewer; grey "Can't tell" when there is no number from last week yet; amber "Getting better" when there are fewer things than last week and no light got worse; amber "Not getting better" otherwise. A red light always shows red and is named under the answer, even when the number falls.

Trust guards: a check whose source cannot be read, is missing or is out of date is grey, "Can't tell"; it is never shown green, and the number becomes "at least". A check that read red and cannot be read now keeps its light red. "Everything's moving" leaves out blocked tasks, which "No bottlenecks" counts, so one task is never counted twice. An answer the Mac worked out more than two hours ago turns the answer, the lights and the bars grey, "Not updated since …", and a row the Mac could not build shows its reason instead of its last answer. The health bar's "AI team verdict read from the Mac" check says the same. The old cards' numbers (the agent work split, the defect split, blocks by kind and the fix-of-a-fix rate) sit inside the checks they explain.

Below the panel, under the heading North star: how much work AI does, two cards measure progress to getting up to 90% of repeatable operational work done by AI agents. The two agent cards follow under The agents and your approvals.

CardWhat it shows
AI AgentsHow many agents are live and testing, and how much of their work is waiting for an approval.
Agent ApprovalsWork waiting for a yes right now, plus each agent's accuracy record per type of work.
Work Done by AIThe share of completed work (measured in time, not task count) done by AI over the last 30 days. Target: 90% of repeatable operational work.
AI Time & Money SavedThe same work expressed as hours handed to AI and the labour cost that saved, over the last 30 days and since AI go-live (1 Jun 2026). Includes a "doing the work of X of a full-time person" line and a trend against the 30 days before.

What counts as AI work (both cards use the same rules): a task completed with an AI agent on the Team Member field, or work an agent prepared that was approved first time. Work that was sent back for changes never counts as AI work, whatever the links say, because a human had to redo it.

How the money figure is worked out: every completed task carries a time estimate (how long a person would need). AI hours are those estimates summed for AI work, then valued at £17.50 per hour: the UK office admin wage plus employer National Insurance and pension. The rate and the go-live date live in js/config.js (AI_LABOUR_RATE_GBP_PER_HOUR, AI_WORK_EPOCH), so changing them is one edit. Figures are whole pounds; pence on an estimate would be false precision.

Trust guards: both cards publish the share of completed tasks carrying a time estimate and warn below 80% (a task without an estimate is invisible to the sums). An empty query shows "no data", never a fake 0% or £0. The health bar's "AI Time & Money Saved card computed" check confirms the card loaded on every sync.

📝 17. Changelog

VersionDateChanges
2.151 15 Oct 2026 The AI Team panel made simpler: one answer, one number ("Things needing a fix") against last week, weekly bars and three lights (Work is flowing, Something is broken, It can keep going). The fourteen checks open from the lights, unchanged. A red light always shows, even when the number falls.
2.150 15 Oct 2026 The AI Team section opens with one "Is the AI team healthy?" panel: fourteen questions in three groups, each with a colour, a short answer and last week's number, under one verdict. It replaced the Agent Work, Defects, Bottleneck and Fix of a Fix cards; their numbers moved inside the lines. A line that cannot be read is grey and named.
2.147 14 Oct 2026 Money figures audit against Airtable. Four figures corrected: the operating cushion card now measures In Payment tenancies against the £14,300 income floor (it counted CFV Actioned rent and called the floor a target); the cash cushion and cash income cards headline the last full month (a rolling 31 days counted two mortgages twice); Safe to withdraw holds back the £3,650 of the variable budget not already in wages; credit card “months to clear” runs on personal net cash flow times the Debt pot share. Stale wages, budget and allowance figures in this guide corrected.
2.140 9 Oct 2026 New AI Team section: Agent Work, Defects, Bottleneck and Fix of a Fix cards, with the four existing AI cards moved into it from the 31-Day Operational Metrics.
2.94 3 Sep 2026 Documentation-drift corrections. Added Strategic KPI Automation (the dashboard runs compute code held in Airtable and writes KPI values back on every load and every refresh) and corrected the list of tables touched. Rewrote the credit card section against the live avalanche allocator, including the estimated APR and the best-case “months to clear”. Replaced the forecast column table with the seven real columns and added the Withdrawal Advisor and weekly commitments. Replaced the retired Balance Calculator with the in-row what-if tick boxes and corrected the storage claim (localStorage, not sessionStorage). Removed the retired amount-only inflow match and added the linked-tenancy priority.
2.93 28 Aug 2026 Added AI Workforce & Labour Saved section covering the four agent cards, including the new AI Time & Money Saved card (hours + £ saved, 30-day and since go-live, FTE framing, trend).
2.9 16 Apr 2026 Updated budget values (wages £1,500 not £3,000; required GP £16,000 not £17,500). Added: stale-while-revalidate cache, income/profit range display, AI Reconciliation Accuracy card, Run Reconciliation button, 31-Day Operational Metrics section, CFV alert banner, budget progress bars with target markers, credit card repayment plan detail, 9 Airtable tables fetched. Corrected: auth stored in localStorage (not sessionStorage).
2.3 12 Apr 2026 Added Platform Navigation, Balance Calculator, Smart Refresh, mobile responsive, Google Apps Script setup sections.
2.0 Mar 2026 Restructured into modular JS files. Sidebar navigation replaces horizontal tabs.