Facility: 112029
New Frontier Self Storage
- Facility ID
- 112029
- Name
- New Frontier Self Storage
- URL
- https://www.newfrontierselfstorage.com/locations/laramie-wy-82070
- Address
- N/A
- Platform
- custom_facility_112029
- Parser File
- src/parsers/custom/facility_112029_parser.py
- Last Scraped
- 2026-03-23 03:13:41.116762
- Created
- 2026-03-06 23:45:35.865957
- Updated
- 2026-03-23 03:13:41.124796
- Parser Status
- ✓ Working
- Status Reason
- N/A
- Last Healing Attempt
- Not attempted
Parser Source (src/parsers/custom/facility_112029_parser.py)
"""Parser for New Frontier Self Storage - Laramie, WY facility.
This is a CCStorage/Dragon-themed site with structured unit cards.
Each card contains dimensions, a sale price (price-normal), a regular
price (price-discount/strikethrough), a promotion, scarcity badge,
and unit type indicator.
"""
from __future__ import annotations
from bs4 import BeautifulSoup
from src.parsers.base import BaseParser, ParseResult, UnitResult
class Facility112029Parser(BaseParser):
"""Extract storage units from New Frontier Self Storage (Laramie, WY).
Unit cards use CSS classes:
- .dimensions — size text e.g. "5' x 5'"
- .price-normal — discounted/sale price e.g. "$30"
- .price-discount — regular (strikethrough) price e.g. "$61"
- .unit-recurring — promotion text e.g. "75% Off 1st Month"
- .badge-danger span — scarcity badge e.g. "2 Units Left"
- .unit-type-name — unit type e.g. "Drive-up", "Inside"
"""
platform = "custom_facility_112029"
def parse(self, html: str, url: str = "") -> ParseResult:
soup = BeautifulSoup(html, "lxml")
result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)
containers = soup.find_all(
"div", class_=lambda c: c and "unit-card" in c
)
for container in containers:
unit = UnitResult()
# Size / dimensions
dims_el = container.find("span", class_="dimensions")
if dims_el:
size_text = dims_el.get_text(strip=True)
unit.size = size_text
w, ln, sq = self.normalize_size(size_text)
if w is not None:
unit.metadata = {"width": w, "length": ln, "sqft": sq}
# Sale price (the discounted "price-normal" displayed prominently)
sale_el = container.find(
"div", class_=lambda c: c and "price-normal" in c
)
if sale_el:
unit.sale_price = self.normalize_price(sale_el.get_text(strip=True))
# Regular price (strikethrough "price-discount")
regular_el = container.find(
"div", class_=lambda c: c and "price-discount" in c
)
if regular_el:
unit.price = self.normalize_price(regular_el.get_text(strip=True))
# Promotion text
promo_el = container.find("div", class_="unit-recurring")
if promo_el:
unit.promotion = promo_el.get_text(strip=True)
# Scarcity badge (badge-danger indicates limited availability)
scarcity_badge = container.find(
"span", class_=lambda c: c and "badge-danger" in c
)
if scarcity_badge:
unit.scarcity = scarcity_badge.get_text(strip=True)
# Unit type — store in metadata alongside dimensions
type_el = container.find(
"div", class_=lambda c: c and "unit-type-name" in c
)
if type_el:
unit_type = type_el.get_text(strip=True)
if unit.metadata is None:
unit.metadata = {}
unit.metadata["unit_type"] = unit_type
unit.description = unit_type
if unit.size or unit.price or unit.sale_price:
result.units.append(unit)
if not result.units:
result.warnings.append("No units found on page")
return result
Scrape Runs (8)
-
started Run #22642026-03-27 14:11:21.186540
-
exported Run #13972026-03-23 03:13:35.502230 | 6 units | Facility112029Parser | View Data →
-
exported Run #9042026-03-21 19:06:10.861897 | 6 units | Facility112029Parser | View Data →
-
exported Run #4532026-03-14 16:45:18.158710 | 6 units | Facility112029Parser | View Data →
-
failed Run #132026-03-07 01:42:08.368302 | 1 failure(s)
-
started Run #32026-03-07 01:05:19.120478
-
started Run #22026-03-07 00:21:22.440886
-
started Run #12026-03-07 00:12:05.578727
Run #3 Details
- Status
- started
- Parser Used
- N/A
- Platform Detected
- N/A
- Units Found
- 0
- Stage Reached
- started
- Timestamp
- 2026-03-07 01:05:19.120478
All Failures for this Facility (1)
fetch
DatatypeMismatch
unknown
unknown
permanent
Run #13 | 2026-03-07 01:42:13.089607
column "success" is of type boolean but expression is of type integer LINE 3: ... VALUES ('112029', 13, '112029_20260307T014213Z.html', 0) ^ HINT: You will need to rewrite or cast the expression.
Stack trace
Traceback (most recent call last):
File "/app/src/pipeline.py", line 329, in _process_facility
manifest_id = storage.insert_snapshot_manifest(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/src/db/pg_backend.py", line 615, in insert_snapshot_manifest
row = self._execute_returning(
^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/src/db/pg_backend.py", line 54, in _execute_returning
cur.execute(sql, params)
File "/app/.venv/lib/python3.11/site-packages/psycopg2/extras.py", line 236, in execute
return super().execute(query, vars)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
psycopg2.errors.DatatypeMismatch: column "success" is of type boolean but expression is of type integer
LINE 3: ... VALUES ('112029', 13, '112029_20260307T014213Z.html', 0)
^
HINT: You will need to rewrite or cast the expression.