What it does
Battery storage siting is a filtering problem before it is a ranking problem. Most parcels in a county are disqualified for a reason that has nothing to do with how good they would otherwise be: the land is government held, the interconnection queue at the nearest substation is already saturated, the owner is a school district.
The platform scrapes parcel records and interconnection queue status, runs every parcel through a scoring function from 0 to 100, and stores the score alongside the list of reasons that produced it. The qualified flag and the outreach priority both fall out of the score rather than being maintained separately.
Recomputable, and therefore arguable
The scoring function takes a parcel dictionary and returns a number and a list of factor deltas. It touches no database, which means it is unit testable directly and, more importantly, means any score in the system can be regenerated from the raw record and compared against what is stored. A ranking nobody can re-derive is a ranking nobody should act on.
In the code
GOVT_OWNER_NAME = re.compile(
r"(city of|county of|state of|united states|us army|us air force"
r"|txdot|ercot|utility district|\bMUD\b|\bPUD\b|\bISD\b"
r"|\bUSDA\b|\bBLM\b)",
re.IGNORECASE,
)
def score_parcel(parcel: dict, queue_status: str | None = None,
has_developer_activity: bool = False) -> tuple[int, list[dict]]:
"""Score a single parcel dict. Returns (score, reasons_list).
This is a pure function — no DB access. Can be unit tested directly.
"""
if GOVT_OWNER_NAME.search(owner_name):
return 0, [{"factor": "govt_owner_name", "delta": 0, "disqualified": True}]
# Intelligence hard disqualifier: saturated queue + no tax delinquency
if queue_status == "saturated" and not tax_delinquent:
return 0, [{"factor": "saturated_no_tax_delinquency",
"delta": 0, "disqualified": True}]
Two things make this worth quoting. First, the disqualifiers return zero and stop rather than subtracting points, so a government parcel cannot accumulate its way back onto the list through good acreage and a nearby substation. Second, the function returns the reasons, not just the number, and each reason is a structured record naming the factor and its contribution. That is the difference between a tool that produces a ranked list and a tool that produces a ranked list you can defend to a landowner, a lender or a commissioner. The second hard rule is the domain knowledge: a saturated interconnection queue kills a site outright unless the owner is tax delinquent, because delinquency is the signal that the parcel might actually transact in time to matter.
How this differs from the ordinary version
The score is a function, not a stored opinion
Score and reason are both recomputable from the raw parcel at any time. When a queue status changes or a delinquency year rolls over, the whole list re-ranks itself and the new reasons are visible next to the old ones.
Disqualification is separate from ranking
Mixing the two is how a siting tool ends up recommending a parcel owned by the county. Hard rules run first and short circuit; only what survives gets scored.
It is built on public records
Parcel data, interconnection queue status and tax delinquency are all public. The instrument is the joining and the scoring, not privileged access.
In the field
Why El Paso
The regional grid is where the storage argument is actually being had, and land acquisition is moving faster than the public conversation about it. A tool that ranks parcels and shows its reasoning is useful to a developer and, run in reverse, useful to anyone trying to understand why a particular field got bought.
Questions
- What disqualifies a parcel outright?
- Government land use or a government owner name, and a saturated interconnection queue with no tax delinquency. Those return zero immediately rather than subtracting points.
- Can a score be re-derived?
- Yes. Scoring is a pure function over the parcel record with no database access, and it returns the list of factor contributions alongside the number.