Skip to content

Finance Tracker Automation — Technical Documentation

Purpose: Complete context for a developer or AI assistant to understand, recreate, or extend the Finance Tracker. Reflects the actual implemented state of the system.


Table of Contents

  1. System Overview
  2. Architecture
  3. Google Drive Setup
  4. Google Sheets Setup
  5. Google Apps Script
  6. iOS Shortcuts Overview
  7. Data Flow Details
  8. Configuration Reference
  9. Known Limitations & Design Decisions

1. System Overview

This system captures financial transactions from three iPhone sources and records them in a Google Spreadsheet.

SourceShortcutInput type
Bank SMS notificationSMS Automation (automatic)Text — raw SMS body
Bank app screenshotLog Transaction v2 (Share Sheet)Image — PNG screenshot
Manual entryInput Transaction (Home Screen icon)Text — constructed from user prompts

For each transaction the system records:

  • Date and time
  • Type (Income or Expense)
  • Amount
  • Item name (merchant, biller, sender)
  • Category
  • Split By (how many people share the bill)
  • My Share (computed: Amount ÷ Split By)

2. Architecture

High-level flow

mermaid
flowchart TD
    A1["SMS Automation (auto)\nSenders: Rogers Credit Card\nTransaction / Bemn Lam\nKeyword: 'Details:'"] -->|SMS body text| D
    A2["Log Transaction v2\n(Share Sheet)\nBank app screenshot"] -->|PNG image| D
    A3["Input Transaction\n(Home Screen icon)\nAsk: amount + split + details"] -->|Constructed text| D

    D["iOS Shortcut\nBase64 encode input\nAsk split count\nPOST JSON to Web App"]

    D -->|filename, mimeType, data, splitBy| E

    E["Apps Script — doPost\nDecode base64\nEmbed splitBy in filename\nSave to Drive Inbox\nReturn immediately ~1s"]

    E --> F["Google Drive\nFinance/Inbox/"]

    G["Apps Script — processInboxFiles\n5-minute time-based trigger"]
    G -->|reads new files| F
    G -->|text or image| H["Gemini Flash API\nExtract: date, type, amount\nitem, category"]
    H -->|JSON entries| G
    G -->|one row per entry| I["Google Sheets — tracker"]
    G -->|archive| J["Google Drive\nFinance/Processed/"]

Processing mode: deferred

doPost saves the file and returns in ~1 second. The 5-minute time-based trigger handles Gemini parsing and sheet writing. Rows appear within 0–5 minutes of the shortcut completing.

Component responsibilities

ComponentRole
SMS AutomationFires on matching SMS, asks split, encodes SMS text, POSTs
Log Transaction v2Share Sheet entry for screenshots, asks split, encodes image, POSTs
Input TransactionManual entry, prompts amount/split/details, builds text string, POSTs
doPostDecodes base64, embeds splitBy in filename, saves to Drive, returns immediately
processInboxFilesPolls inbox every 5 min, routes files to Gemini, writes rows, archives files
analyzeWithGeminiSends text or image to Gemini API, parses JSON response, applies date fallback
resolveFallbackDateDate fallback chain: Gemini → filename pattern → 1970-01-01
appendRowWrites one row, sets =C/F formula for My Share, color-codes Type cell
Google DriveStaging inbox and processed archive
Google SheetsPermanent transaction ledger

3. Google Drive Setup

Folder structure

My Drive/
└── Finance/
    ├── Inbox/        ← uploaded files land here
    └── Processed/    ← files archived here after processing

Steps

  1. Create folder Finance in the root of Google Drive
  2. Create subfolders Inbox and Processed inside Finance
  3. Note the Folder ID of each from the browser URL: https://drive.google.com/drive/folders/FOLDER_ID_HERE
  4. Paste both IDs into the Apps Script constants

4. Google Sheets Setup

Create the spreadsheet

  1. Inside Finance/, create a new Google Sheet named tracker
  2. Open it and go to Extensions → Apps Script to create a bound script
  3. The script uses SpreadsheetApp.getActiveSpreadsheet() — it must be bound, not standalone
  4. The header row is auto-created on the first run — no manual setup needed

Column structure

ColHeaderTypeNotes
ADateTextYYYY-MM-DD HH:MM from Gemini or fallback
BTypeTextIncome or Expense — color-coded
CAmountNumberNegative = expense, positive = income
DItemTextMerchant, biller, or sender name
ECategoryTexte.g. Groceries, Utilities, Salary
FSplit ByNumberNumber of people sharing the transaction
GMy ShareFormula=C/F — live, updates if C or F is edited
HSource FileTextOriginal filename from iPhone
IProcessed AtTimestampWhen the script wrote this row

5. Google Apps Script

How to open the bound script editor

Open the tracker Google Sheet → Extensions → Apps Script → paste the full script below → save.

Constants

javascript
const GEMINI_API_KEY    = "YOUR_GEMINI_API_KEY";
const GEMINI_URL        = `https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-latest:generateContent?key=${GEMINI_API_KEY}`;
const INBOX_FOLDER_ID   = "YOUR_INBOX_FOLDER_ID";
const ARCHIVE_FOLDER_ID = "YOUR_ARCHIVE_FOLDER_ID";
const SHEET_NAME        = "Sheet1";

Complete script

See ExpenseTracker.js.

Deployment

mermaid
flowchart LR
    A[Edit code] --> B["Deploy → Manage deployments"]
    B --> C[Click pencil icon]
    C --> D[Version → New version]
    D --> E[Deploy]
    E --> F[Same URL preserved ✓]
SettingValue
TypeWeb app
Execute asMe
Who has accessAnyone
VersionAlways "New version" on update — never create a new deployment

Critical: Creating a new deployment generates a new URL and breaks all three iOS shortcuts. Always edit the existing deployment.

Initial setup sequence

  1. Open Google Sheet → Extensions → Apps Script
  2. Paste script, fill in constants, save
  3. Deploy as Web App (first time only)
  4. Copy the Web App URL into all three iOS shortcuts
  5. Run createTrigger once from the editor
  6. Authorize all permissions when prompted

6. iOS Shortcuts Overview

There are three independent shortcuts. Each has its own full action sequence — there is no shared core shortcut. See ios-shortcuts-setup.md for the complete step-by-step setup of each.

ShortcutTypeHow triggeredSends
SMS AutomationAutomationSMS from defined contacts containing Details:.txt (text/plain)
Log Transaction v2Manual — Share SheetTap Share in bank app, select shortcut.png (image/png)
Input TransactionManual — Home ScreenTap icon on home screen.txt (text/plain)

Shared POST body structure

All three shortcuts POST to the same Web App URL with this JSON body:

json
{
  "filename": "sms_March 27 2026 at 9 10 AM.txt",
  "mimeType": "text/plain",
  "data": "<base64 encoded content>",
  "splitBy": 2
}

doPost then renames the file to embed splitBy: sms_March 27 2026 at 9 10 AM.txtsms_March 27 2026 at 9 10 AM_split2.txt


7. Data Flow Details

File naming convention

ShortcutFilename prefixExtension
SMS Automationsms_.txt
Log Transaction v2file_.png
Input Transactioninput_.txt

All filenames use iOS's raw Current Date format. See Known Limitations.

Input Transaction text format

The shortcut constructs this string before encoding:

New spending on [Current Date]
Amount $[Amount]
Details: [User typed details]

Gemini extracts structured fields from this natural language string.

Date resolution chain

mermaid
flowchart TD
    A[Gemini extracts date field] --> B{Is date null?}
    B -->|No| C[Use Gemini date ✓]
    B -->|Yes| D{"Filename matches\nyyyy-MM-dd pattern?"}
    D -->|Yes + time| E[Use yyyy-MM-dd HH:MM:SS]
    D -->|Yes, date only| F[Use yyyy-MM-dd 00:00]
    D -->|No match| G["Fallback: 1970-01-01 00:00\n⚠ current behaviour\ndue to filename format"]

Deferred processing sequence

mermaid
sequenceDiagram
    participant iPhone
    participant doPost
    participant Drive
    participant Trigger
    participant Gemini
    participant Sheet

    iPhone->>doPost: POST (base64 + splitBy)
    doPost->>Drive: createFile in Inbox (splitBy in filename)
    doPost->>iPhone: {"status":"ok"} in ~1 second
    Note over Trigger: 0–5 minutes later...
    Trigger->>Drive: getFiles from Inbox
    Trigger->>Gemini: send text or image
    Gemini->>Trigger: JSON array of entries
    Trigger->>Sheet: appendRow per entry
    Trigger->>Drive: moveTo Processed

8. Configuration Reference

LocationKey / SettingHow to find or set it
Apps ScriptGEMINI_API_KEYaistudio.google.com → Get API key
Apps ScriptINBOX_FOLDER_IDOpen Inbox folder in Drive → copy ID from URL
Apps ScriptARCHIVE_FOLDER_IDOpen Processed folder in Drive → copy ID from URL
Apps ScriptSHEET_NAMETab name at bottom of Google Sheet (default: Sheet1)
Apps ScriptWeb App URLDeploy → Manage deployments → copy URL
iOS ShortcutsWeb App URLPaste into "Get Contents of URL" in all 3 shortcuts
SMS AutomationSender filterContact names: Rogers Credit Card Transaction
SMS AutomationMessage containsDetails: (with colon)

9. Known Limitations & Design Decisions

Filename date format (active issue)

iOS Shortcuts' raw Current Date variable produces March 27, 2026 at 9:10 AM, not 2026-03-27_091000. The resolveFallbackDate regex expects yyyy-MM-dd and will never match — any row where Gemini returns null for date will show 1970-01-01 00:00.

Recommended fix: Add a Format Date action (format: yyyy-MM-dd_HHmmss) in each shortcut and use that variable in the filename field instead of raw Current Date.

Base64 line breaks

All three shortcuts have the Encode action set to Line Breaks: Every 76 Characters. This is RFC 2045 MIME format — it adds \n every 76 chars. Utilities.base64Decode in Apps Script handles this gracefully, but it is non-standard for raw data transfer. If you ever see corrupted files in Drive, disable line breaks in the Encode actions.

getActiveSpreadsheet() binding requirement

The script uses SpreadsheetApp.getActiveSpreadsheet() — this only works in a script bound to the spreadsheet via Extensions → Apps Script. A standalone Apps Script project will throw TypeError: Cannot call method "getSheetByName" of null.

Three independent shortcuts (no shared core)

Each shortcut is self-contained. Changes to the Web App URL or the split-by logic must be made in all three shortcuts independently. This was a pragmatic choice — the single-core-shortcut pattern was designed but the final implementation diverged into separate shortcuts per use case.

Deferred processing delay

Rows appear within 0–5 minutes of the shortcut completing. If immediate updates are needed, add processInboxFiles() at the end of doPost — but this makes the shortcut wait 3–5 seconds for a response.

SMS keyword

The automation triggers on messages containing Details: (with a colon), not just details. The keyword is case-sensitive in iOS automations — ensure the SMS content matches exactly.