← 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 fixed costs. Two figures: amber = In Payment income minus costs; green = (In Payment + CFV Actioned) minus 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
MAINT_TARGET_GBP£1,000/monthMaintenance spend budget
WAGES_TARGET_GBP£1,500/monthWages spend budget
CFV_TARGET_GBP£1,500/monthCFV exposure allowance
CLEAR_PROFIT_TARGET£10,000/monthTarget clear profit after all variable costs
variableCostReserve£4,000/monthSum of maintenance + wages + CFV budgets (calculated)
requiredOperatingCushion£14,000/monthClear profit target + variable cost reserve (calculated)

🔍 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 PaymenttenPayStatus = In Payment. Sorted by tenDueDay asc. Expandable with surname, ref, rent, due day.
  • CFVtenPayStatus = CFV. Amber text.
  • CFV ActionedtenPayStatus = 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,000 (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. £1,500 (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) £1,500 (CFV_TARGET_GBP) Same traffic light logic
Target Operating Cushion Best-case operating cushion (In Payment + CFV Actioned income minus fixed costs) vs required £14,000 £14,000 (requiredOperatingCushion) Green if actual ≥ target, 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.
💡
Target Operating Cushion breakdown: Expanding the Target Operating Cushion card shows: maintenance budget (£1,000), wages budget (£1,500), CFV allowance (£1,500), variable cost reserve (£4,000), clear profit target (£10,000), and required operating cushion (£14,000). The operating cushion must be large enough to absorb all variable costs and still leave the clear profit target behind — this becomes the operating profit.

📅 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 £330 and a Top-up of £140 are added to the Out column every Friday by default (js/cashflow.js:459-465, applied at :491). That is £470 a week, roughly £2,000 a month, and 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. 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%).
  • Profit Targets: Current best-case gross profit vs the £14,000 required target. If on track, shows surplus; if off track, shows shortfall and % achieved.
  • Operational Performance (31-Day): Actual rental income, maintenance status (vs £1,000 budget), wages status (vs £1,500 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 £1,500 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 the low operating cushion figure, rounded down (:1991). 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” is a best case. Under the cash flow forecast, the Credit Card Balances panel divides each balance by a monthly surplus (js/cashflow.js:914-935). That surplus is the best-case 31-day net change, which counts fixed costs only. Maintenance, wages, the CFV reserve and any cash you withdraw are not in it, and it assumes the same 31 days repeat every month. Treat the number of months as the floor, not the plan.

🔧 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 + MAINT_TARGET_GBP + WAGES_TARGET_GBP + CFV_TARGET_GBP = £10,000 + £1,000 + £1,500 + £1,500 = £14,000. 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 Workforce & Labour Saved

Four cards at the bottom of the KPI grid track the north star: getting up to 90% of repeatable operational work done by AI agents. Two of them measure the outcome.

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.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.