Busabase
Busabase는 AI 에이전트를 위한 오픈소스(MIT) 데이터베이스 및 작업 공간입니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
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 |