Skip to main content
This page documents the contracts that shape every Locus scoring response. It complements the APRS data standard — that page covers the underlying record envelope; this page covers the API-surface scoring semantics.

H3 cell index

Locus uses Uber’s H3 hierarchical hexagonal grid for spatial indexing. Every score, metric, and POI lookup resolves to an H3 cell.

Resolution

ResolutionEdge length (km)Use case
63.7Metro-level summaries (San Francisco, Chicago)
80.46Neighborhood-level scoring (default for /api/score)
90.17Block-level analysis (high-res scoring profiles)
The default scoring resolution is H3 r8 — about 0.46 km hex edge, which roughly corresponds to a “block group” in US census terms. You can request higher resolution via the radius parameter — Locus aggregates child cells appropriately.

Per-metro resolution overrides

Some low-density, large-parcel metros are scored at H3 r7 (~5.16 km², ~1280 m radius) instead of the default r8 (~0.74 km², ~490 m radius). At r8 a single cell in these markets often covers only one CRE asset, which leaves signal aggregation dominated by small-n noise. Coarsening to r7 produces enough samples per cell for the score to be statistically meaningful. Metros currently overridden to r7:
  • Phoenix
  • Houston
  • Las Vegas
  • Dallas
  • San Antonio
  • Nashville
  • Jacksonville
  • Oklahoma City
  • El Paso
  • Fort Worth
Every other metro continues to score at the r8 default. The override is additive — historical scores are unaffected and no migration is needed on your side. Each row written to cell_scores is tagged with the resolution it was computed at via the resolution_variant column, so downstream consumers (rankings, MAUP ensembles, the explorer) can distinguish r7 from r8 cells. If you join scores across metros, filter or group on resolution_variant rather than assuming a single resolution.

Cell IDs in API responses

Cell IDs are the standard 15-character hex strings produced by h3-js (e.g. 8828308281fffff). Use these as opaque identifiers when caching scores client-side; Locus pins the H3 algorithm version and won’t change cell-ID semantics without a major API version bump.

Scoring profiles

The profile parameter on /api/score adjusts signal weights for a use case. Each profile is a fixed weighting — Locus does not auto-tune weights from your historical data (custom-tuned profiles are on the roadmap).
ProfileTop-weighted signalsUnderweighted signals
generalDevelopment pipeline (permits), economic strength (employment, GDP), accessibility (transit)Amenity demand, demographics, population momentum
qsrFoot traffic, demographics (income), competitor densityPermits, real estate
self_storagePopulation density, housing turnover, road accessibilityFoot traffic, business vitality
retailFoot traffic, parking availability, complementary tenantsJobs, permits
officeTransit access, daytime population, business vitalityDemographics (residential), traffic
data_centerBroadband, power infrastructure, environmental risk, accessibilityAmenity demand, business vitality
industrialRoad accessibility, workforce availability, zoning, economic strengthAmenity demand, demographics (residential)
Composite scores are 0–100 with a documented confidence interval. Sub-scores per signal group are also 0–100.

general profile weights

The general profile is tuned for generic commercial real estate (CRE) price prediction rather than any specific use case. Weights are calibrated against the empirical CRE price-correlation literature — building permit issuance, employment density change, and transit ridership are the three signals with the strongest published correlations and clearest lead times against CRE prices.
Signal groupWeightRationale
developmentPipeline0.20Building permit issuance has R² ≈ 0.55 against CRE prices on a 6–12 month lead (Conference Board LEI).
economicStrength0.20Employment density change (LEHD) shows strong correlation with retail and office CRE on a 3–9 month lead.
businessVitality0.15Establishment counts and payroll trends — directional but less validated than the top two.
accessibility0.12Transit ridership shows positive correlation with walkable commercial CRE on a 12–24 month lead.
populationMomentum0.10Migration and population change — long-lead but noisier in short windows.
demographics0.10Income, age mix, and education — slow-moving baseline, not a short-term price driver.
safetyEnvironment0.08Crime is the peer-reviewed safety predictor; flood and environmental risk feed in as constraints.
amenityDemand0.05Schools, food access, and retail amenities — important for residential but a weak signal for generic CRE.
Use a use-case profile (qsr, office, industrial, retail, data_center, self_storage) when you have a specific tenant or asset class in mind — those weights reflect industry-specific priorities rather than generic price prediction. The use-case profile weights are unchanged from prior general rebalances.

Development pipeline sub-scores

The developmentPipeline group rolls up several permit- and construction-derived sub-signals. Each subScores entry returned in the API response carries a name, a 0–100 score, a weight, and a source label.
Sub-scoreWeightSourceWhat it measures
Permit Activity0.40Building PermitsCell-level permit count and total declared valuation over the trailing 6 months. Falls back to metro-wide averages when the cell itself has zero permits.
Construction Detection0.20Sentinel-2Satellite-derived change-detection score for active construction footprints.
Land Cover Change0.20NLCDYear-over-year change in the share of developed land cover.
Opportunity Zone0.10Federal OZBinary flag for federal Qualified Opportunity Zone designation.
Permit Velocity0.05Building Permits TrendAcceleration or deceleration in permit issuance vs. the prior 6-month window.
Permit Volatility0.05Building Permits VolatilityRun-to-run variance in permit issuance; high volatility lowers the sub-score.
Permit Valuation Tier0.10AXL-5LLM-classified valuation tier (transformative, structural, stabilization) for the dominant permit mix in the cell.
Permit Scope Quality0.10AXL-108Per-permit scope_type × cost_tier quality signal — see below.

Permit Scope Quality (AXL-108)

Permit Scope Quality weights every permit in the trailing 6-month window by what the permit is for, not just how many were issued. Each permit’s scope_type and estimated_cost_tier — both extracted by an LLM-based permit classifier from the free-text description — are multiplied to yield a 0–100 contribution, and the cell’s sub-score is the average of those contributions across the window. Scope-type weights:
scope_typeWeight
new_construction1.0
addition0.7
demolition0.5
renovation0.3
repair0.1
Cost-tier multipliers:
estimated_cost_tierMultiplier
Tier 1 (highest)1.5
Tier 21.0
Tier 30.5
Tier 4 (lowest)0.25
A cell dominated by new_construction permits in the highest cost tier will trend toward 100; a cell dominated by low-cost repair permits will trend toward 0. The signal is designed to separate cells where permits indicate genuine new development from cells where permits mostly reflect maintenance churn. When the LLM extractor has not yet annotated any permit in a cell, the sub-score is omitted from subScores and AXL-108 appears in sourcesMissing for the group.
{
  "developmentPipeline": {
    "score": 71.4,
    "subScores": [
      { "name": "Permit Activity", "score": 78, "weight": 0.40, "source": "Building Permits" },
      { "name": "Permit Scope Quality", "score": 64, "weight": 0.10, "source": "AXL-108" }
    ],
    "sourcesUsed": ["Building Permits", "AXL-108"],
    "sourcesMissing": []
  }
}

Safety & environment sub-scores

The safetyEnvironment group rolls up crime, regulatory hazard maps, realized loss history, environmental burden, air quality, and 311 service-request signals.
Sub-scoreWeightSourceWhat it measures
Crime Rate0.30Local PoliceReported incidents per 1k residents in the trailing window.
Flood Risk0.20FEMAForward-looking FEMA FIRM flood-zone designation (X, A, AE, V, etc.).
Flood Loss History0.15AXL-109 (FEMA NFIP)Realized NFIP claim history for the cell’s nearest zip — see below.
Environmental Risk0.15EPA EJScreenComposite EJ index (pollution + demographic burden).
Natural Hazard Risk0.15FEMA NRINational Risk Index composite for 18 natural hazard types.
Air Quality0.10EPA AirNowTrailing AQI exposure.
Complaint Density0.05311 Service RequestsOpen-complaint volume per cell.
Complaint Velocity0.05311 Service Requests Velocity30-day change in complaint volume.
Resolution Time0.05AXL-6Average days-to-close for 311 complaints in the cell.
Complaint Trend YoY0.05AXL-107 (311 YoY)Year-over-year delta on the cell’s trailing 30-day 311 complaint density — see below.

Flood Loss History (AXL-109)

Flood Loss History is a realized-loss companion to the regulatory FEMA FIRM Flood Risk sub-score. The two signals answer different questions:
  • Flood RiskWhat does the regulatory map say this cell is? Forward-looking, redrawn on a multi-year cycle.
  • Flood Loss HistoryWhat has actually happened here? Backward-looking, derived from FEMA National Flood Insurance Program (NFIP) claims.
This distinction matters because repeat-loss corridors and flash-flood-prone areas frequently sit in non-A/V FIRM zones — the FIRM map is a snapshot of modeled hazard, while NFIP claims capture realized loss patterns including pluvial flooding, drainage failures, and stormwater backups that the FIRM zone often misses. The sub-score is computed by:
  1. Finding the nearest US zip centroid to the cell (single PostGIS nearest-neighbor lookup).
  2. Joining that zip to the aggregated NFIP claims table (677k+ raw claims rolled up to ~17.8k zip-level records).
  3. Returning a normalized exposure score in [0, 1] derived from claim count, repeat-loss policy share, and the most recent claim year.
  4. Inverting to a 0–100 safety contribution: a zip with no historic claims scores 100, a maximum-exposure zip scores 0.
When the cell’s nearest zip has no NFIP claims (common for inland low-risk geographies), the sub-score is omitted from subScores and AXL-109 (FEMA NFIP) appears in sourcesMissing for the group. NFIP coverage is US-only — international cells will always show this source as missing.
{
  "safetyEnvironment": {
    "score": 64.2,
    "confidence": 0.83,
    "subScores": [
      { "name": "Crime Rate", "score": 72, "weight": 0.30, "source": "Local Police" },
      { "name": "Flood Risk", "score": 80, "weight": 0.20, "source": "FEMA" },
      { "name": "Flood Loss History", "score": 78, "weight": 0.15, "source": "AXL-109 (FEMA NFIP)" },
      { "name": "Environmental Risk", "score": 61, "weight": 0.15, "source": "EPA EJScreen" }
    ],
    "sourcesUsed": ["Local Police", "FEMA", "AXL-109 (FEMA NFIP)", "EPA EJScreen"],
    "sourcesMissing": ["FEMA NRI", "EPA AirNow", "311 Service Requests"]
  }
}
A coastal cell in a repeat-loss corridor will typically score high on Flood Risk (because it sits in a regulatory AE or V zone) and low on Flood Loss History (because NFIP has paid out repeated claims there). Use both sub-scores together when ranking cells for hazard-sensitive use cases — relying on the FIRM zone alone will under-flag the worst pluvial-flood corridors.

Complaint Trend YoY (AXL-107)

Complaint Trend YoY is a directional companion to the volume-based Complaint Density and Complaint Velocity sub-scores. Density measures how loud a cell is right now; velocity measures the 30-day change; the YoY delta measures whether that loudness is rising or falling against the same window one year ago — the slow-moving signal that volume and short-window velocity both miss. The sub-score is computed in-app from the same service_requests_311 rows the scorer already pulls for density and resolution time, so no additional data source is required:
  1. Count 311 complaints in the trailing 30 days (density_30d).
  2. Count 311 complaints in the matching 30-day window one year prior (the 335-to-395-day window).
  3. Compute the YoY delta as (current − prior) / max(prior, 1), capped at +5× to bound runaway ratios in cells with a near-zero prior baseline.
  4. Map the delta onto a 0–100 safety contribution: −1 (complaints down 100%) → 100, +3 (complaints up 3×) → 0, with values above +3× saturating at 0.
Negative YoY (complaints trending down) lifts the cell’s safety score; positive YoY (complaints rising) penalizes it. Rising 311 density is treated as a Pioneer Stage 1 signal — the same complaint-acceleration pattern that typically precedes pioneer-business clustering by 12–24 months and feeds the Pioneer Signal cascade. When both the current and prior 30-day windows are empty for a cell, the sub-score is omitted from subScores and AXL-107 (311 YoY) appears in sourcesMissing for the group rather than emitting a misleading “improving” signal from a quiet cell with no complaint history. Cells in metros without 311 ingestion will always show this source as missing.
{
  "safetyEnvironment": {
    "score": 67.1,
    "subScores": [
      { "name": "Crime Rate", "score": 72, "weight": 0.30, "source": "Local Police" },
      { "name": "Complaint Density", "score": 64, "weight": 0.05, "source": "311 Service Requests" },
      { "name": "Complaint Velocity", "score": 58, "weight": 0.05, "source": "311 Service Requests Velocity" },
      { "name": "Complaint Trend YoY", "score": 81, "weight": 0.05, "source": "AXL-107 (311 YoY)" }
    ],
    "sourcesUsed": ["Local Police", "311 Service Requests", "311 Service Requests Velocity", "AXL-107 (311 YoY)"]
  }
}
Read all four 311 sub-scores together when interpreting a cell: a cell with high Complaint Density and a high (improving) Complaint Trend YoY is loud-but-getting-quieter, while a cell with low density and a low (deteriorating) trend score is quiet-but-getting-louder — the kind of leading indicator that volume alone obscures.

Confidence semantics

Every Locus response includes a confidence field (0.0–1.0) representing the ratio of expected data sources that returned data for the location.
{
  "composite": 78.4,
  "confidence": 0.92,
  "sourcesUsed": 36,
  "sourcesTotal": 43
}
  • 0.9+ (high) — almost all data sources contributed; treat as authoritative.
  • 0.7–0.9 (medium) — significant signals present; treat as directional, not exact.
  • 0.5–0.7 (low) — sparse coverage; useful for filtering but not for ranking.
  • <0.5 (very low) — Locus returns the score for transparency but it should not be used for production decisions.
Confidence varies by metro. Major US metros (top 50 MSAs) consistently exceed 0.85. Rural areas, US territories, and international markets often fall below 0.7.

Coverage guards on signal groups

Some signal groups include a minimum-source guard to prevent a single universally-available data source from dominating the group score for cells where every other source is missing. When a group’s coverage guard isn’t met, the group emits a neutral no-signal score of 50 and a confidence of 0, with an empty subScores array — instead of returning a misleadingly high or low score derived from one input.
Signal groupMinimum sub-signalsReason
safetyEnvironment2FEMA flood zone is available for almost every US cell (typically X = 100). Without a guard, cells with no crime, EJScreen, NRI, AQI, NFIP loss history, or 311 data would score 100/100 on safety purely because of flood-zone coverage.
All other groups1 (no guard)These groups don’t currently have a high-scoring, universally-available source that would dominate the average.
Example response for a rural cell where only FEMA flood zone is available:
{
  "safetyEnvironment": {
    "score": 50,
    "confidence": 0,
    "subScores": [],
    "sourcesUsed": [],
    "sourcesMissing": ["Local Police", "AXL-109 (FEMA NFIP)", "EPA EJScreen", "FEMA NRI", "EPA AirNow", "311 Service Requests"]
  }
}
Treat a safetyEnvironment.score of 50 paired with confidence: 0 as “no safety signal available” — not as “neutral safety.” The 50 is a placeholder so the composite score can still be computed; it is not a measurement. Use the confidence field to filter these cells out of safety-sensitive ranking lists.

Pioneer Signal and early-stage gentrification indicator

Locus includes a Pioneer Signal detection system that identifies early signs of neighborhood transformation. The system tracks a multi-stage cascade that typically precedes gentrification by 12–24 months:
  1. Pioneer businesses — new specialty coffee shops, art galleries, or co-working spaces appear in a previously underserved area.
  2. Council language shift — city council meeting minutes begin referencing “revitalization,” “mixed-use,” or “transit-oriented development” for the area.
  3. Permit acceleration — building permit velocity increases, particularly renovation and change-of-use permits.
  4. Rezoning activity — formal rezoning applications or variance requests are filed.
These stages feed into the Early-Stage Gentrification Indicator (ESGI), a score returned alongside the composite score in scoring responses. The ESGI combines causal edge attribution, upzoning probability, litigation risk, and spatial spillover effects to quantify how likely a cell is to experience rapid value change.
{
  "composite": 78.4,
  "esgi": {
    "score": 0.72,
    "stage": "permit_acceleration",
    "upzoning_probability": 0.45,
    "litigation_risk": 0.12,
    "spatial_spillover": 0.31
  }
}
FieldTypeDescription
esgi.scorefloatESGI score from 0.0 to 1.0 indicating transformation likelihood.
esgi.stagestringCurrent cascade stage: pioneer_businesses, council_language_shift, permit_acceleration, or rezoning_activity.
esgi.upzoning_probabilityfloatProbability that the cell will be upzoned within 12 months.
esgi.litigation_riskfloatRisk of development delays from litigation or community opposition.
esgi.spatial_spilloverfloatInfluence from transformation activity in neighboring cells.

Uncertainty quantification

Scoring responses include uncertainty metadata that helps you assess how much to trust a given score. Locus runs a Monte Carlo simulation (96 samples) with confidence-aware noise injection to produce:
  • Confidence intervals — geo-conformal prediction intervals at 80% and 90% coverage.
  • Sobol sensitivity indices — first-order and total-order indices showing which signal groups contribute most to score variance.
  • Epistemic flags — signals where data is sparse or conflicting, flagged for transparency.
  • Spatial Sharpe ratio — a risk-adjusted score metric analogous to a financial Sharpe ratio, indicating score stability relative to spatial neighbors.
Request uncertainty data by adding include=uncertainty to any scoring endpoint:
curl "https://axiomlocus.io/api/score?lat=37.7749&lng=-122.4194&include=uncertainty"
{
  "composite": 78.4,
  "confidence": 0.92,
  "uncertainty": {
    "ci_80": [72.1, 84.7],
    "ci_90": [69.5, 87.3],
    "spatial_sharpe": 1.42,
    "sobol_first_order": {
      "businessVitality": 0.28,
      "developmentPipeline": 0.22,
      "demographics": 0.15,
      "accessibility": 0.12,
      "economicStrength": 0.10,
      "safetyEnvironment": 0.07,
      "populationMomentum": 0.04,
      "amenityDemand": 0.02
    },
    "epistemic_flags": ["populationMomentum"]
  }
}
FieldTypeDescription
uncertainty.ci_80array80% confidence interval [lower, upper] for the composite score.
uncertainty.ci_90array90% confidence interval [lower, upper].
uncertainty.spatial_sharpefloatRisk-adjusted score relative to neighboring cells. Values above 1.0 indicate the score is stable compared to peers.
uncertainty.sobol_first_orderobjectFirst-order Sobol indices per signal group. Higher values mean that group drives more score variance.
uncertainty.epistemic_flagsarraySignal groups with sparse or conflicting data.

Per-cell consensus class

The uncertainty payload tells you how noisy a score is given the data feeding it. The consensus class answers a different question: would this score still hold if Locus had aggregated to a different grid? It is a per-cell sensitivity check against the Modifiable Areal Unit Problem (MAUP) — the well-known finding that spatial statistics can move materially when the underlying grid changes. Locus computes a consensus_class for every cell by comparing the cell’s percentile rank within its metro at the default resolution against the percentile rank of the parent (coarser) hex it sits inside. When the two ranks agree the score is robust to grid choice; when they diverge the score is at least partly a grid artifact. The classification is surfaced on /api/cells/detail (the endpoint that powers the Explorer’s cell-detail panel) as three fields:
FieldTypeDescription
consensus_classstring | nullOne of stable_core, ambiguity_shell, stable_non_signal, or null if the cell has not yet been classified.
support_stabilityfloat | nullAgreement between the cell’s resolution-8 percentile and its resolution-7 parent percentile within the metro. 1.0 = perfect agreement, 0.0 = the two supports disagree completely.
consensus_computed_atstring | nullISO-8601 timestamp of the most recent classification run for this cell.

Class definitions

ClassWhen it firesHow to read it
stable_coresupport_stability ≥ 0.80 and the cell sits above the bottom quartile of its metro.The score is robust to grid choice. Treat as a high-confidence ranking.
ambiguity_shell0.20 ≤ support_stability < 0.80.The cell ranks materially differently when re-aggregated to a coarser grid. Use the score directionally and pair it with neighbors before acting.
stable_non_signalThe cell is consistently in the bottom quartile of its metro across both supports.Genuinely quiet. The low score is not a grid artifact — it is the same answer at every resolution.
A cell shows consensus_class: null until the classification job has populated its row. Treat null the same way you’d treat confidence: null — render no robustness badge rather than guessing.

Example response

{
  "h3_index": "882a100d63fffff",
  "composite_score": 78.4,
  "support_stability": 0.92,
  "consensus_class": "stable_core",
  "consensus_computed_at": "2026-04-29T20:14:00Z"
}

When to use it

  • Ranking and shortlisting. Filter to consensus_class = stable_core when you need the most defensible top-N list — those cells survive a grid change.
  • Risk-flagging the long tail. Cells in ambiguity_shell are the cells most worth a human review before commitment. They typically sit on tier boundaries where small grid choices flip rankings.
  • Suppressing false-positive low scores. A stable_non_signal cell is genuinely low across grids — a stable_non_signal paired with a composite_score of 35 means the cell is reliably quiet, not under-sampled.
The classification is recomputed after every material scoring-engine change. The companion Spatial Sharpe ratio gives you the same robustness intuition relative to neighbors rather than relative to grid resolution — combine both when filtering for production-grade rankings.

Data freshness

SourceRefresh cadenceLag
POI inventory (Google Places)Weekly<7 days
Building permits (city open data)Per-city; mostly weekly7-30 days depending on city
Traffic patterns (state DOTs)Monthly30-60 days
Transit ridership (FTA NTD)Monthly60-90 days (FTA publishing lag)
USPS vacancy (HUD)Quarterly90-120 days
Jobs (BLS QCEW)Quarterly90 days (BLS publishing lag)
Demographics (Census ACS)Annual12-18 months
Real estate listingsDaily (where available)1-3 days
Crime (UCR/local)Monthly30-60 days
The freshness metadata in score responses tells you the oldest source feeding a given response — useful for deciding whether to cache.

API stability commitments

  • Cell IDs — frozen across major API versions
  • Scoring profile names — frozen
  • Composite + sub-score scales (0–100) — frozen
  • Confidence semantics — frozen
  • Sub-score names within a group — may change with 90-day notice
  • Underlying data sources — may change without notice (we pick the best available)
  • Weight tuning per profile — adjusted quarterly based on backtesting; minor adjustments not announced, major rebalances get a 30-day blog post
If a profile’s behavior changes meaningfully, you’ll see a profile_version bump in the response. Pin to a specific profile_version if you need replicable scores across time.

What Locus doesn’t include

Locus is non-PII. We do not include:
  • Individual person data (names, addresses, phone numbers)
  • Mobile-device location traces (Locus uses aggregated patterns, not raw movements)
  • Customer-specific data unless you explicitly upload it via /api/data/upload
Aggregations resolving to fewer than 25 people (or 5 households for residential metrics) are suppressed in responses with a suppressed: true flag.