new-site/scripts/formation/states/nm/adapter.py
justin f8cd37ac8c Initial commit — Performance West telecom compliance platform
Includes: API (Express/TypeScript), Astro site, Python workers,
document generators, FCC compliance tools, Canada CRTC formation,
Ansible infrastructure, and deployment scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 06:54:22 -05:00

116 lines
4.1 KiB
Python

"""New Mexico — SOS SOS portal automation.
Name search implemented via the public business entity search.
LLC/Corp filing selectors pending live portal verification.
"""
from __future__ import annotations
import re
from scripts.formation.base import (
StatePortal,
NameSearchResult,
FormationOrder,
FilingResult,
FilingStatus,
)
from .config import CONFIG
class NMPortal(StatePortal):
STATE_CODE = "NM"
STATE_NAME = "New Mexico"
PORTAL_NAME = "SOS"
PORTAL_URL = CONFIG["portal_url"]
NWRA_ADDRESS = CONFIG["nwra_address"]
NWRA_CITY = CONFIG["nwra_city"]
NWRA_STATE = CONFIG["nwra_state"]
NWRA_ZIP = CONFIG["nwra_zip"]
async def search_name(self, name: str) -> NameSearchResult:
"""Search New Mexico business name availability via the public portal."""
try:
page = await self.start_browser()
await page.goto(CONFIG["name_search_url"], wait_until="networkidle")
await self.human_delay(1.0, 2.5)
search_sel = (
CONFIG["selectors"].get("search_input")
or 'input[type="text"], input[name*="earch"], input[name*="ame"]'
)
await page.fill(search_sel, "")
await self.type_slowly(page, search_sel, name)
await self.human_delay(0.5, 1.0)
btn_sel = (
CONFIG["selectors"].get("search_button")
or 'button[type="submit"], input[type="submit"]'
)
await page.click(btn_sel)
await page.wait_for_load_state("networkidle")
await self.human_delay(1.0, 2.0)
content = await page.content()
await self.screenshot(page, f"${CODE}_name_search_{name}")
no_results = any(
phrase in content.lower()
for phrase in ["no match", "no results", "no records", "no entities", "0 results"]
)
if no_results:
return NameSearchResult(
available=True, exact_match=False, similar_names=[],
state_code="NM", searched_name=name,
raw_response=content[:2000],
)
similar: list[str] = []
pattern = re.compile(r'<td[^>]*>([^<]*?' + re.escape(name[:8]) + r'[^<]*?)</td>', re.IGNORECASE)
for m in pattern.finditer(content):
found = m.group(1).strip()
if found and 3 < len(found) < 200:
similar.append(found)
exact = any(
s.upper().replace(",", "").strip() == name.upper().replace(",", "").strip()
for s in similar
)
return NameSearchResult(
available=not exact, exact_match=exact,
similar_names=similar[:10], state_code="NM",
searched_name=name, raw_response=content[:2000],
)
except Exception as exc:
return NameSearchResult(
available=False, state_code="NM", searched_name=name,
raw_response=f"Error: {exc}",
)
async def file_llc(self, order: FormationOrder) -> FilingResult:
"""File an LLC in New Mexico. Selectors pending live portal verification."""
return FilingResult(
success=False, status=FilingStatus.PENDING,
state_code="NM", entity_name=order.entity_name,
error_message=(
"NM filing adapter selectors pending verification. "
f"Admin: file manually at {CONFIG['portal_url']} — LLC formation."
),
)
async def file_corporation(self, order: FormationOrder) -> FilingResult:
"""File a corporation in New Mexico. Selectors pending live portal verification."""
return FilingResult(
success=False, status=FilingStatus.PENDING,
state_code="NM", entity_name=order.entity_name,
error_message=(
"NM filing adapter selectors pending verification. "
f"Admin: file manually at {CONFIG['portal_url']} — Corp formation."
),
)
def adapter() -> NMPortal:
return NMPortal()