Facility: 084967

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
084967
Name
Morningstar Storage
URL
https://www.morningstarstorage.com/location/377/tx/houston/the-heights/
Address
4495 Katy Fwy, Houston, TX 77007
Platform
custom_facility_084967
Parser File
src/parsers/custom/facility_084967_parser.py
Last Scraped
2026-03-27 13:42:03.767733
Created
2026-03-23 02:35:08.816820
Updated
2026-03-27 13:42:03.767733
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_084967_parser.py)
"""Parser for Morningstar Storage facility 084967 (CANDEE platform)."""

from __future__ import annotations

import json
import re

from bs4 import BeautifulSoup

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


class Facility084967Parser(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_084967"

    _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 #1055 Details

Status
exported
Parser Used
Facility084967Parser
Platform Detected
table_layout
Units Found
13
Stage Reached
exported
Timestamp
2026-03-23 02:41:56.111642
Timing
Stage Duration
Fetch10246ms
Detect131ms
Parse197ms
Export5ms

Snapshot: 084967_20260323T024206Z.html · Show Snapshot · Open in New Tab

Parsed Units (13)

5x5

$59.00/mo

5x9

$82.00/mo

5x10

$89.00/mo

5x10

$52.00/mo

10x7.5

$172.00/mo

7.5x10

$74.00/mo

10x9

$89.00/mo

10x10

$112.00/mo

10x15

$149.00/mo

10x20

$337.00/mo

10x20

$209.00/mo

10x25

$247.00/mo

10x30

$367.00/mo

All Failures for this Facility (2)

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

No units extracted for 084967

Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 084967
parse _WarningAsException scraper no_units_extracted warning Run #N/A | 2026-03-27 13:42:01.683202

No units extracted for 084967

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

← Back to dashboard