Open Net Worth
Open Net Worth is an open-source personal AI wealth assistant you run yourself: one ledger for your whole financial life, worked by an AI agent you control.
翻訳待ち
No updates detected yet. GitHub releases will appear here.
Loading the full README. The saved excerpt is shown below.
A short-term rental arbitrage analysis platform. One question drives every
screen: does the nightly revenue beat the lease? The hero metric
everywhere is breakeven occupancy — never a letter grade, never a score.
The product name is a placeholder. Change APP_NAME in config/app.ts
and the whole app follows — nothing else hardcodes it.Read the full README ↗A short-term rental arbitrage analysis platform. One question drives every screen: does the nightly revenue beat the lease? The hero metric everywhere is breakeven occupancy — never a letter grade, never a score.
The product name is a placeholder. Change
APP_NAMEinconfig/app.tsand the whole app follows — nothing else hardcodes it.
UI-only pass. Every feature is a fully interactive screen backed by typed,
seeded mock data. No database, no billing, no auth provider, no external
APIs. All data access is stubbed behind lib/data/* so real services can
drop in later without touching a component.
npm install
npm run dev # http://localhost:3000
npm test # calc engine unit tests (vitest)
npm run build # production build
Anyone can register; confirming the email is the whole of getting in.
Every account starts on the Scale plan (DEFAULT_TIER in config/app.ts,
matched by the column default in supabase/auth-schema.sql) and is
metered server-side against that plan's caps. MOCK_CHECKOUT=1 lets a
signed-in account switch plans with no payment behind it, for testing
the other tiers. ADMIN_EMAILS is optional: unset, every account sees
/admin; set, only the listed emails do.
Run supabase/support-schema.sql once, alongside auth-schema.sql.
Members open tickets at /support and the thread is the whole
conversation; replies mail both ways when RESEND_API_KEY and
ALERTS_FROM are set.
ADMIN_EMAILS behaves differently here, and deliberately so. For
/admin an unset list means everyone is staff. For the support queue an
unset list means nobody is: a ticket holds one member's private
correspondence, so with no team named, every ticket is visible only to
the member who raised it and the "All tickets" view does not exist. Name
the team in ADMIN_EMAILS — confirmed addresses only — to turn the
queue on. The two rules live side by side in lib/auth/gate.ts as
isStaff and isSupportStaff.
The tables carry row-level security with no policies, so the browser
key cannot reach them at all; every read and write goes through
/api/support/*, which is where the owner/staff decision is made. Don't
add a policy to them.
Copy .env.example to .env.local and set RENTCAST_API_KEY. With the
key present, searching a single market in Deal Finder's Location filter
(e.g. "Jacksonville") swaps that market's preview inventory for today's
actual active rentals — a gold Live chip shows the fetch time.
Without a key, or when the feed is unreachable, the same search shows the
seeded preview with an honest Preview inventory chip.
The key stays server-side (app/api/rentals holds it; the browser never
sees it) and responses cache for 24 hours per market, so the free
50-requests/month tier comfortably covers daily browsing of a handful of
markets — one request per market per day, shared by every user.
No app-wide cap unless you set one. The only limits a student meets
are their plan's — credits a month, per account, one spent the first
time a market is searched or a property analyzed. An
operator who wants a brake on the feed's bill states the plan in
RENTCAST_MONTHLY_REQUESTS, and the feed gets that many spread over 31
days, never under one a day; RENTCAST_DAILY_CAP overrides the
arithmetic outright. With a cap set, the first search of a market or
ZIP each day spends a slot, repeats ride the cache for free, failed
requests spend nothing, and past the cap new areas fall back to preview
inventory and say so until midnight UTC. (The old default assumed the
free tier — fifty a month, so one new market a day — and refused the
second market anybody opened.) The ledger lives in server memory, so
with several instances warm the true ceiling is a small multiple of the
cap — move it to a shared store (Vercel KV) if you need it exact.
The other per-area vendors — the furnished search, STR market pulls, the
comps behind an analysis — share a separate LIVE_SEARCH_DAILY_CAP
ledger, likewise off unless set.
GET /api/rentals?market=jacksonville&shape=1 reports the field names
and value types RentCast actually ships — never their values — plus a
plain verdict on whether anything in the payload can answer "is this
unit furnished".
It reads the RAW vendor rows and unions every field across ALL of them.
Both details are load-bearing: describing our mapped row would only echo
back our own field names and could never reveal a field the mapper
ignores, and JSON feeds omit null fields per row, so a description
carried by one listing in fifty is invisible in rows[0]. Either
shortcut answers the question with a confident false no.
Read proseFields (free text long enough to mine) and amenityFields
(named like amenity data, whatever its length). Both empty means the
Furnished filter cannot work on live rows and correctly disables itself.
The probe shares the feed's Data-Cache entry, so probing a market already
searched today costs no extra vendor request, and it never spends a
daily-cap slot.
Turning on Furnished with a market searched swaps the result set for Redfin's own furnished-filtered search, reached through ScraperAPI's Redfin structured endpoint. The toolbar says Furnished · via Redfin so a student always knows which inventory they're looking at.
This is the beginning of the README. Read the rest at the original. Read the full README ↗