The Honest CAM platform MVP: Xero sync, billing, reporting, budgets (and a rename from PropCo)

2026-04-01

This is the PR where the project gets its real name. "PropCo" was a placeholder. The thing I'm actually building is Honest CAM — a Community Association Management platform that replaces software like PayHOA and CondoManager, built directly on Xero's API so the bookkeeping half is someone else's problem and the condo-specific half is mine.

This PR lands Phase A: the MVP. It's a monster — +11,183 / −149 across 251 new tests — but every chunk is gated by the architecture docs that landed in PR #2.

Step 1 — Rename and DRY foundation

  • Everything under propco.* becomes honestcam.*. CLI entrypoint is now honestcam.
  • Shared YAML loader, so every feature reads property config through one code path.
  • CLI slug validation — honestcam <cmd> <slug> rejects slugs that don't match a known property.
  • structlog instead of stdlib logging, JSON output by default.
  • Error hierarchy rooted at HonestcamError, with subclasses per subsystem.
  • Protocol-based clients (Xero, Stripe, storage) — everything that hits a network is behind a Python Protocol so tests can fake it without monkey-patching.

Step 2 — Xero sync engine

The hard part. Xero's data model isn't designed for HOA operating-and-reserve-fund bookkeeping, so the sync layer has to do a lot of translation:

  • Vendor alias resolution. 58 real vendors with 149 alias strings — "WASTE MGMT", "Waste Management Inc.", "W.M. Miami", "Waste Management of Florida" all collapse to one canonical vendor ID. O(1) lookup via a pre-built alias map loaded from YAML.
  • Category-based account routing. 44 rules mapping OCR'd document categories → Xero chart-of-accounts codes. "Utilities / Electric" routes to the electricity expense account for the property's subsidiary ledger; "Insurance / Liability" routes to prepaid insurance with the right class tag.
  • OAuth 2.0 + PKCE client, with refresh handling. Access tokens cached to disk, refresh tokens rotated on use.
  • Idempotent sync with SHA-256 tracking. Every attempted sync is tagged with a content hash; re-running the same batch detects already-synced documents and skips them.

Step 3 — Assessment billing

  • Monthly assessment generation for 12 units at $450/month (Bamboo House's current fee schedule).
  • Late fee calculation with configurable grace periods per property.
  • Per-unit ledger with FIFO payment application — a partial payment closes the oldest open charge first, then the next oldest, which matches how Florida statute treats assessment accounting.
  • 30 / 60 / 90-day aging rollup per unit, feeding the future portal dashboard.

Step 4 — Reporting

  • Budget vs actual comparison with variance columns.
  • Operating / reserve fund balance tracking, modeled per FL 718 (separate balances, separate reporting).
  • 1099-NEC identification — flags vendors paid >$600 in the calendar year so I can pull the list at tax time.
  • Board meeting packet generation — combines the treasurer's report, the aging summary, and the variance report into one PDF per meeting.

Step 5 — Budget management

  • YAML-based storage, one file per property per year (properties/<slug>/budgets/<year>.yaml).
  • honestcam budget-create-from-actuals <slug> --year 2026 --adjustment 3.5 — drafts next year's budget from this year's actuals with a percentage bump per line.
  • Year-over-year comparison baked in.

Verification

  • 251 tests passing across all modules.
  • Branch coverage is on (not just line coverage) because the billing and routing logic has a lot of conditional paths I want exercised.
  • Ruff lint clean.
  • Manual smoke tests deferred until OAuth credentials are actually plugged in: honestcam xero-auth bamboo-house, xero-sync --dry-run, billing-generate, billing-late-fees.

What this is not

It's explicitly not a replacement for the bookkeeper's eyeballs. Every sync step has a --dry-run mode because I want a human to look at the proposed Xero writes before any of them commit. Trust the model to extract fields and propose vendor matches; don't trust it to push anything to a real general ledger unattended.


PR: https://github.com/StevieIsmagic/honest-cam/pull/4