1. The tiles and where each number comes from
Think of the dashboard as a storefront window: some items are made fresh in the back kitchen (AppFolio), some are grown in our own garden (Clara's own events), and a few are just yesterday's photo of the window (the trend chart).
- Occupancy, rents, leasing, renewals: AppFolio's rent_roll, tenant_directory, renewal_summary reports.
- Maintenance, turnovers: AppFolio work_order report plus PropFlow's own turnover-cycle logic.
- Collections: AppFolio aged_receivables + delinquency reports (balances job).
- Tours, some leads: PropFlow's own Clara-booked events, not AppFolio at all.
Proves: src/lib/domain/dashboard/stats/compute.ts, src/lib/data/types.ts (DashboardStats, LeasingStats).
Deeper → Appendix: full tile table
2. How each is calculated: the metrics snapshot
The snapshot is a photograph taken once a night — useful for "what did last week look like," useless for "what's happening right now," which is why the live tiles never use it.
- Runs once/day per property (~04:00 UTC), writes METRIC# rows via lambda/metric-snapshot/handler.ts.
- Built by jose-propflow (2026-04-23), not Gera, per git log.
- Feeds ONLY expanded trend/sparkline views, never a tile's headline number.
- Sound design: one shared compute function keeps live and snapshot figures from disagreeing.
- Inspect later: new property has zero snapshot rows before its first run — flat trend, correct live number.
Proves: lambda/metric-snapshot/handler.ts, git log --follow on that file.
3. What a new customer sees, and when
Moving in day: the movers unload the couch and bed fast (stage 1-2, ~19 seconds), but the cable guy, the mail forwarding, and the first bank statement each show up on their own separate, slower schedule.
- Occupancy, rent, notices: real within ~19s (Willows bench: 7.6s + 11.0s).
- Applications, guest cards, renewals, maintenance, vendors: first regular poll after go-live (1 min–24h).
- NOI and 12-month financials: only after the once-daily financials job, not accelerated at all.
- Basic info (occupancy/units/notices) needs only stages 1-2; a labeled revenue/NOI number needs financials, outside any import stage.
Proves: lambda/appfolio-sync/onboarding-import-runner.ts (MAX_IMPLEMENTED_STAGE=2), schedules.json.
4. Module gating gaps
It's like locking a store's front door but leaving the display window lit up with everything inside — the sign says closed, the window doesn't agree.
- Nav hides /maintenance, /leasing/renewals, /collections when their module flag is off.
- compute.ts and DashboardHome.tsx never read enabledModules — zero references found.
- So Maintenance, Turnovers, Renewals, Collections tiles render regardless of module state.
- Western Slope's "2 work orders": no property anywhere has exactly 2 open WOs — likely stale cache, not a leak.
- Turnovers/Collections tiles for Western Slope are genuine, current-day rows.
Proves: src/lib/platform/auth/permissions.ts (routeToModuleFlag), DynamoDB read-only query 2026-09-12.
Deeper → Appendix: work-order query evidence
A1 · Appendix — full record
Every tile, its origin, its earliest import stage, and the citation, plus the Western Slope work-order evidence.
Tile → source → import stage
| Tile | Origin | Report / event | Earliest stage | File:line |
|---|---|---|---|---|
| Occupancy | (a) AppFolio | rent_roll (occupancies job) | 1 | compute.ts:271 |
| Rents / revenue | (a) AppFolio | rent_roll or income_statement | 2 (financials for statement figure) | types.ts:10893-10919 |
| NOI | (d) AppFolio accounting | income_statement (financials job, 06:00 UTC) | not in any import stage | schedules.json:322 |
| Leasing pipeline | (a)+(b) mixed | rent_roll, rental_applications, guest_cards | 2 (units) / 3 (apps, design) | types.ts:12001 |
| Renewals | (a) AppFolio | renewal_summary (renewals job) | 4 (design) | schedules.json:165 |
| Leads by source | (a)+(b) mixed | guest_cards + Clara's own inbound | 3 (AppFolio half); not from import (Clara half) | compute.ts |
| Maintenance | (a) AppFolio | work_order (1 min) + wo_enrichment (daily) | 4 (design) | schedules.json:39,84 |
| Turnovers | (a)+(b) mixed | rent_roll move-out + WO close events | 4 (design) | src/lib/domain turnover logic |
| Collections | (a) AppFolio | aged_receivables + delinquency (balances, 15 min) | 5 (design) | types.ts:11046-11060 |
| Vendors | (a) AppFolio | vendor_directory (hourly) + vendor_contact (4x/day) | 4 (design) | schedules.json:199,204 |
| Payments | (d) AppFolio accounting | payments report (daily) | 5 (design) | schedules.json:281 |
Western Slope work-order query (2026-09-12, read-only, propflow-prod)
Org org_c42d9150-b9ad-46f8-8f5f-b0fe56bacf38. Roster: 69 properties, confirmed identically via proporg-index (ROSTERPK=PROPORG#<org>) and via Property.organizationId attribute scan (entityType-createdAt-index) — no drift between the two.
| Check | Result |
|---|---|
| Direct partition query (PK=PROP#<id>, SK begins_with WO#) across all 69 Western Slope properties | 0 WorkOrder rows |
| System-wide open WorkOrders (status in pending_dispatch/dispatched), by propertyId | appfolio-45 (Willows, test)=97; 1773625953462 (Camellia, org_jpco)=31; yale-sandbox (test)=4 |
| Any property/org with exactly 2 open WorkOrders | none found |
| Western Slope BAL# (balance snapshot) rows | real: 11 properties, 4-37 rows each — genuine, not a leak |
| Western Slope TURNOVER# rows | real: 6 rows across 3 properties, all created 2026-09-12, stage notice_received |
Conclusion: no WorkOrder row anywhere in prod reproduces "2" for Western Slope, real or any other org/test property — not a row-level leak of another tenant's data. Most likely explanation is the client-side dashboardStatsCache/sessionStorage hydration (src/app/api/dashboard/stats/route.ts) serving a stale figure across a company switch; recommend a hard refresh + cache-busted re-check before concluding anything about Western Slope's true maintenance count (which the data says should be 0, module off).
Module → nav-gating flag map
| Route | Module flag | Dashboard tile reads same flag? |
|---|---|---|
| /maintenance (incl. turnovers, vendors) | MODULE_FLAGS.MAINTENANCE | No — Maintenance + Turnovers tiles always render |
| /leasing/renewals | MODULE_FLAGS.LEASING_RENEWALS | No — Renewals tile always renders |
| /collections | MODULE_FLAGS.COLLECTIONS | No — Collections tile always renders |
| /dashboard itself | none (always-on) | n/a |
File:line — src/lib/platform/auth/permissions.ts:274 (isRouteModuleEnabled), :435 (routeToModuleFlag), :502-510 (maintenance/collections/renewals mapping); src/components/primitives/SideNav.tsx:849,1435 (nav gate call sites); zero matches for "enabledModules" in src/lib/domain/dashboard/stats/compute.ts or src/components/feature/dashboard/DashboardHome.tsx.