Facility: 090744

5150 Storage

Stale Data Warning: This facility has not been successfully scraped in 26 days (threshold: 3 days). Data may be outdated.
Facility Information active
Facility ID
090744
Name
5150 Storage
URL
https://www.5150storage.com/2650-e-yellowstone-hwy-casper-wy-82609
Address
N/A
Platform
custom_facility_090744
Parser File
src/parsers/custom/facility_090744_parser.py
Last Scraped
2026-03-27 14:04:28.323297
Created
2026-03-06 23:45:35.865957
Updated
2026-03-27 14:04:28.352147
Parser & Healing Diagnosis working
Parser Status
✓ Working
Status Reason
N/A
Last Healing Attempt
Not attempted
Parser Source (src/parsers/custom/facility_090744_parser.py)
"""Parser for 5150 Storage - Casper, WY facility.

This is a React/Material-UI site that renders storage unit cards.
Each unit is a MuiCard-root div containing an h5 title with dimensions
and an h6.MuiTypography-subtitle1 with the monthly price.
The card content is duplicated three times for responsive breakpoints;
we take only the first h5/h6 in each card to deduplicate.
"""

from __future__ import annotations

import re

from bs4 import BeautifulSoup

from src.parsers.base import BaseParser, ParseResult, UnitResult


class Facility090744Parser(BaseParser):
    """Extract storage units from 5150 Storage (MUI card layout)."""

    platform = "custom_facility_090744"

    # Matches "5' x 10' Unit", "10' x 10' Unit", etc.
    _SIZE_RE = re.compile(
        r"(\d+(?:\.\d+)?)['\u2019\u2032]?\s*[xX\u00d7]\s*(\d+(?:\.\d+)?)['\u2019\u2032]?",
        re.IGNORECASE,
    )

    def parse(self, html: str, url: str = "") -> ParseResult:
        soup = BeautifulSoup(html, "lxml")
        result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)

        # Each unit is wrapped in a MuiCard-root div
        cards = soup.select(".MuiCard-root")

        for card in cards:
            unit = UnitResult()

            # Size is in the first h5 element (e.g. "5' x 10' Unit")
            h5 = card.find("h5")
            if h5:
                size_text = h5.get_text(strip=True)
                unit.size = size_text
                m = self._SIZE_RE.search(size_text)
                if m:
                    width = float(m.group(1))
                    length = float(m.group(2))
                    unit.metadata = {"width": width, "length": length, "sqft": width * length}

            # Price is in the first h6.MuiTypography-subtitle1 (e.g. "$48/mo")
            # Strip the "/mo" suffix before normalizing.
            price_el = card.find(
                "h6",
                class_=lambda c: c and "MuiTypography-subtitle1" in c,
            )
            if price_el:
                price_text = re.sub(r"\s*/\s*mo\b.*", "", price_el.get_text(strip=True), flags=re.IGNORECASE)
                unit.price = self.normalize_price(price_text)

            # Description from first p element
            p_el = card.find("p")
            if p_el:
                unit.description = p_el.get_text(strip=True)

            if unit.size or unit.price:
                result.units.append(unit)

        if not result.units:
            result.warnings.append("No unit cards found on page")

        return result

Scrape Runs (7)

Run #388 Details

Status
exported
Parser Used
Facility090744Parser
Platform Detected
storageunitsoftware
Units Found
4
Stage Reached
exported
Timestamp
2026-03-14 16:38:20.028762
Timing
Stage Duration
Fetch3892ms
Detect31ms
Parse16ms
Export21ms

Snapshot: 090744_20260314T163823Z.html · Show Snapshot · Open in New Tab

Parsed Units (4)

5' x 10' Unit

$48.00/mo

10' x 10' Unit

$67.00/mo

10' x 15' Unit

$80.00/mo

10' x 20' Unit

$95.00/mo

All Failures for this Facility (1)

fetch DatatypeMismatch unknown unknown permanent Run #15 | 2026-03-07 01:42:20.864580

column "success" is of type boolean but expression is of type integer LINE 3: ... VALUES ('090744', 15, '090744_20260307T014220Z.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 ('090744', 15, '090744_20260307T014220Z.html', 0)
                                                                     ^
HINT:  You will need to rewrite or cast the expression.

← Back to dashboard