LCIS User Manual
Land & Commercial Investment Search β internal reference for Green Stone Properties / Adler Financial Group partners. This system auto-scrapes land and commercial listings across Virginia and NoVA daily, scores every deal automatically, and surfaces the best opportunities first.
1Getting Started
Login & Authentication
LCIS is partner-gated. Two sign-in methods are supported:
- Email + Password β enter your address and password, click Sign In
- Google SSO β click the Google button; uses your existing Google account
Your session is stored in browser localStorage. You stay logged in until you sign out via the π€ Your Name button. Clearing browser data or using incognito requires re-login.
Code Β· checkAuth() β Auth Gate
// Runs on every page load β blocks UI if not authenticated
const REQUIRE_LOGIN = true;
function checkAuth(){
const name = localStorage.getItem('re_name');
if(name){ document.getElementById('lbtn').textContent='π€ '+name.split(' ')[0]; return true; }
if(REQUIRE_LOGIN){ document.getElementById('login-overlay').style.display='flex'; return false; }
return true;
}
if(checkAuth()){ load(); initSavedSearches(); setTimeout(checkDeepLink,800); }Navigation Overview
LCIS Home β sticky header navigation, live stats bar, Top Scored Deals, and quick-access feature tiles
| Button | Function |
|---|---|
| π Home | Dashboard β stats, top deals, recently viewed, quick-access tiles |
| π² Land | Land listings with left-panel filter sidebar (counties, acres, price per acre) |
| π’ Commercial | Commercial/NNN listings with commercial-specific filters |
| πΊοΈ Map | Interactive GIS map β listing markers, parcel overlays, draw-to-search |
| β Watchlist | Your starred properties with a live badge count |
| πΏ Rural Land | One-click shortcut: loads Rural Land saved search (Clarke, Fauquier, Frederick, Rappahannock, Warren Β· 50 ac min) |
| π Comm/NNN | One-click shortcut: loads Comm/NNN saved search (all commercial, $4Mβ$20M) |
| π Redfin GIS / Crexi | Source status chips β click to open the data fetch status panel |
| π Dark | Cycles Dark β Light β System theme |
| π Alerts | Email alert setup and saved search management |
| βοΈ Admin | Manual fetch triggers and API usage meter |
| π€ Login | Shows your first name when logged in β click to sign out |
| π Manual | This page |
2Home Dashboard
Stats Bar
Five live counters. Each is clickable and navigates to a pre-filtered listing view:
π² Land
Total land listings in the database right now.
π’ Commercial
Total commercial/NNN listings currently available.
π’ New Today
Listings scraped within the last 24 hours. Click to jump to new-only view.
π‘ Price Drops
Listings where price decreased since first seen. Click to filter to drops only.
Code Β· updateStats() β Live Counters
function updateStats(){
document.getElementById('s-land').textContent = all.filter(l=>l.type==='land').length;
document.getElementById('s-comm').textContent = all.filter(l=>l.type==='commercial').length;
document.getElementById('s-new').textContent = all.filter(l=>l.is_new).length;
document.getElementById('s-drop').textContent = all.filter(l=>l.price_drop||false).length;
}β Top Scored Deals
Top 6 listings by investment score. Click View All β for the full sorted view. Click any mini-card to open the full detail modal.
Code Β· renderHomeDeals() β Top 6 by Score
function renderHomeDeals(){
const top = [...all].sort((a,b)=>(b.score||0)-(a.score||0)).slice(0,6);
// renders 6 mini cards: price, $/ac, location, grade badge, score ring
}Recently Viewed Strip
Appears after you've clicked property detail modals. Stored in sessionStorage (clears when tab closes). Click Clear to reset.
3Land Listings
Land view β filter sidebar (left), market snapshot bar, county filter chips, and scored property cards with grade badges and score rings
Filter Panel
| Filter | What It Does | Default |
|---|---|---|
| Min Acres | Range slider + number input. Hides listings below this acreage. | 50 ac |
| Max Price | Hides listings above this total asking price. | No limit |
| Max $/Acre | Range slider + number input. Key value-screening filter β hides overpriced-per-acre listings instantly. | No limit |
| Show | All Listings Β· New Only π’ Β· Price Drops π‘ | All Listings |
| Apply | Re-runs all filters and re-renders cards. | β |
| Reset | Clears all filters to defaults. | β |
Code Β· applyF() β Filter Engine
function applyF(){
let list = all.filter(l=>l.type===ctype);
if(ctype==='land'&&counties.length)
list=list.filter(l=>counties.some(c=>(l.county||'').toLowerCase().includes(c)));
const ma=+document.getElementById('f-acres').value||0;
const mp=+document.getElementById('f-maxp-l').value||Infinity;
const pp=+document.getElementById('f-ppa').value||Infinity;
if(ma) list=list.filter(l=>(l.acres||0)>=ma);
if(mp!l.price||l.price<=mp);
if(pp{const v=l.acres&&l.price?l.price/l.acres:null;return !v||v<=pp;});
filtered=list;
renderCards(sortL());
renderMarketSnap(filtered);
updateFilterCount(filtered.length);
} County Selection
Two collapsible sections. Click a header to expand/collapse:
- Rural VA (Land) β Clarke, Fauquier, Frederick, Rappahannock, Warren, Culpeper, Madison, Shenandoah. Each shows a live listing count.
- NoVA / DC (Commercial) β Northern Virginia / DC market counties.
Click All/None to toggle all counties. The county bar above cards lets you click a chip to isolate that county instantly. Click All β to clear.
Sorting & Market Snapshot
Sort dropdown: Best Score Β· Price β/β Β· $/Acre β Β· Newest
The Market Snapshot bar above cards shows: Median Price Β· Avg $/Acre Β· Avg Days on Market Β· New (30d) Β· Total Results. Updates live with every filter change.
Code Β· renderMarketSnap() β Aggregate Stats Bar
function renderMarketSnap(list){
const sorted=list.filter(l=>l.price>0).map(l=>l.price).sort((a,b)=>a-b);
const med=sorted[Math.floor(sorted.length/2)];
const ppas=list.filter(l=>l.acres&&l.price).map(l=>l.price/l.acres);
const avgPPA=ppas.length?Math.round(ppas.reduce((a,b)=>a+b,0)/ppas.length):null;
// renders: Median Price Β· Avg $/Acre Β· Avg DOM Β· New(30d) Β· Results
}4Commercial Listings
Click π’ Commercial in the header or the tab above cards. The filter sidebar updates to commercial-specific fields.
| Filter | What It Does | Default |
|---|---|---|
| Min Price | Hides listings below this total price. | $4,000,000 |
| Max Price | Caps listings above this total price. | $20,000,000 |
| Type | All Types Β· Office Β· Retail Β· Industrial Β· Flex Β· Multifamily Β· Self-Storage Β· Specialty Β· Land (Comm) | All Types |
| Min Sqft | Minimum building square footage. | No limit |
| Max $/Sqft | Price-per-sqft cap β key for pre-screening cap rate viability. | No limit |
Code Β· Commercial Filter Logic
// Applied inside applyF() when ctype === 'commercial'
const mn=+document.getElementById('f-minp-c').value||0;
const mx=+document.getElementById('f-maxp-c').value||Infinity;
const ms=+document.getElementById('f-sqft').value||0;
const ct=document.getElementById('f-ctype').value;
if(mn) list=list.filter(l=>!l.price||l.price>=mn);
if(mx!l.price||l.price<=mx);
if(ms) list=list.filter(l=>(l.sqft||0)>=ms);
if(ct) list=list.filter(l=>(l.property_type||'').toLowerCase().includes(ct.toLowerCase())); 5Property Cards
Each listing renders as a card designed to surface critical investment signals at a glance.
Score Ring & Grade Badge
Bottom-right: Score Ring β circular SVG arc (0β100). Bottom-left: Grade Badge:
- A 80β100 β Exceptional. Investigate immediately.
- B 60β79 β Strong deal, worth a closer look.
- C 40β59 β Average. Review carefully before pursuing.
- D 0β39 β Below average. Low priority.
Code Β· gradeLabel() + scoreRing()
function gradeLabel(sc){
if(sc===null)return'';
const g=sc>=80?'A':sc>=60?'B':sc>=40?'C':'D';
return`<span class="grade-badge gr-${g.toLowerCase()}">${g}</span>`;
}
function scoreRing(sc){
if(sc===null)return'';
const r=15,circ=2*Math.PI*r,offset=circ-(sc/100)*circ;
const tier=sc>=80?'a':sc>=60?'b':sc>=40?'c':'d';
return`<div class="score-ring"><svg ...stroke-dashoffset arc...></svg>
<div class="sr-val">${sc}</div></div>`;
}PPA Hero & Percentile Badge
For land cards, Price Per Acre (PPA) is the primary value signal. The Percentile Badge compares to all current filtered land:
- Below avg $/ac β priced below the median PPA (buy signal)
- At avg $/ac β near the median
- Above avg $/ac β premium vs. comparable parcels
For commercial cards, a Cap Badge shows cap rate when available from Crexi data.
Card Action Buttons
| Icon | Action |
|---|---|
| π Pin | Jump to this listing on the GIS Map |
| β / β Star | Add to / remove from Watchlist. Fills solid when watching. |
| β Compare | Select up to 4 listings. Comparison bar appears at page bottom when 2+ are selected. |
| View β | Opens original listing on Redfin or Crexi in a new tab |
Click anywhere on the card body to open the full Property Detail modal.
Code Β· renderCards() β Card Renderer
function renderCards(list){
const g=document.getElementById('cg');
g.innerHTML=list.map(l=>{
const sc=l.score?Math.round(l.score*100):null;
const isL=l.type==='land';
const rawPPA=l.acres&&l.price?l.price/l.acres:null;
const ppu=isL?(rawPPA?'$'+Math.round(rawPPA).toLocaleString()+'/ac':'')
:(l.sqft?'$'+Math.round(l.price/l.sqft)+'/sqft':'');
return`<div class="lc" onclick="openDetail(${l.id})">
${ppu?`<div class="ppa-hero">${ppu} ${isL?pctBadge(rawPPA):capBadge(...)}</div>`:''}
${gradeLabel(sc)} ${scoreRing(sc)}
</div>`;
}).join('');
observeCards(); // IntersectionObserver lazy-loads photos on scroll
}6Property Detail
Property detail modal β hero photo, metrics grid, Quick Investment Calc, and action buttons (Watch / Map / Share / Print)
Click any property card to open a slide-up detail modal β your primary single-property due diligence view.
Metrics Grid
PRICE
Total asking price.
ACRES / SQFT
Total acreage (land) or building sqft (commercial).
$/ACRE or $/SQFT
Price per acre or per square foot.
COUNTY
County or jurisdiction.
SCORE
Raw investment score out of 100. See Section 12 for methodology.
LISTED
Days on market, or "Today" if scraped within 24 hours.
β‘ Quick Investment Calc
Auto-calculated for land listings:
- Price/Acre β restated $/ac for quick reference
- Est. 20-ac lots β how many 20-acre sub-lots, and retail value each at 2.5Γ markup
- Est. 5-ac lots β how many 5-acre sub-lots and retail value each
- Est. Annual Tax β rough estimate at 0.85% of purchase price per year
Suggested additions (placeholders for future builds):
- Seller financing availability flag
- Zoning classification (A-1, A-2, R-1) from county GIS
- Timber value estimate per acre for forested parcels
- Comparable sold parcels within 10 miles
- Agricultural land-use program eligibility (VA tax reduction)
- Utilities availability (power, water, sewer access)
Code Β· openDetail() β Investment Calc Logic
function openDetail(id){
const l=all.find(x=>x.id===id);
currentDetailId=id;
trackView(id); // adds to Recently Viewed strip
const ppa=l.price/l.acres;
const lots20=Math.floor(l.acres/20);
const lots5=Math.floor(l.acres/5);
const val20=lots20*ppa*20*2.5; // 2.5Γ markup on raw land cost
const val5=lots5*ppa*5*2.5;
const taxEst=l.price*0.0085; // 0.85% annual tax estimate
document.getElementById('detail-modal').style.display='flex';
document.getElementById('detail-backdrop').style.display='block';
}Detail Action Buttons
| Button | Action |
|---|---|
| View Listing β | Opens original listing on Redfin or Crexi in a new tab |
| β Watch / Watching | Add/remove from Watchlist |
| π | Closes modal, pans GIS map to this property's coordinates |
| π | Copies a deep link URL to clipboard β share this exact listing with a partner |
| π¨ | Prints a formatted one-page property sheet (hides all navigation chrome) |
Keyboard nav while modal is open: β next listing Β· β previous Β· Esc close
7GIS Map View
GIS Map β listing markers (green = land, blue = commercial), county parcel overlays, and right-side layer control panel
Click πΊοΈ Map to open the interactive GIS map (Leaflet). All loaded listings appear as clickable markers.
Basemap Options
- πΊ Street β OpenStreetMap road view (default)
- π° Satellite β aerial imagery β use for visual site assessment
- β° Topo β topographic elevation β useful for evaluating terrain and drainage
Map Layers Panel
| Layer | What It Shows | Investment Use |
|---|---|---|
| Fauquier / Frederick / Warren / Clarke / Rappahannock | County parcel boundaries. Click any parcel for ownership data. | Identify adjacent parcels, compare sizes, research neighboring ownership |
| Floodplain (FEMA) | FEMA flood zones β Zone AE = high risk, Zone X = low risk | Critical due diligence. Heavy Zone AE can eliminate subdivision potential and spike insurance costs. |
| Power Lines | High-voltage transmission corridors | Easement identification β may prevent subdivision or building |
| Railroad | Rail corridors and rights-of-way | Noise impact for residential land; access advantage for industrial commercial |
| Historic Sites | Historic designations and registered sites | May restrict development or ground disturbance |
| Churches | Church locations | Community proximity indicator for rural subdivision marketing |
| Listings | Your LCIS listing markers | Toggle off to focus on overlay analysis without marker clutter |
Draw Search Area
- β Draw β draw a polygon on the map. A π Search this area button appears. Click it to filter listings to only those within your drawn boundary.
- π Clear β removes the boundary and restores all listings
Click any marker for a popup with key data and a Details button that opens the full property detail modal.
Code Β· initMap() + togParcel() + applyDrawFilter()
function initMap(){
map=L.map('map').setView([38.8,-77.8],9);
// basemap tiles, Leaflet.draw toolbar, zoom controls
}
async function togParcel(county,on){
if(on){
const geo=await fetch(`/api/re/parcels/${county}`).then(r=>r.json());
parcelLayers[county]=L.geoJSON(geo).addTo(map);
} else { parcelLayers[county]?.remove(); }
}
function applyDrawFilter(){
const bounds=drawLayer.getBounds();
filtered=all.filter(l=>l.lat&&l.lng&&bounds.contains([l.lat,l.lng]));
renderCards(filtered);
}8Watchlist
Click β Watchlist to view all starred properties. The badge counter shows how many you're tracking.
- Add: click β on any card, or β Watch in the detail modal
- Remove: click the filled β again
- Clear all: Clear All on the Watchlist page
- Storage:
localStoragekeylcis-watchβ persists across browser sessions
Suggested enhancements (placeholders):
- Export Watchlist to CSV for partner sharing
- Notes field per watched listing
- Price-change alert when a watched listing drops
- "Acquired" status to convert to portfolio tracking
Code Β· toggleWatch() β Watchlist localStorage
let watchlist=new Set(JSON.parse(localStorage.getItem('lcis-watch')||'[]'));
function toggleWatch(id,btn){
const s=String(id);
if(watchlist.has(s))watchlist.delete(s); else watchlist.add(s);
localStorage.setItem('lcis-watch',JSON.stringify([...watchlist]));
updateWatchCount();
if(btn) btn.textContent=isWatched(id)?'β
Watching':'β Watch';
}9Saved Searches & Analysis
LCIS has two tiers of search memory: Saved Searches (persistent forever) and Recent Searches (auto-expire after 30 days). Both are accessible from the π Analysis page.
Pre-Loaded Searches
- πΏ Rural Land β Land type, 50 ac min, Clarke + Fauquier + Frederick + Rappahannock + Warren
- π Comm/NNN β Commercial type, all counties, $4Mβ$20M price range
Saving a New Search
- Set your filters exactly as desired on the Land or Commercial page
- Click πΎ Save in the filter sidebar
- Name it when prompted β saved to the database and
localStoragesimultaneously
Managing Saved Searches
Navigate to π Alerts β Saved Searches panel:
| Button | Action |
|---|---|
| Load | Applies this search's filters and switches to the Listings view |
| β | Rename this saved search |
| β | Delete from the database and localStorage |
Recent Searches (Auto-Log)
Every time you apply filters and results render, LCIS silently logs the search to the database with a 30-day expiry. No action needed β it happens automatically. You can revisit any recent search from the Analysis page by clicking it to instantly reload those filters.
π Analysis Page β Compare Two Searches
Click π Analysis in the top nav to open the Analysis page. From here you can:
- Recent searches β click any row to reload that filter state instantly
- Saved searches β click any row to reload (same as loading from Alerts page)
- Compare A vs B β pick two searches from the dropdowns, click Compare β
The comparison table shows all listings from both searches side-by-side, color-coded: Both in both searches, A only exclusive to A, B only exclusive to B. Click any row to open the full listing detail.
Code Β· Saved Search CRUD β Save / Edit / Delete
// Save current filter state to DB + localStorage
async function saveSearchToDB(name,stateJson,type='both'){
const res=await fetch('/api/re/saved-searches?user_id=1',{
method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({name,type,state_json:stateJson})
}).then(r=>r.json());
const dbId=res.id; // stored for future delete/edit
}
function deleteSearch(name){
const s=savedSearches.find(x=>x.name===name);
fetch(`/api/re/saved-searches/${s.dbId}`,{method:'DELETE'});
savedSearches=savedSearches.filter(x=>x.name!==name);
localStorage.setItem('lcis-searches',JSON.stringify(savedSearches));
renderSavedList();
}
function editSearch(oldName){
const newName=prompt('New name:',oldName);
// deletes old DB record by dbId, saves new record with newName
}10Email Alerts
Click π Alerts to configure automated deal notifications.
| Field | What to Enter |
|---|---|
| Name | Your name (pre-filled if previously saved) |
| Where alerts are delivered | |
| π’ New listings | Alert when new listings appear |
| π‘ Price drops | Alert when prices decrease on existing listings |
| π Weekly digest | Weekly summary of all current top deals |
Click Save Alerts. Alerts send daily at 8am when new matches are found.
Manual Controls
- βΆ Send Now β immediately dispatches alerts. Use after a manual data fetch to push fresh deals without waiting for the daily schedule.
- π Geocode Listings β fills in lat/lng for listings missing map coordinates (~1/sec via Nominatim). Run after a fetch to ensure new listings appear on the map.
Code Β· saveAlert() + triggerAlerts() + triggerGeocode()
async function saveAlert(){
await fetch('/api/re/alerts/subscribe',{method:'POST',
headers:{'Content-Type':'application/json'},
body:JSON.stringify({name,email,on_new,on_drop,on_weekly})});
}
async function triggerAlerts(){
const d=await fetch('/api/re/alerts/send',{method:'POST'}).then(r=>r.json());
alert(d.ok?'Alert dispatch queued β check logs in ~30s.':'Error: '+JSON.stringify(d));
}
async function triggerGeocode(){
const d=await fetch('/api/re/geocode/run',{method:'POST'}).then(r=>r.json());
alert(d.ok?`Geocoding ${d.queued} listings in background (~1/sec).`:'Error.');
}11Data Sources & Fetching
LCIS aggregates listings from multiple data sources. The table below is the complete picture β what's running, what's blocked, what's available if you want to unlock it, and what it costs.
Active Pipelines β Running Now
| Source | Type | Cost | Coverage | Schedule | On-Demand |
|---|---|---|---|---|---|
| π’ Redfin GIS redfin.com public API |
π² Land | FREE $0/mo β public GIS endpoint |
VA β all 95 counties WV β all 55 counties MD β all 24 counties DC β District of Columbia 175 total regions Β· min 50 acres filter |
Daily 6am ET + manual via Admin |
POST /api/re/search/countyAny county by name, any min_acres |
| π΅ Crexi API api.crexi.com |
π’ Commercial | FREE $0/mo β public JSON endpoint |
VA + National NNN / Commercial Β· $4Mβ$20M Β· Industrial, Retail, Office, Mixed-Use |
Daily 6am ET + manual via Admin |
β |
|
π‘ LoopNet loopnet.com (Apify actor) β οΈ Akamai risk β may return empty |
π’ Commercial |
FREE β $0 hard cap Max 10 items/URL Β· free actors only Akamai block = empty result, $0 spent What free tier gets you: address, price range, property type, photos NOT included free: broker phone, cap rate, NOI, detailed financials LoopNet Standard ~$299/mo adds: full contacts, financials CoStar Suite $1Kβ5K+/mo adds: analytics, comps, market data |
VA Β· MD Β· DC Β· WV NNN + Commercial for-sale Β· CoStar subsidiary β broker-listed deals that don't appear on Crexi |
Daily 6am ET Runs after Crexi |
β |
Installed but Blocked β Need Subscription to Activate
| Source | Type | Why Blocked | Fix Cost | Coverage |
|---|---|---|---|---|
| β LandWatch / Land.com | π² Land | Cloudflare bot protection | $50β200/mo RapidAPI key Scraper built and ready to activate |
National rural land Β· Best VA/WV source outside Redfin |
| β LandsOfAmerica | π² Land | Akamai bot protection | $50β200/mo RapidAPI key Same $29/mo ScraperAPI proxy unlocks both |
National rural land Β· Strong Mid-Atlantic Β· FSBO + broker rural listings |
π Next Step β Unlock Non-MLS Land Sources
Every major free rural land site is now behind Cloudflare or Akamai. We tested 6 alternatives (LandFlip, LandHub, FarmFlip, LandsOfAmerica, LandWatch, Craigslist) β all blocked at the API layer even with a headless browser.
"The honest picture: Every major free land site is now behind Cloudflare. The only true unlock is ScraperAPI at $29/mo β rotating residential proxy that bypasses CF, activates both LandWatch and LandsOfAmerica simultaneously. That would immediately add ~300β500 VA rural FSBO + broker listings that Redfin misses."
The single most cost-effective unlock is:
| Solution | Cost | What it unlocks | What you gain |
|---|---|---|---|
| ScraperAPI rotating residential proxy scraperapi.com |
$29/mo 250K requests/mo |
β
LandWatch / Land.com β scraper already built β LandsOfAmerica β scraper already built Both activate the same day you add the API key |
~300β500 new VA/WV rural land listings per daily run that never appear on Redfin β FSBO farms, timber tracts, hunting land from owners and specialty land brokers. One key, two sources, same day. |
To activate: get key at scraperapi.com β set SCRAPER_API_KEY env var in docker-compose.yml β both scrapers auto-activate on next daily run. No code changes needed.
Planned β Not Yet Built
| Source | Type | Cost | Why It Matters | Priority |
|---|---|---|---|---|
| β Bright MLS / MRIS | π Both | IDX agreement with broker/agent No cash cost if you have a contact |
Full Mid-Atlantic MLS β every listed property, not just Redfin's subset | π΄ Highest |
| Zillow RapidAPI | π Both | $30β100/mo RapidAPI | Best for comps, price history, Zestimate cross-check | π‘ High |
| Realtor.com RapidAPI | π Both | $30β100/mo RapidAPI | NAR-affiliated MLS data Β· Good secondary cross-check | π‘ Medium |
| ScraperAPI Proxy | π§ Infrastructure | $29/mo (250K requests) | Rotating residential proxy β unlocks LandWatch + LandsOfAmerica immediately | π‘ Medium β activates 2 blocked sources at once |
| CoStar API | π’ Commercial | $1,000β5,000+/mo enterprise | Gold standard for commercial RE data β LoopNet is their consumer product | π’ Low β use Crexi first |
On-Demand County Search
You can search any county in VA, WV, MD, or DC right now β not just the configured daily counties. No data is stored β results are returned live from Redfin.
- Use the Admin panel on the map or listings page to trigger a county-specific fetch
- API:
POST /api/re/search/countywith{"county":"culpeper","state":"va","min_acres":50} - List all counties for a state:
GET /api/re/counties?state=wv
Manual Fetch (Admin Panel)
- Redfin Land Fetch β triggers immediate scrape across all configured counties. Results in ~60 seconds. Runs all 175 regions so takes 2β4 minutes total.
- Crexi Commercial Fetch β pulls VA commercial listings from Crexi. Auto-paginates up to 200 results per run.
12Investment Scoring System
Every listing gets an automated Investment Score (0β100) from scorer.py. This drives card ordering, Top Scored Deals, and grade badges.
Land Score Factors
- Price per Acre vs. County Median β below the county's median PPA scores higher
- Total Acreage β larger parcels score higher (more subdivision potential)
- Days on Market β newer listings score higher (less stale inventory)
- County Desirability β Fauquier, Frederick score higher than Shenandoah
- Data Completeness β listings with photos, full address, and verified acreage score higher
Commercial Score Factors
- Cap Rate β higher cap rate = higher score when available from Crexi
- Price per Sqft vs. Market β below-market $/sqft scores higher
- Property Type Demand β Industrial and NNN Retail score higher than Office
PPA Percentile Badge
Tells you where this listing sits in the $/acre distribution of your current filtered results. A listing in the 10th percentile is cheaper than 90% of comparable land β a strong signal.
Code Β· pctBadge() β PPA Percentile
let _allPPAs=[]; // sorted array of all land PPAs in current filtered set
function pctBadge(ppa){
if(!ppa||!_allPPAs.length)return'';
const rank=_allPPAs.filter(x=>x<=ppa).length;
const pct=Math.round((rank/_allPPAs.length)*100);
if(pct<=33)return`<span class="ppa-pct low">Below avg $/ac</span>`;
if(pct<=66)return`<span class="ppa-pct mid">At avg $/ac</span>`;
return`<span class="ppa-pct high">Above avg $/ac</span>`;
}Scoring improvements (placeholders for future builds):
- Road frontage quality (paved vs. gravel vs. easement only)
- Proximity to major highways (I-66, I-81) for access value
- Agricultural exemption eligibility (VA land use tax program)
- Seller motivation signals (expired, re-listed, steep repeat reductions)
- Utilities availability (power, water, sewer β increases subdivision value significantly)
13Power Features
Keyboard Shortcuts
| Key | Action |
|---|---|
| Esc | Close the open property detail modal |
| β | Next listing in current filtered set (while detail modal is open) |
| β | Previous listing in current filtered set (while detail modal is open) |
Deep Links
Every listing has a permanent shareable URL: /?listing=ID. Click the π button in the detail modal to copy it. When opened, the app loads and auto-opens the correct detail modal.
Property Comparison Tool
Check the β checkbox on 2β4 listings. A comparison bar appears at the bottom. Click Compare for side-by-side metrics. Click β CSV to export as a spreadsheet.
Print Property Sheet
Click π¨ in the detail modal. The print stylesheet hides all navigation and renders a clean one-page property sheet with key data, photo, and investment calc β ideal for site visit packets or partner presentations.
Theme Cycling
Click π Dark to cycle: Dark β Light β System. Saved in localStorage and persists across sessions.
Code Β· Keyboard Nav + Deep Links + Compare CSV Export
// Keyboard navigation inside detail modal
document.addEventListener('keydown',e=>{
if(document.getElementById('detail-modal')?.style.display!=='none'){
if(e.key==='Escape')return closeDetail();
if(e.key==='ArrowRight'||e.key==='ArrowLeft'){
const idx=filtered.findIndex(x=>x.id===currentDetailId);
const next=e.key==='ArrowRight'?filtered[idx+1]:filtered[idx-1];
if(next!=null)openDetail(next.id);
}
}
});
function checkDeepLink(){
const lid=new URLSearchParams(window.location.search).get('listing');
if(lid&&all.find(x=>String(x.id)===lid))setTimeout(()=>openDetail(+lid),400);
}
function shareDetail(){
navigator.clipboard.writeText(window.location.origin+'/?listing='+currentDetailId);
}
function exportCompareCSV(){
const items=[...compareSet].map(id=>all.find(x=>String(x.id)===id)).filter(Boolean);
const rows=['Name,Price,Acres,PPA,County,Score,Source',
...items.map(l=>`"${l.address}",${l.price},${l.acres},`+
`${Math.round(l.price/l.acres)},${l.county},`+
`${Math.round((l.score||0)*100)},${l.source}`)];
Object.assign(document.createElement('a'),{
href:URL.createObjectURL(new Blob([rows.join('\n')],{type:'text/csv'})),
download:'compare-listings.csv'
}).click();
}14Feature Roadmap
πΈ Apify Spend Widget
A spend pill in the top navigation bar tracks your Apify API usage for the current day. It shows estimated cost and a fill bar that turns yellow (β₯70%) and red (β₯100%) as you approach the $10/day budget limit.
- The pill reads from
localStorageβ it resets at midnight - Every Apify run call in the frontend calls
logUsage(cost)which updates the pill - No server round-trip needed for display β it's purely client-side
πΌοΈ View & Cache (Listing Images)
Every View β button on listing cards and map popups now fires a background image pre-cache when clicked. The listing opens in a new tab immediately β the cache request runs silently in parallel.
- Backend endpoint
/api/img/{id}checks disk cache first β if the image exists, zero outbound request - If not cached: image is fetched via Apify proxy (never direct from India IP to CDN)
- Next time the listing card loads, the image is served from disk instantly
Built in 2026-05-02 Session
| Feature | Status | Notes |
|---|---|---|
| Google Sign-In | β Fixed | New OAuth client created in correct Google Cloud project, client ID updated. |
| Redfin CF Worker Proxy | β Built | All Redfin GIS calls now route through Cloudflare edge. India server IP has zero exposure to redfin.com. |
| Apify Spend Widget | β Built | πΈ pill in header nav. Tracks daily cost vs $10 budget. Color-coded bar. |
| View & Cache | β Built | View β fires background image pre-cache. Zero UX friction. |
| π Analysis Page | β Built | Recent searches (30-day), saved searches, A vs B comparison table. |
| Search Auto-Log | β Built | Every filter run logged to search_log DB table. 30-day TTL. Deduped. |
Still Pending
Features identified by comparing LCIS against Zillow, Redfin, LoopNet, LandWatch, LandAndFarm, and Realtor.com, filtered for investor-specific value and ranked by impact.
| Feature | Best Example | Priority | Status | Notes |
|---|---|---|---|---|
| AI Assisted Search | Perplexity, You.com | High | In Progress | Iframe browsing with auto-cache. AI sidebar (35b) guides user to best matches. CF Worker iframe proxy + Ginger LLM tunnel required. |
| Price History Chart | Zillow, Redfin | High | Not Built | price_drop flag exists in DB β extend to store full price history. Render as sparkline in the detail modal. |
| Comparable Sales (Comps) | Redfin, Zillow | High | Not Built | Show recently sold parcels within 10 miles with similar acreage. Fundamental for land valuation. Requires sold-data source (county records or Redfin sold feed). |
| Bulk CSV Export | LoopNet, LandWatch | High | Partial | Currently only exports compared listings (up to 4). Add "Export All Results" to the listings toolbar for the full filtered set. |
| Per-Listing Price Alerts | Zillow, Redfin, LoopNet | High | Partial | Current alerts cover broad events. Need per-listing alerts: "Parcel #312 dropped $100K." Ties into Watchlist. |
| Cap Rate / NOI Calculator | LoopNet, Crexi | High | Partial | Interactive calculator in commercial detail: enter projected rent + expenses β get NOI and cap rate. Currently only shows cap rate when Crexi provides it. |
| Notes per Listing | LoopNet, LandWatch | Medium | Not Built | Free-text notes on watchlisted properties. "Called agent 4/30, motivated seller, willing to carry 20%." localStorage or DB per user. |
| Demographic / Market Overlays | Redfin, LoopNet | Medium | Not Built | Add to GIS Map: income levels, population growth, employment trends by county. Helps identify emerging investment zones. |
| County Analytics Dashboard | Redfin Neighborhoods | Medium | Not Built | Per-county stats: median PPA trend over time, DOM trend, listing volume. Helps time market entry by county. |
| Investor Lead Flags | PropStream, Crexi | Medium | Not Built | Flag motivated seller signals: tax-delinquent, absentee owners, expired/re-listed, steep price reductions. Requires county tax data integration. |
| Portfolio Tracker | Airtable RE, Crexi | Medium | Not Built | Add "Acquired" status to watchlisted properties. Track acquisition price, current estimated value, equity position. |
| Zoning Data in Detail | Zillow, Redfin | Medium | Not Built | Pull county zoning (A-1, A-2, R-1) from VA county GIS portals and surface in the property detail. Critical for subdivision feasibility. |
| Drive-by Route Planner | LandWatch | Medium | Not Built | Select multiple watchlisted parcels β optimized driving route for site visits. Useful for quarterly county tours. |
| PWA / Add to Home Screen | Zillow, Redfin apps | Medium | Responsive only | Site is mobile-responsive with a bottom nav bar. Package as a PWA (service worker + manifest) for offline access and home screen icon. |
- Bulk CSV Export β 2β3 hours, immediate utility for partner sharing
- Notes per Listing β 3β4 hours, pure localStorage, high daily workflow value
- Price History Chart β 1 day, DB schema extension + sparkline in detail modal
- Per-Listing Price Alerts β 1 day, connects Watchlist to the alert system
- Comparable Sales (Comps) β 2β3 days, changes the valuation game entirely