Facility: 080080

LG Properties 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
080080
Name
LG Properties Storage
URL
https://www.storage-lgproperties.com/
Address
N/A
Platform
custom_facility_080080
Parser File
src/parsers/custom/facility_080080_parser.py
Last Scraped
2026-03-27 14:03:28.445896
Created
2026-03-06 23:45:35.865957
Updated
2026-03-27 14:03:28.481063
Parser & Healing Diagnosis working
Parser Status
✓ Working
Status Reason
N/A
Last Healing Attempt
Not attempted
Parser Source (src/parsers/custom/facility_080080_parser.py)
"""Parser for LG Properties, LLC - Laramie, Wyoming (facility 080080).

This is a GoDaddy Website Builder page (Starfield Technologies) that describes
a single unit type (12' x 30') with a flat monthly rate embedded in plain-text
content blocks. Unit details are inside elements with ``data-aid`` attributes
such as ``ABOUT_DESCRIPTION_RENDERED*``, not in a structured pricing table.
"""

from __future__ import annotations

import re

from bs4 import BeautifulSoup

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


class Facility080080Parser(BaseParser):
    """Extract storage units from LG Properties, LLC (GoDaddy Website Builder).

    The site advertises one unit type — 12' x 30' outdoor drive-up units —
    at a single flat rate.  Relevant data is scattered across several
    ``data-aid="ABOUT_DESCRIPTION_RENDERED*"`` content blocks:

    * Storage Facilities block: describes the unit dimensions and RV suitability.
    * Pricing block: "All units are $175 per month."

    Strategy:
      1. Find the "Pricing" heading via ``data-aid="ABOUT_HEADLINE_RENDERED*"``
         and read the adjacent description block.
      2. Extract the dollar amount with a regex.
      3. Find the "Storage Facilities" block the same way and extract dimensions.
      4. Emit one ``UnitResult`` combining both pieces of information.
    """

    platform = "custom_facility_080080"

    # "All units are $175 per month." or variations
    _PRICE_RE = re.compile(
        r"\$\s*([\d,]+(?:\.\d+)?)\s+per\s+month",
        re.IGNORECASE,
    )

    # Dimension like "12' x 30'" — tolerates smart quotes and varied spacing
    _DIM_RE = re.compile(
        r"(\d+(?:\.\d+)?)['\u2018\u2019\u2032]?\s*[xX\u00d7]\s*(\d+(?:\.\d+)?)['\u2018\u2019\u2032]?",
    )

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

        # Collect all content blocks (heading + description pairs)
        # Each ContentBasic div contains one h4 heading and one description div.
        content_blocks = soup.find_all("div", attrs={"data-ux": "ContentBasic"})

        price: float | None = None
        dimensions: list[tuple[float, float]] = []
        description_text: str = ""
        amenities: list[str] = []

        for block in content_blocks:
            heading_el = block.find(attrs={"data-ux": "ContentHeading"})
            desc_el = block.find(attrs={"data-ux": "ContentText"})

            if not heading_el or not desc_el:
                continue

            heading_text = heading_el.get_text(strip=True)
            desc_text = desc_el.get_text(separator=" ", strip=True)

            heading_lower = heading_text.lower()

            if "pricing" in heading_lower:
                m = self._PRICE_RE.search(desc_text)
                if m:
                    price = float(m.group(1).replace(",", ""))

            elif "storage facilit" in heading_lower:
                description_text = desc_text
                for m in self._DIM_RE.finditer(desc_text):
                    w = float(m.group(1))
                    length = float(m.group(2))
                    dimensions.append((w, length))

            elif "safe" in heading_lower or "secure" in heading_lower:
                amenities_text = desc_text.strip()
                if amenities_text:
                    amenities.append(amenities_text)

        if not dimensions:
            result.warnings.append("No unit dimensions found in Storage Facilities block")
            return result

        if price is None:
            result.warnings.append("No price found in Pricing block")
            return result

        # Detect drive-up access from door size description
        is_drive_up = bool(re.search(r"door|RV|motorhome|drive.?up", description_text, re.IGNORECASE))

        for width, length in dimensions:
            size_label = f"{int(width)}' x {int(length)}'"
            meta: dict = {
                "width": width,
                "length": length,
                "sqft": width * length,
                "drive_up": is_drive_up,
            }
            if amenities:
                meta["amenities"] = amenities

            unit = UnitResult(
                size=size_label,
                price=price,
                description=description_text,
                metadata=meta,
            )
            result.units.append(unit)

        return result

Scrape Runs (7)

Run #826 Details

Status
exported
Parser Used
Facility080080Parser
Platform Detected
table_layout
Units Found
1
Stage Reached
exported
Timestamp
2026-03-21 18:57:31.335315
Timing
Stage Duration
Fetch2809ms
Detect32ms
Parse11ms
Export4ms

Snapshot: 080080_20260321T185734Z.html · Show Snapshot · Open in New Tab

Parsed Units (1)

12' x 30'

$175.00/mo

All Failures for this Facility (1)

fetch DatatypeMismatch unknown unknown permanent Run #16 | 2026-03-07 01:42:23.092739

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

← Back to dashboard