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.*becomeshonestcam.*. CLI entrypoint is nowhonestcam. - 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
Protocolso 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.