Facility: 003747

Ross Point Mini 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
003747
Name
Ross Point Mini Storage
URL
https://www.rosspointministorage.com/3856-e-12th-ave-post-falls-id-83854
Address
3856 E 12th Ave, Post Falls, ID 83854, USA, Post Falls, Idaho 83854
Platform
custom_facility_003747
Parser File
src/parsers/custom/facility_003747_parser.py
Last Scraped
2026-03-27 13:57:09.437489
Created
2026-03-14 16:21:53.706708
Updated
2026-03-27 13:57:09.468641
Parser & Healing Diagnosis working
Parser Status
✓ Working
Status Reason
N/A
Last Healing Attempt
Not attempted
Parser Source (src/parsers/custom/facility_003747_parser.py)
"""Parser for Ross Point Mini Storage."""

from __future__ import annotations

import re

from bs4 import BeautifulSoup

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


class Facility003747Parser(BaseParser):
    """Extract storage units from Ross Point Mini Storage."""

    platform = "custom_facility_003747"

    _UNIT_RE = re.compile(
        r"(\d+\s*[\'\'\u2032]?\s*[xX\u00d7]\s*\d+\s*[\'\'\u2032]?)"
        r"[^\$]{0,120}"
        r"\$(\d[\d,.]*)",
        re.DOTALL,
    )

    _PRICE_SIZE_RE = re.compile(
        r"\$(\d[\d,.]*)"
        r".{0,120}"
        r"(\d+\s*[\'\'\u2032]?\s*[xX\u00d7]\s*\d+\s*[\'\'\u2032]?)",
        re.DOTALL,
    )

    _SIZE_ONLY_RE = re.compile(
        r"(\d+\s*[\'\'\u2032]?\s*[xX\u00d7]\s*\d+\s*[\'\'\u2032]?)"
    )

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

        for tag in soup.find_all(["script", "style"]):
            tag.decompose()

        body_text = soup.get_text(separator="\n")

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

        # Try size-then-price pattern
        for m in self._UNIT_RE.finditer(body_text):
            size_text = m.group(1).strip()
            price_text = m.group(2).strip()
            key = (size_text, price_text)
            if key in seen:
                continue
            seen.add(key)

            unit = UnitResult()
            unit.size = size_text
            w, ln, sq = self.normalize_size(size_text)
            if w is not None:
                unit.metadata = {"width": w, "length": ln, "sqft": sq}
            unit.price = self.normalize_price(price_text)
            unit.description = m.group(0).strip()[:200]
            if unit.size or unit.price:
                result.units.append(unit)

        # Try price-then-size pattern if no results
        if not result.units:
            for m in self._PRICE_SIZE_RE.finditer(body_text):
                price_text = m.group(1).strip()
                size_text = m.group(2).strip()
                key = (size_text, price_text)
                if key in seen:
                    continue
                seen.add(key)

                unit = UnitResult()
                unit.size = size_text
                w, ln, sq = self.normalize_size(size_text)
                if w is not None:
                    unit.metadata = {"width": w, "length": ln, "sqft": sq}
                unit.price = self.normalize_price(price_text)
                unit.description = m.group(0).strip()[:200]
                if unit.size or unit.price:
                    result.units.append(unit)

        # Fallback: extract sizes without prices
        if not result.units:
            seen_sizes: set[str] = set()
            for m in self._SIZE_ONLY_RE.finditer(body_text):
                size_text = m.group(1).strip()
                if size_text in seen_sizes:
                    continue
                w, ln, sq = self.normalize_size(size_text)
                if w is None or w < 3 or ln < 3:
                    continue
                seen_sizes.add(size_text)
                unit = UnitResult()
                unit.size = size_text
                unit.metadata = {"width": w, "length": ln, "sqft": sq}
                result.units.append(unit)

        if not result.units:
            result.warnings.append("No units found via regex")

        return result

Scrape Runs (5)

Run #294 Details

Status
exported
Parser Used
Facility003747Parser
Platform Detected
storageunitsoftware
Units Found
21
Stage Reached
exported
Timestamp
2026-03-14 16:31:23.173068
Timing
Stage Duration
Fetch2875ms
Detect36ms
Parse20ms
Export14ms

Snapshot: 003747_20260314T163126Z.html · Show Snapshot · Open in New Tab

Parsed Units (21)

5'x5'

No price

4'x10'

No price

4'x12'

No price

6'x8'

No price

5'x10'

No price

5'x12'

No price

6'x12'

No price

5'x15'

No price

8'x10'

No price

10'x10'

No price

10'x12'

No price

10'x15'

No price

10'x20'

No price

12'x20'

No price

10'x25'

No price

12'x25'

No price

10'x30'

No price

12'x28'

No price

12'x30'

No price

12'x35'

No price

12'x40'

No price

← Back to dashboard