Facility: 076452

Morningstar 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
076452
Name
Morningstar Storage
URL
https://www.morningstarstorage.com/location/379/tx/houston/eado/
Address
812 Live Oak St, Houston, TX 77003
Platform
custom_facility_076452
Parser File
src/parsers/custom/facility_076452_parser.py
Last Scraped
2026-03-27 13:41:54.016221
Created
2026-03-23 02:35:08.816820
Updated
2026-03-27 13:41:54.049360
Parser & Healing Diagnosis working
Parser Status
✓ Working
Status Reason
N/A
Last Healing Attempt
Not attempted
Parser Source (src/parsers/custom/facility_076452_parser.py)
"""Parser for Morningstar Storage facility 076452 (CANDEE platform)."""

from __future__ import annotations

import json
import re

from bs4 import BeautifulSoup

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


class Facility076452Parser(BaseParser):
    """Extract storage units from Morningstar Storage (CANDEE reservation system).

    The CANDEE platform renders unit data in div.unitMasterData elements with
    data-price (monthly rate) and a nested div.unitName containing the size
    string (e.g. "5x5 Heated & Cooled Ground").  data-size holds the sq ft.
    """

    platform = "custom_facility_076452"

    _DIM_RE = re.compile(r"(\d+\.?\d*)\s*[xX×]\s*(\d+\.?\d*)")

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

        seen: set[tuple[str, str]] = set()

        for unit_el in soup.find_all(class_="unitMasterData"):
            price_raw = unit_el.get("data-price", "").strip()
            price = self.normalize_price(price_raw)
            if price is None:
                continue

            # unitName contains the display name e.g. "5x5 Heated & Cooled Ground"
            name_el = unit_el.find(class_="unitName")
            if not name_el:
                continue
            name_text = name_el.get_text(strip=True)

            # Extract dimension from the name text
            dim_m = self._DIM_RE.search(name_text)
            if dim_m:
                size_text = dim_m.group(0)
            else:
                # Fall back to sq ft if no WxL found
                sqft_raw = unit_el.get("data-size", "")
                size_text = f"{sqft_raw} sqft" if sqft_raw else name_text

            key = (size_text, price_raw)
            if key in seen:
                continue
            seen.add(key)

            # Features from data-features JSON array
            features_raw = unit_el.get("data-features", "")
            desc: str | None = None
            if features_raw:
                try:
                    features_list = json.loads(features_raw)
                    # Deduplicate while preserving order
                    seen_feats: list[str] = []
                    for f in features_list:
                        if f not in seen_feats:
                            seen_feats.append(f)
                    desc = ", ".join(seen_feats)
                except (json.JSONDecodeError, TypeError):
                    pass

            unit = UnitResult(
                size=size_text,
                price=price,
                description=desc,
            )
            w, ln, sq = self.normalize_size(size_text)
            if w is not None:
                unit.metadata = {"width": w, "length": ln, "sqft": sq}
            elif unit_el.get("data-size"):
                try:
                    unit.metadata = {"sqft": float(unit_el["data-size"])}
                except ValueError:
                    pass
            result.units.append(unit)

        if not result.units:
            result.warnings.append("No units found in CANDEE unitMasterData elements")

        return result

Scrape Runs (3)

Run #1582 Details

Status
exported
Parser Used
Facility076452Parser
Platform Detected
table_layout
Units Found
25
Stage Reached
exported
Timestamp
2026-03-27 13:41:45.665750
Timing
Stage Duration
Fetch8128ms
Detect108ms
Parse51ms
Export24ms

Snapshot: 076452_20260327T134153Z.html · Show Snapshot · Open in New Tab

Parsed Units (25)

5x5

$63.00/mo

5x5

$37.00/mo

5x5

$18.00/mo

8.5x5

$23.00/mo

5x10

$41.00/mo

5x10

$74.00/mo

7x10

$59.00/mo

10x10

$67.00/mo

10x10

$112.00/mo

10x15

$112.00/mo

10x15

$149.00/mo

10x17

$119.00/mo

17x10

$119.00/mo

10x20

$142.00/mo

10x20

$172.00/mo

20x10

$134.00/mo

25x10

$187.00/mo

10x30

$247.00/mo

10x30

$269.00/mo

15x25

$277.00/mo

13x28

$194.00/mo

30x15

$337.00/mo

10x40

$299.00/mo

20x20

$299.00/mo

20x15

$248.00/mo

All Failures for this Facility (1)

parse _WarningAsException scraper no_units_extracted warning Run #N/A | 2026-03-27 13:41:47.404337

No units extracted for 076452

Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 076452

← Back to dashboard