Busabase
Busabase is an open-source (MIT) database and workspace for AI agents — data, docs, skills, and apps in one place. Run it locally, self-host it inside your network, or use Busabase Cloud, and turn age
View daily KPI dashboard for property portfolio performance
Translation pending
AI summary · website content
DataHTML · PythonGitHub @ymi-flowingOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Real-time property management dashboard for SNS portfolio using ResMan API and Google Sheets integration.
Live Dashboard: https://sns-daily-report.vercel.app
Last Updated: November 4, 2026
View Live: Visit sns-daily-report.vercel.app
Local Development:
cd "C:\Users\ymosh\Claude\ROI\SNS Daily Report"
python generate_dashboard.py
Then open dashboard_live.html in a browser.
This dashboard provides a unified view of:
/renewals — standalone renewals analytics page. See the Renewals Dashboard section below./api/record_snapshot — daily snapshot endpoint. See the Supabase Daily Snapshot section below./api/skill — read-only skill markdown for sharing with any AI. See Supabase Daily Snapshot section below.| File | Description |
|---|---|
generate_dashboard.py | Main script - fetches data and generates HTML |
dashboard_live.html | Generated dashboard (open in browser) |
SNS_STYLEGUIDE.md | SNS design system documentation |
DEVELOPMENT.md | Technical documentation and API reference |
.github/workflows/refresh.yml | GitHub Actions - auto-refresh every 4 hours |
api/index.py | Vercel serverless function - triggers manual refresh |
api/get_leases.py | Vercel API - fetch leases for renewal letters |
api/get_charges.py | Vercel API - fetch deposit and recurring charges |
api/push_monday.py | Vercel API - push data to Monday.com |
renewal_summary.json | Current-month renewal buckets + % per property (regenerated every 4h, consumed by n8n) |
vercel.json | Vercel deployment configuration |
requirements.txt | Python dependencies |
renewal_letters.html | Renewal letter generation tool |
renewals.html | Renewals Dashboard — /renewals page (see section below) |
renewals_data.json | Baked JSON that powers renewals.html (committed by sync-renewals.yml) |
renewals_sync/ | Python scripts that pull ResMan → Sheet and Sheet → renewals_data.json |
api/get_renewals.py | Live fallback that reads the Google Sheet directly (used mid-refresh for immediate feedback) |
api/trigger_renewals_sync.py | Dispatches sync-renewals.yml from the Refresh button |
api/sync_status.py | Reports the last sync-renewals.yml run status |
api/last_sync.py | Reports the last successful run time (shown in the chip) |
https://api.myresman.comIntegrationPartnerID, APIKey, AccountID)Account/GetProperties - List all propertiesProperty/GetUnitStatus - Unit vacancy & ready status (VacancyStatus, Status fields)Leasing/GetApplicantsAndCurrentResidents - Identify eviction unitshttps://partners-api.myresman.comResMan-Account-IdGET /Leasing/Leases - Lease data with history for renewalsGET /Units/Availability - Unit vacancy, occupancy, move-out dates (primary endpoint)GET /Units - Unit details, renovation status, availableForOnlineMarketing fieldGET /WorkOrders - Open work orders and new orders in last 24hGET /Transactions/Receivables - Owed account balances as of a date (used for Receivable MTD / Del %)1D8eBjdcClYf9L4DNQg2cKDE1Wuwkc5AOSQcRCPmz18A1TF3cpVlGcllKMJnkPOB_fHmFgN-wwb1qbiJ05l8JMNU| Region | Properties |
|---|---|
| Tampa | 49thStr |
| Orlando | TV, MOC, MUSO |
| Jacksonville | AP, SJ, ROP, ORT |
| Ft. Lauderdale | EAST |
Note: Down/Abated units are excluded from all counts and calculations.
The Property/GetUnitStatus endpoint returns these VacancyStatus values:
Occupied - Unit is occupiedVacant - Unit is vacant, not pre-leasedVacant Pre-Leased - Unit is vacant but has a signed future leaseNotice to Vacate - Current resident gave notice, not pre-leasedNotice to Vacate Pre-Leased - Current resident gave notice, has future lease signed| Category | Logic | Notes |
|---|---|---|
| Occupied | VacancyStatus = "Occupied" | |
| Vacant Ready | VacancyStatus in ["Vacant", "Vacant Pre-Leased"] AND Status = "Ready" | Includes pre-leased |
| Vacant Not Ready | VacancyStatus in ["Vacant", "Vacant Pre-Leased"] AND Status != "Ready" | Includes pre-leased |
| Vacant Leased | VacancyStatus = "Vacant Pre-Leased" | Subset of Vacant Ready/Not Ready |
| NTV | VacancyStatus in ["Notice to Vacate", "Notice to Vacate Pre-Leased"] | Includes pre-leased |
| NTV Leased | VacancyStatus = "Notice to Vacate Pre-Leased" | Subset of NTV |
| Under Eviction | Resident Status = "Under Eviction" (from GetApplicantsAndCurrentResidents) |
Rentable Units = Vacant Ready + Vacant Not Ready + NTV - Vacant Leased - NTV Leased
This gives the count of non-pre-leased vacant and NTV units that need to be rented.
Example (ORT):
Units that are rentable but NOT listed for online marketing.
Source: Partners API /Units endpoint, field availableForOnlineMarketing
Logic:
availableForOnlineMarketing fieldavailableForOnlineMarketing = falseExample (ORT):
Physical Occupancy % = (Total - Vacant Ready - Vacant Not Ready) / Total * 100
Occupancy Rented % = (Total - Vacant Ready - Vacant Not Ready + Vacant Leased) / Total * 100
Includes pre-leased vacant units as "rented".
| Metric | Description |
|---|---|
| Total Units | All active units per property (excludes Down/Abated) |
| Occupied | Units with "Occupied" status |
| Vacant Ready | Vacant + ready (includes pre-leased) |
| Vacant Not Ready | Vacant + not ready (includes pre-leased) |
| NTV | Notice to Vacate (includes NTV Leased) |
| Under Eviction | From resident status |
| Physical Occupancy | (Total - Vacant Ready - Vacant Not Ready) / Total |
| Vacant Leased | Subset: pre-leased units in vacant counts |
| NTV Leased | Subset: pre-leased units in NTV count |
| Rentable Units | Vacant Ready + Vacant Not Ready + NTV - Vacant Leased - NTV Leased |
| Off the Market | Rentable units not listed online |
| Occupancy Rented | Physical Occupancy + Vacant Leased |
| Metric | Source | Description |
|---|---|---|
| Total Prospects | Google Sheets | MTD leads per property |
| Daily AVG | Google Sheets | MTD prospects ÷ calendar days elapsed this month (zero-lead days included) |
| Total Applicants | Partners API | Pending leases (future move-ins) |
| Pending Approval | Partners API | approvalDate is null |
| Approved | Partners API | Has approvalDate, no leaseSignedDate |
| Signed Lease | Partners API | Has leaseSignedDate |
Shows leases expiring in current month + next 3 months (4 total).
Collapsible months - Click to expand/collapse each month.
Header shows Renewal % per property:
Renewed / (Current + Renewed + NTV + Under Eviction)| Status | Description |
|---|---|
| Current | No action taken yet |
| Pending Renewal | Renewal offered, not signed |
| Renewed | Renewal signed |
| NTV | Notice to Vacate given |
| Under Eviction | Eviction in progress |
Color coding:
Shows unit renovation breakdown per property.
| Status | Description |
|---|---|
| Renovated | Fully renovated units (parsed from unitType field) |
| Partial Renovated | Partially renovated units |
| Classic | Original/non-renovated units |
Data source: Partners API GET /Units - parses unitType field
Shows income and delinquency data per property.
Income Section:
| Metric | Description |
|---|---|
| Total Income | Sum of all charges for the period |
| Total Credit | Sum of all credits/adjustments |
| Net | Total Income - Total Credit |
| Receivable MTD | Owed to the property as of today — sum of positive account balances (ResMan Partners API GET /Transactions/Receivables) |
| Del % | Receivable MTD ÷ Net × 100, color-coded (>10% red, >5% amber, else green) |
Delinquency Section:
| Metric | Description |
|---|---|
| Current | Amount owed by current residents (hover for count) |
| Under Eviction | Amount owed by residents under eviction (hover for count) |
| Formers | Amount owed by former residents (hover for count) |
| Total | Sum of all delinquency categories (hover for count) |
Data source: Google Sheets (populated by n8n from ResMan)
Features:
property_snapshots, plus portfolio_snapshots and property_daily_deltas views. See Supabase Daily Snapshot section below./api/record_snapshot — single POST endpoint called by n8n daily, does guard + insert + returns success/already_recorded/error status./api/skill — serves a read-only analyst skill at one URL. Publishable (anon) key embedded; RLS + security_invoker=on restrict it to SELECT.property_name column on property_snapshots so queries surface the full name (e.g. "The Village at San Jose"), not just the abbreviation.supabase_sync.py mapper emits NULL for prospects_mtd / daily_avg_prospects / conversion_rate_pct when the traffic source is absent (prevents a repeat of the 10/22-10/24 CRM-dark incident).renewal_summary.json at repo root — machine-readable current-month renewal snapshot for n8n end-of-month email. Contains per-property buckets (Current / Pending Renewal / Renewed / NTV-Formers / Under Eviction), computed renewalPct (same formula as the dashboard header: renewed / (current + renewed + ntvFormers + underEviction)), and a portfolio total row. Regenerated + committed on every 4h refresh alongside dashboard_live.html. Consume via https://raw.githubusercontent.com/ymi-flowing/sns-daily-report/main/renewal_summary.json with a GitHub PAT (repo is private).GET /Prospects (MTD count by first-contact date, including Lost).GET /Transactions/Receivables; Del % = Receivable MTD ÷ Net.Units/Availability endpoint (replaces legacy GetUnitStatus)eviction_units set -> list)generate_dashboard.py on schedule or manual triggerdashboard_live.html with current timestampRESMAN_API_KEY - ResMan API keyRESMAN_PARTNER_ID - ResMan partner IDRESMAN_ACCOUNT_ID - ResMan account IDGITHUB_TOKEN - Fine-grained token with Actions write permission (for refresh button)Every property's daily state is persisted in Supabase so trends can be analysed over time. Single endpoint drives it; one skill file lets any AI answer analytical questions over the data.
ResMan + Google Sheets (sources, every 4h)
|
v
generate_dashboard.py writes data.json + dashboard_live.html (unchanged)
|
v
n8n (midnight ET) calls POST /api/record_snapshot once a day
|
v
/api/record_snapshot fetch data.json -> guard against duplicates -> insert 9 rows
|
v
Supabase property_snapshots one row per (property, date), kept forever
| Object | Role |
|---|---|
property_snapshots (table) | One row per (property_abbr, snapshot_date). ~70 columns. |
portfolio_snapshots (view) | Portfolio totals summed per snapshot_date. |
property_daily_deltas (view) | Day-over-day deltas for MTD counters; window partitioned by month so deltas reset on the 1st automatically. |
Security: RLS enabled on the base table. Publishable (sb_publishable_*) key can SELECT, cannot write. Secret (sb_secret_*) key used only by the Vercel endpoint for inserts. Both views set to security_invoker=on so they honour the base table's RLS.
/api/record_snapshot pre-flight SELECT: returns already_recorded without writing.UNIQUE(property_abbr, snapshot_date) constraint on the table.Prefer: resolution=ignore-duplicates on the POST → ON CONFLICT DO NOTHING.POST /api/record_snapshot (no body, no auth)
Returns one of three shapes:
{ "status": "success", "snapshot_date": "YYYY-MM-DD", "inserted": 9 }
{ "status": "already_recorded", "snapshot_date": "YYYY-MM-DD", "existing_count": 9 }
{ "status": "error", "snapshot_date": "YYYY-MM-DD", "error": "..." }
error cases also return a non-2xx HTTP code so n8n's error branch picks them up cleanly.
SUPABASE_URL — https://judbcwsdalgfwaxzhxlq.supabase.coSUPABASE_SERVICE_ROLE_KEY — the sb_secret_* key with write accessGET /api/skill serves skills/SNS Data Analyst Skill.md as text/markdown with a 60-second cache. The file is self-contained:
Share the URL with any AI that supports URL fetching (Claude Desktop, Cursor, Claude Code, ChatGPT with browsing). No file management, no stale copies.
applicants_mtd family NULL for snapshot_date < 2026-10-28 — old xlsx layout didn't carry those columns.generated_at and future_mi_count NULL for all historical rows (backfill artifact).applicants_mtd_active decreases within a month (not strictly cumulative — applicants exit when decided).prospects_mtd = NULL means the CRM source was dark (don't confuse with "zero leads"). 0 means literal zero activity.income_total / credit_total downticks are usually real ResMan adjustments, not pipeline bugs.# Dry-run the mapper against today's data.json
python supabase_sync.py --dry-run
# Backfill historical xlsx files (after dropping them into backfill/)
python backfill_snapshots.py --dir backfill --dry-run
python backfill_snapshots.py --dir backfill --apply
# Inspect what landed
python query_snapshots.py --prop AP
Needs .env.local with SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY.
Standalone page at /renewals that surfaces every expiring lease plus signed-renewal detail, with per-resident PDF letter preview. Same Vercel app, static-first load, same off-white light / dark theme.
Normal open:
browser → GET /renewals_data.json → ~100ms, no API
Refresh click:
POST /api/trigger_renewals_sync
→ GitHub Actions sync-renewals.yml:
1. renewals_sync/sync_all.py ResMan → Sheet
2. renewals_sync/write_renewals_json.py Sheet + ResMan dates → renewals_data.json
3. git commit + push bakes JSON into repo
→ dashboard polls /api/sync_status until "completed"
→ dashboard reads /api/get_renewals (live sheet) for immediate feedback
→ next normal reload serves the newly-baked static JSON
ETA ~3–5 minutes end-to-end.
RESMAN_API_KEY, RESMAN_PARTNER_ID, RESMAN_ACCOUNT_ID — ResMan Partners APISHEETS_WEBHOOK_URL — Apps Script /exec endpoint for the Renewal Letters sheetSHARED_SECRET — Apps Script shared secretGITHUB_TOKEN — for /api/trigger_renewals_sync to dispatch the workflowWritten by renewals_sync/sync_renewal_status.py:
| Status | Meaning |
|---|---|
Renewed | Signed renewal entry in lease.history[], actual ≥ offered |
Lower Offer | Signed renewal, actual < offered |
Pending Renewal | History entry with status == "Pending Renewal" AND signedDate null |
Current | No renewal entry yet; still live, no notice |
MTM | scheduledMoveOutDate > leaseEndDate AND no moveOutDate yet (month-to-month) |
NTV | noticeToVacateDate set, no moveOutDate yet |
Moved Out, Under Eviction, Evicted, Cancelled, Denied | Mapped straight from lease.leaseStatus |
The "best renewal candidate" picker handles the common ResMan case of a Cancelled sibling sitting next to a signed Current renewal with the same startDate: it ranks (signed AND not cancelled) > signed > not cancelled > latest applicationDate.
? tooltip (just the calculation). Double-click opens a drill-in modal.Renewed of total = (Renewed + Lower Offer) / Total% Renewable = (Pending Renewal + Current) / TotalAvg Δ (signed) = mean of Actual Δ $ and % across signed renewals
Every cell is clickable → drill-in modal.Opens from the "Renewal Offers" header button. Reads the same filtered subset as the main dashboard but shows an offer-focused grid:
Property · Unit · Lease End (all frozen) · Resident · Current Rent · Current Total · Rent Δ · Total Δ · Rent Offer · Utility · Valet · Pest · Amenity · Internet · Total Utilities · Pet · Parking · WD · Storage · Total Other · Total Offer
A checkbox column + select-all header lets the user pick specific residents. "Print Letters" button prints all selected (or all visible if none selected) by opening a single new tab with every letter stacked and separated by page-break-before: always — one Ctrl+P produces a multi-page PDF. Totals footer sums every $ column (deltas sum too).
Each resident name is clickable → opens that resident's formal renewal letter in a new tab (A4, print-ready). The letter template is inlined into renewals.html as LETTER_TEMPLATE; placeholder data is produced by buildLetterContext(), which ports letters/generate_letters.py logic (first-name split, charges rows, deposit sub-block). The SNS logo is self-hosted at /SNS-letter-logo.jpg.
renewals_sync/ directoryCopies of the upstream Renewal Letters pipeline kept in-repo so the GitHub Actions workflow can run without external deps:
| File | Role |
|---|---|
sync_all.py | Loops through 10 properties → calls sync_renewal_status.run_sync() on each |
sync_renewal_status.py | ResMan → Sheet: writes Renewal Status, Actual Renewal, Lease ID |
create_monday_items.py | ResMan API helpers (fetch_leases, get_property_id) — Monday code dead but harmless |
sheets_client.py | Apps Script webhook wrapper |
sheet_schema.py | Canonical headers + tab names |
write_renewals_json.py | NEW — fetches each sheet tab, joins ResMan lease data by Lease ID, writes renewals_data.json with four extra columns (NTV Date, Move Out Date (falls back to scheduled), Lease End Date, Renewal Signed Date) |
Important: renewals_sync/sync_renewal_status.py is a hand-maintained copy of the one in C:\Users\ymosh\Claude\ROI\Renewal Letters. When fixing a status-detection bug, mirror the fix to both.
bg-slate-100 (not slate-50); cards + modals + tooltips use #fafbfc via a scoped html:not(.dark) .bg-white { … !important } override. Dark mode uses the normal dark:bg-slate-800 for cards..fancy-scroll and the global * scrollbar use #94a3b8 → #64748b light / #64748b → #475569 dark.#64748b light / #94a3b8 dark). No native accent-color.#06b6d4 cyan is the brand accent on this page: MTM pill, chart 3 bars, chart 4 line, filter count badge, Renewal Offers button hover.# Static files + /api/*.py handlers, mimics Vercel routing
python dev_server.py
# → http://localhost:3000/renewals
Needs .env.local with SHEETS_WEBHOOK_URL and SHARED_SECRET (plus RESMAN_API_KEY/PARTNER_ID/ACCOUNT_ID if testing write_renewals_json.py locally).
Generated with ResMan API + Google Sheets integration
.github/workflows/sync-renewals.yml | GitHub Actions workflow for the Renewals Dashboard refresh pipeline |
SNS-letter-logo.jpg | Self-hosted SNS logo used in the per-resident renewal letter preview |
dev_server.py | Local Python dev server that emulates Vercel routing (static files + /api/*.py handlers) |
supabase_sync.py | Maps data.json to one row per property; upserts to Supabase REST. CLI + importable. |
backfill_snapshots.py | Parses historical SNS Daily M_D_YYYY.xlsx files and upserts with source='backfill' |
query_snapshots.py | Read-only inspection of Supabase: row counts, portfolio trend, per-property deltas |
api/record_snapshot.py | Vercel POST endpoint — n8n calls this once/day at midnight ET. Fetches data.json, guards against duplicates, inserts 9 rows. |
api/skill.py | Vercel GET endpoint — serves the SNS Data Analyst Skill markdown with publishable key baked in. One URL to share with any AI. |
skills/SNS Data Analyst Skill.md | Read-only reference any AI can load to query the Supabase daily snapshots |
supabase/migrations/*.sql | DDL: table + two views + RLS + security_invoker + property_name column |
backfill/ | Drop historical xlsx files here for backfill_snapshots.py |