Facility: 016286

Laramie 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
016286
Name
Laramie Storage
URL
http://laramiestorage.com/
Address
N/A
Platform
custom_facility_016286
Parser File
src/parsers/custom/facility_016286_parser.py
Last Scraped
2026-03-27 14:01:03.381835
Created
2026-03-06 23:45:35.865957
Updated
2026-03-27 14:01:03.381835
Parser & Healing Diagnosis needs_fix
Parser Status
⚠ Needs Fix
Status Reason
Parser returned 0 units
Last Healing Attempt
Not attempted
Parser Source (src/parsers/custom/facility_016286_parser.py)
"""Parser for LaramieStorage facility (Laramie, Wyoming).

This is a WordPress/MotoPress site that lists storage unit types as h1 headings
inside .motopress-text-obj divs. No prices are published on the website —
only unit dimensions and feature descriptions are available.

Unit types found:
  - 20x30: Garage door 9' wide x 9' high, Walk-in door, Overhead lights, 110 electric outlets
  - 10x20: Garage door 9' wide x 9' high, Overhead lights
  - 5x10:  Walk-in door, Overhead light
"""

from __future__ import annotations

import re

from bs4 import BeautifulSoup

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

# Match dimension headings like "20×30", "10×20", "5×10" (standard x or Unicode ×)
_SIZE_RE = re.compile(r"^(\d+)\s*[xX\u00d7\u2715]\s*(\d+)$")


class Facility016286Parser(BaseParser):
    """Extract storage units from LaramieStorage (laramiestorage.com).

    The storage-units page uses MotoPress rows where each unit type is
    represented by an h1 heading (the size) followed by a paragraph with
    feature descriptions inside a .motopress-text-obj div.  No pricing
    information is published on the site.
    """

    platform = "custom_facility_016286"

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

        # Each unit type lives inside a .motopress-text-obj div that has
        # an h1 whose text matches a WxL dimension pattern.
        for text_obj in soup.select(".motopress-text-obj"):
            heading = text_obj.find(re.compile(r"^h[1-6]$"))
            if not heading:
                continue

            heading_text = heading.get_text(strip=True)
            m = _SIZE_RE.match(heading_text)
            if not m:
                continue

            width = float(m.group(1))
            length = float(m.group(2))
            size_label = f"{int(width)}x{int(length)}"

            # Collect feature text from the paragraph(s) that follow the heading
            desc_parts: list[str] = []
            for sibling in heading.find_next_siblings():
                text = sibling.get_text(separator=", ", strip=True)
                if text:
                    desc_parts.append(text)

            description = "; ".join(desc_parts) if desc_parts else None

            unit = UnitResult(
                size=size_label,
                description=description,
                url=url or "https://www.laramiestorage.com/storage-units/",
                metadata={
                    "width": width,
                    "length": length,
                    "sqft": width * length,
                },
            )
            result.units.append(unit)

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

        return result

Scrape Runs (6)

Run #31 Details

Status
failed
Parser Used
N/A
Platform Detected
N/A
Units Found
0
Stage Reached
fetch
Timestamp
2026-03-09 20:49:41.533253

Failures (1)

fetch DatatypeMismatch unknown unknown permanent

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

All Failures for this Facility (6)

parse _WarningAsException scraper no_units_extracted warning Run #N/A | 2026-03-27 14:01:03.363665

No units extracted for 016286

Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 016286
parse _WarningAsException scraper no_units_extracted warning Run #N/A | 2026-03-27 14:01:03.350933

No units extracted for 016286

Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 016286
parse _WarningAsException scraper no_units_extracted warning Run #N/A | 2026-03-23 03:02:54.537948

No units extracted for 016286

Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 016286
parse _WarningAsException scraper no_units_extracted warning Run #N/A | 2026-03-21 18:54:30.997637

No units extracted for 016286

Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 016286
parse _WarningAsException scraper no_units_extracted warning Run #N/A | 2026-03-14 16:35:08.445922

No units extracted for 016286

Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 016286
fetch DatatypeMismatch unknown unknown permanent Run #31 | 2026-03-09 20:49:45.925560

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

← Back to dashboard