UnitCycle — System Patterns
Architecture Overview
[Angular 19 SPA] → [server.js :4400] → [PostgreSQL 17]
↓ proxy
[Django DRF :3001]
- server.js (port 4400): serves Angular dist + embedded raw SQL API endpoints
- Django (port 3001): DRF viewsets for complex features (auth, vendor portal, IoT, invoices)
- PM2 manages both:
unitcycle (server.js) + property-api-django (Django)
- Nginx reverse proxies demo.unitcycle.com → port 4400
Frontend Patterns (Angular 19)
- Standalone components only — no NgModules
- New control flow:
@if, @for, @switch — NEVER *ngIf, *ngFor
- Signals-based state — no RxJS/NgRx for state management
- OnPush change detection on every component
- Feature-organized under
src/app/features/
- Tailwind CSS 4 utilities + 208 CSS variables in
theme-tokens.css
- ZERO hardcoded hex —
rgb(var(--color-accent)) everywhere
- 7 preset themes — UnitCycle Light/Dark, Slate, Ocean, Emerald, Sunset, Purple
- Fonts: Manrope (headings), Inter (body)
- Sidebar: 7 groups + accordion, data-driven
navSections, outlined icons
Backend Patterns (Django 5.x + DRF)
managed=False on all Yardi-synced models — NEVER ALTER
- Companion table pattern: new table with OneToOne FK to extend Yardi data
propintel user = DML only — no CREATE/ALTER/DROP
- New tables:
managed=True in Meta, sudo -u postgres ... migrate to create
- Raw SQL INSERT for writing to Yardi tables (ORM
.create() fails on managed=False)
_aed field names in Django models → _usd keys in API responses (legacy naming)
Database Pattern
- DB:
unitcycle_demo on PostgreSQL 17
- ~254 tables, ~483K rows of real Yardi-synced data
- propintel: DML only. Schema changes via
sudo -u postgres
Authentication
- PM Dashboard: Mock credentials in
auth.service.ts → MOCK_CREDENTIALS
- Tenant Portal: Mock credentials in
tenant-portal-auth.service.ts
- Maintenance Portal: Real API auth →
POST /api/v1/maintenance-portal/auth/login/ against maintenance_staff table with hashed PINs
- Django Backend: Custom
PortalUser model (NOT Django User). JWT + refresh tokens, OTP for tenants, magic links for owners
AI Integration Pattern
- Claude API (claude-sonnet-4-20250514) for: renewal letters, collections messages, invoice extraction, portfolio chat, lease generation, damage assessment
- LlamaParse for PDF invoice extraction
- Celery tasks for scheduled AI operations (nightly scans, daily digests)
- Tool use pattern for Portfolio Chat (5 tools: query_leases, query_financials, query_maintenance, query_vacancy, create_action)
- SSE streaming for chat responses
Testing Pattern
- Playwright E2E tests in
tests/
- ABSOLUTE RULE: Every feature MUST have Playwright tests + screenshots before "done"
- Workflow: Build → Write test → Run test → Screenshots → Post to Discord → Pass or fix
- Test report: standalone HTML at demo.unitcycle.com/test-report/
Key Design Decisions
- Gold for ALL positive indicators (never green/teal)
- No mocked/fake data anywhere — seed DB if needed
- Unit-centric owner detail (6 URL-mapped tabs)
- Tenant detail: 5 URL tabs, 6 PM actions, recurring charges
- Work orders: 10 sources, IoT sensors, Asset Health hub, SVG gauges
- Sidebar: outlined icons, AI items get ✨ sparkle indicator