LCIS
Redfin GIS Crexi

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.

⚠ Session Note
Sessions do not expire automatically. On shared computers, always sign out when done.
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 Dashboard

LCIS Home β€” sticky header navigation, live stats bar, Top Scored Deals, and quick-access feature tiles

ButtonFunction
🏠 HomeDashboard β€” stats, top deals, recently viewed, quick-access tiles
🌲 LandLand listings with left-panel filter sidebar (counties, acres, price per acre)
🏒 CommercialCommercial/NNN listings with commercial-specific filters
πŸ—ΊοΈ MapInteractive GIS map β€” listing markers, parcel overlays, draw-to-search
β˜… WatchlistYour starred properties with a live badge count
🌿 Rural LandOne-click shortcut: loads Rural Land saved search (Clarke, Fauquier, Frederick, Rappahannock, Warren · 50 ac min)
πŸ“‹ Comm/NNNOne-click shortcut: loads Comm/NNN saved search (all commercial, $4M–$20M)
🌐 Redfin GIS / CrexiSource status chips β€” click to open the data fetch status panel
πŸŒ™ DarkCycles Dark β†’ Light β†’ System theme
πŸ”” AlertsEmail alert setup and saved search management
βš™οΈ AdminManual fetch triggers and API usage meter
πŸ‘€ LoginShows your first name when logged in β€” click to sign out
πŸ“– ManualThis 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.

πŸ’‘ Investor Tip
Open your top candidates from the listings view, then use Recently Viewed to rapidly toggle between them in the detail modal without re-scrolling.

3Land Listings

Land Listings with Filter Panel

Land view β€” filter sidebar (left), market snapshot bar, county filter chips, and scored property cards with grade badges and score rings

Filter Panel

FilterWhat It DoesDefault
Min AcresRange slider + number input. Hides listings below this acreage.50 ac
Max PriceHides listings above this total asking price.No limit
Max $/AcreRange slider + number input. Key value-screening filter β€” hides overpriced-per-acre listings instantly.No limit
ShowAll Listings · New Only 🟒 · Price Drops 🟑All Listings
ApplyRe-runs all filters and re-renders cards.β€”
ResetClears 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.

FilterWhat It DoesDefault
Min PriceHides listings below this total price.$4,000,000
Max PriceCaps listings above this total price.$20,000,000
TypeAll Types Β· Office Β· Retail Β· Industrial Β· Flex Β· Multifamily Β· Self-Storage Β· Specialty Β· Land (Comm)All Types
Min SqftMinimum building square footage.No limit
Max $/SqftPrice-per-sqft cap β€” key for pre-screening cap rate viability.No limit
πŸ’‘ NNN Screening Workflow
Set Type β†’ Retail or Industrial. Set a Max $/Sqft ceiling. Sort by Best Score. The score factors in cap rate when available from Crexi data.
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

IconAction
πŸ“ PinJump to this listing on the GIS Map
β˜† / β˜… StarAdd to / remove from Watchlist. Fills solid when watching.
☐ CompareSelect 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

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
πŸ’‘ Reading the Investment Calc
The 2.5Γ— markup is a standard baseline for rural VA subdivision analysis β€” directional, not definitive. Actual subdivision potential depends on county zoning, road frontage, setbacks, and perc tests.

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

ButtonAction
View Listing β†—Opens original listing on Redfin or Crexi in a new tab
β˜… Watch / WatchingAdd/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 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

LayerWhat It ShowsInvestment Use
Fauquier / Frederick / Warren / Clarke / RappahannockCounty 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 riskCritical due diligence. Heavy Zone AE can eliminate subdivision potential and spike insurance costs.
Power LinesHigh-voltage transmission corridorsEasement identification β€” may prevent subdivision or building
RailroadRail corridors and rights-of-wayNoise impact for residential land; access advantage for industrial commercial
Historic SitesHistoric designations and registered sitesMay restrict development or ground disturbance
ChurchesChurch locationsCommunity proximity indicator for rural subdivision marketing
ListingsYour LCIS listing markersToggle off to focus on overlay analysis without marker clutter
⚠ Layer Performance
County parcel overlays fetch large datasets on demand. Toggle off counties you're not actively researching if the map is slow.

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: localStorage key lcis-watch β€” persists across browser sessions
πŸ’‘ Investor Tip
Watchlist survives browser closes (unlike Recently Viewed which is session-only). Use the πŸ”— deep link button to send specific listings to partners.

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

  1. Set your filters exactly as desired on the Land or Commercial page
  2. Click πŸ’Ύ Save in the filter sidebar
  3. Name it when prompted β€” saved to the database and localStorage simultaneously

Managing Saved Searches

Navigate to πŸ”” Alerts β†’ Saved Searches panel:

ButtonAction
LoadApplies 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.

Auto-log deduplication
Identical back-to-back filter states are not double-logged β€” only the first occurrence records.

πŸ“Š 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.

FieldWhat to Enter
NameYour name (pre-filled if previously saved)
EmailWhere alerts are delivered
🟒 New listingsAlert when new listings appear
🟑 Price dropsAlert when prices decrease on existing listings
πŸ“… Weekly digestWeekly 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

SourceTypeCostCoverageScheduleOn-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/county
Any 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

SourceTypeWhy BlockedFix CostCoverage
β›” 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:

SolutionCostWhat it unlocksWhat 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

SourceTypeCostWhy It MattersPriority
⭐ 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/county with {"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>`;
}
πŸ’‘ Reading the Score in Context
Score 79 (B) + 400 acres + Fauquier + 15% below median PPA = strong signal. Always cross-reference on the Map with Floodplain (FEMA) β€” Zone AE coverage can eliminate subdivision value entirely.

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

KeyAction
EscClose 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

FeatureStatusNotes
Google Sign-Inβœ… FixedNew OAuth client created in correct Google Cloud project, client ID updated.
Redfin CF Worker Proxyβœ… BuiltAll 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βœ… BuiltView β†— fires background image pre-cache. Zero UX friction.
πŸ“Š Analysis Pageβœ… BuiltRecent searches (30-day), saved searches, A vs B comparison table.
Search Auto-Logβœ… BuiltEvery 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.

FeatureBest ExamplePriorityStatusNotes
AI Assisted SearchPerplexity, You.comHighIn ProgressIframe browsing with auto-cache. AI sidebar (35b) guides user to best matches. CF Worker iframe proxy + Ginger LLM tunnel required.
Price History ChartZillow, RedfinHighNot Builtprice_drop flag exists in DB β€” extend to store full price history. Render as sparkline in the detail modal.
Comparable Sales (Comps)Redfin, ZillowHighNot BuiltShow 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 ExportLoopNet, LandWatchHighPartialCurrently only exports compared listings (up to 4). Add "Export All Results" to the listings toolbar for the full filtered set.
Per-Listing Price AlertsZillow, Redfin, LoopNetHighPartialCurrent alerts cover broad events. Need per-listing alerts: "Parcel #312 dropped $100K." Ties into Watchlist.
Cap Rate / NOI CalculatorLoopNet, CrexiHighPartialInteractive calculator in commercial detail: enter projected rent + expenses β†’ get NOI and cap rate. Currently only shows cap rate when Crexi provides it.
Notes per ListingLoopNet, LandWatchMediumNot BuiltFree-text notes on watchlisted properties. "Called agent 4/30, motivated seller, willing to carry 20%." localStorage or DB per user.
Demographic / Market OverlaysRedfin, LoopNetMediumNot BuiltAdd to GIS Map: income levels, population growth, employment trends by county. Helps identify emerging investment zones.
County Analytics DashboardRedfin NeighborhoodsMediumNot BuiltPer-county stats: median PPA trend over time, DOM trend, listing volume. Helps time market entry by county.
Investor Lead FlagsPropStream, CrexiMediumNot BuiltFlag motivated seller signals: tax-delinquent, absentee owners, expired/re-listed, steep price reductions. Requires county tax data integration.
Portfolio TrackerAirtable RE, CrexiMediumNot BuiltAdd "Acquired" status to watchlisted properties. Track acquisition price, current estimated value, equity position.
Zoning Data in DetailZillow, RedfinMediumNot BuiltPull 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 PlannerLandWatchMediumNot BuiltSelect multiple watchlisted parcels β†’ optimized driving route for site visits. Useful for quarterly county tours.
PWA / Add to Home ScreenZillow, Redfin appsMediumResponsive onlySite is mobile-responsive with a bottom nav bar. Package as a PWA (service worker + manifest) for offline access and home screen icon.
πŸ“‹ Recommended Build Order
For maximum investor ROI on development effort:
  1. Bulk CSV Export β€” 2–3 hours, immediate utility for partner sharing
  2. Notes per Listing β€” 3–4 hours, pure localStorage, high daily workflow value
  3. Price History Chart β€” 1 day, DB schema extension + sparkline in detail modal
  4. Per-Listing Price Alerts β€” 1 day, connects Watchlist to the alert system
  5. Comparable Sales (Comps) β€” 2–3 days, changes the valuation game entirely
LCIS User Manual  Β·  Green Stone Properties  Β·  Adler Financial Group