Facility: 044960

Anchor Ontario Storage

Stale Data Warning: This facility has not been successfully scraped in 30 days (threshold: 3 days). Data may be outdated.
Facility Information active
Facility ID
044960
Name
Anchor Ontario Storage
URL
http://www.anchorontario.com/
Address
N/A
Platform
custom_facility_044960
Parser File
src/parsers/custom/facility_044960_parser.py
Last Scraped
2026-03-23 03:17:28.006959
Created
2026-03-06 23:45:35.865957
Updated
2026-03-23 03:17:28.006959
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_044960_parser.py)
"""Parser for Anchor Mini Storage Ontario (facility 044960).

The website (anchorontario.com) is a simple WordPress site with a Unit Size
Guide page listing available unit sizes as headings, but no pricing is
published anywhere on the site. The reservation flow uses an embedded contact
form that only lets visitors select a size.

This parser extracts available unit sizes from the Size Guide page (fetched as
part of the snapshot or from the main snapshot) so the facility is represented
in the database. All price fields are left as ``None`` and a warning is
recorded.

Unit sizes advertised: 5x10, 10x10, 10x15, 10x20, 10x25.
"""

from __future__ import annotations

import re

from bs4 import BeautifulSoup

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


class Facility044960Parser(BaseParser):
    """Extract storage unit sizes from Anchor Mini Storage Ontario.

    The Unit Size Guide page contains headings like:
        "5 x 10 Unit"
        "10 x 10 Unit"
        "10 x 15 Unit"
        "10 x 20 Unit"
        "10 x 25 Unit"

    No pricing is published on the site, so ``price`` and ``sale_price`` are
    left as ``None`` and a warning is recorded.
    """

    platform = "custom_facility_044960"

    # Matches patterns like "5 x 10", "10x15", "10 X 25"
    _SIZE_RE = re.compile(
        r"(\d+(?:\.\d+)?)\s*[xX\u00d7]\s*(\d+(?:\.\d+)?)",
    )

    # Description text associated with each size (keyed by "WxL" string)
    _DESCRIPTIONS: dict[str, str] = {
        "5x10": "Great for small furniture, bikes, mattresses, tools, etc. About two pickup loads",
        "10x10": "About half the size of a one car garage or a one bedroom apartment",
        "10x15": "About the size of a small car or a two bedroom apartment",
        "10x20": "About the size of a two to three bedroom house",
        "10x25": "About the size of a three to four bedroom house",
    }

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

        # The sizes appear as <h2> headings of the form "N x N Unit"
        # Also handle the case where sizes appear in select/option elements
        # (the reservation form embed lists them as <option> values).
        sizes_found: list[tuple[float, float, str]] = []

        for heading in soup.find_all(["h2", "h3", "h4", "option", "strong"]):
            text = heading.get_text(strip=True)
            match = self._SIZE_RE.search(text)
            if match:
                width = float(match.group(1))
                length = float(match.group(2))
                # Avoid duplicates
                key = (width, length)
                if not any(w == key[0] and l == key[1] for w, l, _ in sizes_found):
                    desc_key = f"{int(width)}x{int(length)}"
                    description = self._DESCRIPTIONS.get(desc_key, f"Unit size {int(width)}' x {int(length)}'")
                    sizes_found.append((width, length, description))

        if not sizes_found:
            # Fallback: scan all text nodes for size patterns
            for element in soup.find_all(string=self._SIZE_RE):
                parent = element.parent
                if not parent or parent.name in ("script", "style"):
                    continue
                text = element.strip()
                match = self._SIZE_RE.search(text)
                if match:
                    width = float(match.group(1))
                    length = float(match.group(2))
                    key = (width, length)
                    if not any(w == key[0] and l == key[1] for w, l, _ in sizes_found):
                        desc_key = f"{int(width)}x{int(length)}"
                        description = self._DESCRIPTIONS.get(desc_key, f"Unit size {int(width)}' x {int(length)}'")
                        sizes_found.append((width, length, description))

        for width, length, description in sizes_found:
            size_label = self.normalize_size(f"{int(width)}x{int(length)}")
            unit = UnitResult(
                size=size_label,
                description=f"{description} — no pricing published on site",
                price=None,
                sale_price=None,
                metadata={
                    "width": width,
                    "length": length,
                    "sqft": width * length,
                    "no_pricing": True,
                },
                url=url or None,
            )
            result.units.append(unit)

        if result.units:
            result.warnings.append(
                "No pricing information is published on this facility's website; "
                "unit sizes were extracted but all price fields are None."
            )
        else:
            result.warnings.append("No unit size patterns found on page")

        return result

Scrape Runs (4)

Run #1455 Details

Status
exported
Parser Used
Facility044960Parser
Platform Detected
unknown
Units Found
0
Stage Reached
exported
Timestamp
2026-03-23 03:17:18.530205
Timing
Stage Duration
Fetch9374ms
Detect46ms
Parse30ms
Export3ms

Snapshot: 044960_20260323T031727Z.html · Show Snapshot · Open in New Tab

No units found in this run.

All Failures for this Facility (4)

parse _WarningAsException scraper no_units_extracted warning Run #N/A | 2026-03-23 03:17:28.002787

No units extracted for 044960

Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 044960
parse _WarningAsException scraper no_units_extracted warning Run #N/A | 2026-03-21 19:10:16.814940

No units extracted for 044960

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

No units extracted for 044960

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

No units extracted for 044960

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

← Back to dashboard