# Zoho Receipts Pipeline — Cost-Impact Analysis

**Status:** Approved 2026-06-13 — see §10
**Date:** 2026-06-13
**Author:** Claude (with Jason Schwefel)
**Approval gate:** Approved up to $5/mo ceiling. Provisioning may proceed once all eight Zoho-domain docs are written.

---

## 1. What is being provisioned

A new automation pipeline that:

1. Polls the `finance@coldboreballisticsllc.com` shared mailbox hourly via Microsoft Graph (app-only, scoped via `ApplicationAccessPolicy`).
2. Normalizes each incoming receipt to the existing Markdown schema documented in the Normalized Receipts Corpus.
3. **Renders the canonical receipt to PDF** (uses the original PDF attachment if present, else renders the inline HTML body to PDF via `weasyprint`).
4. **Filters out zero-dollar receipts** at the validation stage (free trials, $0 statements) — these never become Zoho expenses; tracking is handled per R-15.
5. Imports each non-zero receipt to **Zoho Books Standard plan** via the v3 REST API as an Expense, attaching the canonical PDF.
6. Files the normalized Markdown into OneDrive at `Receipts/<line>/<YYYY>/<MM>/`.
7. Backs up Zoho Books data nightly (JSON-per-module) and weekly (full org export) to Azure Blob Storage with Hot/Cool/Cold/Archive lifecycle tiering and 7-year retention.
8. Runs a weekly consistency-check job (Tuesday morning) against Zoho Books data.
9. Sends Slack DMs and email notifications to the user on `needs_review` events (always), on `zero_dollar_receipt` events (always, per R-15 stopgap), and on successful imports (user-toggleable).
10. Emits asset-candidate events to a Storage Queue; a **separate scoped service** (`caj-cbb-asset-register-prod`, its own managed identity, its own Key Vault) consumes those events and writes new items to the **CBB Asset Register** SharePoint list with idempotent dedup.

**Region:** North Central US (per CLAUDE.md "Azure Region — MANDATORY").

## 2. New Azure resources

| Resource | Type | SKU / Tier | Purpose |
|---|---|---|---|
| `rg-cbb-receipts-prod` | Resource Group | n/a | Container for the pipeline resources |
| `cae-cbb-receipts-prod` | Container Apps Environment | Consumption | Hosts the jobs (both services) |
| `caj-cbb-receipts-ingest-prod` | Container Apps Job | Consumption, scheduled hourly | Ingestion pipeline (includes PDF rendering stage) |
| `caj-cbb-receipts-backup-nightly-prod` | Container Apps Job | Consumption, scheduled nightly | Nightly JSON-per-module export |
| `caj-cbb-receipts-backup-weekly-prod` | Container Apps Job | Consumption, scheduled weekly | Weekly full-org export |
| `caj-cbb-receipts-consistency-prod` | Container Apps Job | Consumption, scheduled Tuesday weekly | Consistency check |
| `caj-cbb-asset-register-prod` | Container Apps Job | Consumption, event-triggered (KEDA Storage Queue) | Separate service writing to SharePoint Asset Register list |
| `stcbbreceiptsprod` | Storage Account | StorageV2, LRS | Backup destination + asset event queue host |
| `kv-cbb-receipts-prod` | Key Vault | Standard | Zoho credentials only |
| `kv-cbb-asset-register-prod` | Key Vault | Standard | SharePoint site/list IDs (asset service only) |
| Managed Identity (system-assigned) | Identity attached to each Container Apps Job | n/a | Auth to Graph + Key Vault + Queue |

**Acquisition lens:** all named per a consistent `cbb-receipts` / `cbb-asset-register` convention so a buyer can identify each service with `az resource list --tag service=cbb-receipts`.

## 3. New non-Azure recurring costs

| Item | Cost | Status |
|---|---|---|
| Zoho Books Standard plan | $20/mo | **Already paid — not new** |
| `finance@` shared mailbox | $0 | Shared mailboxes ≤50GB are unlicensed |
| Microsoft Graph app-only `Mail.ReadWrite` permission | $0 | Tenant-bundled |
| Microsoft Graph app-only `Sites.Selected` permission | $0 | Tenant-bundled |
| `ApplicationAccessPolicy` restricting Graph access to `finance@` only | $0 | Exchange Online admin operation |
| SharePoint per-site write permission (Asset Register site) | $0 | Tenant-bundled |
| CBB Asset Register SharePoint list | $0 | Existing M365 list |
| `weasyprint` (PDF rendering library) | $0 | Open-source Python package, BSD-licensed |

## 4. Azure cost estimate — North Central US

All prices below are 2026 published US rates. North Central US pricing matches the US-default pricing for all resources used.

### 4.1 Container Apps Jobs (Consumption)

**Pricing model:**
- vCPU-seconds: $0.000024 per vCPU-s after free grant
- GiB-seconds: $0.000003 per GiB-s after free grant
- Requests: $0.40 per 1M requests after free grant
- **Free monthly grant per subscription:** 180,000 vCPU-s, 360,000 GiB-s, 2M requests

**Expected job profile (including PDF rendering time in ingestion):**

| Job | Cadence | Runs/mo | Wall time/run | vCPU | RAM | vCPU-s/mo | GiB-s/mo |
|---|---|---|---|---|---|---|---|
| Ingestion (incl. PDF render) | hourly | 720 | 40 s | 0.5 | 1 GiB | 14,400 | 28,800 |
| Backup nightly | daily | 30 | 60 s | 0.5 | 1 GiB | 900 | 1,800 |
| Backup weekly (full export) | weekly | 4–5 | 180 s | 0.5 | 1 GiB | 405 | 810 |
| Consistency check | weekly (Tue) | 4–5 | 60 s | 0.5 | 1 GiB | 135 | 270 |
| Asset register service | event-triggered | ~50 (matches asset-eligible receipts) | 10 s | 0.5 | 1 GiB | 250 | 500 |
| **Total / month** | | | | | | **~16,090 vCPU-s** | **~32,180 GiB-s** |

**vs. free grants** (180,000 / 360,000): we use ~8.9% / ~8.9%. **Well under the free tier on a combined-pipeline basis, with substantial headroom.**

PDF rendering increased the ingest wall time from 30s to ~40s (added a `weasyprint` HTML-to-PDF render step in the worst case). Even with this, we are not close to the free-grant ceiling.

**Estimated Container Apps cost: $0.00/mo**, with substantial headroom for additional services to share the same Environment without exceeding the free grant.

### 4.2 Storage Account — Blob Storage backup

**Pricing (US default, LRS):**

| Tier | $/GB-mo | Min retention before tier change | Early-deletion fee |
|---|---|---|---|
| Hot | $0.0184 | none | none |
| Cool | $0.0100 | 30 days | charged for full 30 days if deleted earlier |
| Cold | $0.0036 | 90 days | charged for full 90 days if deleted earlier |
| Archive | $0.00099 | 180 days | charged for full 180 days if deleted earlier |

**Lifecycle policy proposed:**
- Hot 0–30 days
- Cool 31–90 days
- Cold 91–365 days
- Archive 366+ days
- Delete at 7 years

**Estimated data volume:**

| Backup type | Size/run | Cadence | Year-1 total |
|---|---|---|---|
| Nightly JSON-per-module | ~5 MB | daily | ~1.8 GB |
| Weekly full export | ~50 MB | weekly | ~2.6 GB |
| **Year-1 footprint** | | | **~4.4 GB** |

**Steady-state (year 7), assuming linear growth and lifecycle tiering: ~$0.06/mo storage subtotal. Transaction charges: <$0.10/mo. Estimated Storage cost: <$0.20/mo at year 7, ~$0.05/mo year 1.**

### 4.3 Key Vaults — Standard tier (two vaults)

**Pricing:**
- Operations (get/set/list secrets): **$0.03 per 10,000 transactions**
- No monthly baseline charge per vault
- No HSM use (Standard tier, software-protected only)

**Expected operations across both vaults: ~4,000 ops/mo → ~$0.02/mo combined.**

### 4.4 Microsoft Graph API calls

**Pricing:** Free up to standard tenant limits. No per-call charge.

### 4.5 Egress

**All traffic is intra-Azure or outbound to public APIs.** Public outbound traffic up to 100 GB/mo is free in most Azure subscriptions, and our actual outbound is well below 1 GB/mo. **$0.00/mo estimated.**

### 4.5b Storage Queue (asset candidate events)

A single Storage Queue `asset-candidate-events` lives in the same `stcbbreceiptsprod` storage account, plus a companion `asset-candidate-events-poison` queue.

- Pricing: $0.045 per million queue transactions (LRS, US default)
- Expected volume: ~50 receipts/mo × ~3 transactions per event = ~150 ops/mo
- **Estimated: <$0.01/mo** (rounding error)

### 4.6 Log Analytics workspace (Container Apps logs)

- Pricing: ~$2.30 per GB ingested (US default)
- Expected log volume across both services: under 150 MB/mo
- **Estimated: $0.25–$0.35/mo**

(Alternative — Azure Monitor Basic Logs tier at ~$0.50/GB — defer optimization until baseline is observed. See R-3.)

### 4.7 Asset Register service — incremental cost summary

The asset-register service costs are folded into the totals above. Service summary for the acquisition-lens view:

| Resource | Allocation to asset service | $/mo |
|---|---|---|
| `caj-cbb-asset-register-prod` job time | ~2% of free grant headroom | $0 |
| `kv-cbb-asset-register-prod` operations | ~150/mo | <$0.01 |
| Log Analytics ingestion (asset job logs) | ~10 MB/mo | ~$0.03 |
| **Asset service subtotal** | | **~$0.04/mo** |

The asset service is essentially zero-cost incremental — what it buys is the clean credential-ownership separation and strict scoping discipline.

### 4.8 PDF rendering — no incremental cost

The `weasyprint` library is open-source (BSD), runs inside the existing ingest container. Adds ~30 MB to container image size and ~10s wall time per receipt. No new Azure resource, no new license, no per-call charge.

## 5. Total recurring cost — month 1

| Resource | Cost |
|---|---|
| Container Apps Jobs (within free grant) | $0.00 |
| Container Apps Environment baseline | $0.00 |
| Storage Account (year 1) | ~$0.05 |
| Storage Queue (asset events) | <$0.01 |
| Key Vault — receipts | ~$0.01 |
| Key Vault — asset register | ~$0.01 |
| Microsoft Graph | $0.00 |
| Egress | $0.00 |
| Log Analytics ingestion (receipts + asset) | ~$0.30 |
| **Pipeline subtotal — new monthly cost** | **~$0.38/mo** |
| Zoho Books Standard (existing) | $20.00 |
| **Combined Zoho + automation monthly cost** | **~$20.38/mo** |

## 6. One-time / setup costs

- Engineering time (script + Terraform development): user time, no external cost
- One-time Self Client OAuth bootstrap with Zoho Books: $0 (already completed 2026-06-13)
- Exchange Online `New-ApplicationAccessPolicy` admin step: $0
- One-time SharePoint per-site `Sites.Selected` grant via Graph: $0
- Container image storage in Azure Container Registry: **deferred** — see R-4

## 7. Cheaper alternatives considered

Per the "cost-impact analysis must consider cheaper alternatives" directive:

| Alternative | $/mo | Why rejected |
|---|---|---|
| **Azure Functions Consumption** | $0 within free grant | Cost-equivalent, but introduces a fourth Azure runtime. Container Apps Jobs match the runtime CBB has already invested in. Rejected on architecture-fit grounds. |
| **Logic Apps Consumption with Outlook/SharePoint connectors** | $0.10–$3.00/mo | Per-action pricing scales unpredictably; connector-based design is harder to test offline and harder for TDD to read. Rejected. |
| **Power Automate flow** | License-attached | Requires Premium connector licence ($15+/user/mo). Borrows user identity. Rejected on cost + governance grounds. |
| **VM-hosted cron (B1s)** | ~$8/mo + management | More expensive, requires patching. Rejected. |
| **Combine receipts + asset-write into a single service** | ~$0 (same) | Violates Service Identity Ownership; single identity would need Mail.ReadWrite + Sites.Selected + Zoho creds. Rejected for governance even at identical cost. |
| **Free Trial $1.00 bucket pattern for zero-dollar receipts** | $0 | Creates synthetic expense stream that doesn't correspond to real money out. Filter-then-notify (current design) is cleaner. Rejected. |
| **Skip automation; manual entry** | $0 | Creates single-person dependency. Rejected. |

**Conclusion:** Container Apps Jobs (Consumption) split across two strictly-scoped services with PDF rendering in-container is the lowest-cost option that also matches the architecture-fit and acquisition-readiness goals.

## 8. Open decisions confirmed

These were resolved during design and are recorded here for audit trail:

1. **Container image registry:** Public images at start (free); promote to ACR only if private image is needed. ✓ Accepted (R-4).
2. **Log Analytics workspace:** Dedicated `log-cbb-receipts-prod`. ✓ Accepted.
3. **Storage account redundancy:** LRS at current volume; revisit at year 5. ✓ Accepted (R-7).
4. **PDF rendering library:** `weasyprint` for HTML→PDF; original PDF passed through if attached to email. ✓ Accepted (R-16).
5. **Zero-dollar receipts:** Filtered out of Zoho entirely; notification + manual disposition. Free-trial tracking mechanism is deferred (R-15).

## 9. Worst-case cost ceiling

To bound the approval, worst-case scenario at 10× projected volume for a year:

| Scenario | Projected | 10× | Hard ceiling explanation |
|---|---|---|---|
| Container Apps Jobs | $0/mo | Still $0/mo | Even at 10× volume, vCPU-s and GiB-s remain under free grant (would be ~89%). |
| Storage | $0.05/mo year-1 | $0.50/mo year-1 | Linear with data volume. |
| Storage Queue | <$0.01/mo | $0.05/mo | Linear with event volume. |
| Key Vaults | $0.02/mo | $0.20/mo | Linear with operations. |
| Log Analytics | $0.30/mo | $3.00/mo | Linear with log volume. |
| **Worst-case ceiling year 1** | | **~$3.75/mo** | Still under $45/yr above existing Zoho subscription. |

**No conceivable scenario at the current pipeline scope crosses $5/mo of new Azure spend.**

## 10. Approval — granted 2026-06-13

The user explicitly approved:

- [x] **New Azure monthly spend:** up to **$5/mo** ceiling, expected **<$0.50/mo** at projected volume, in North Cen