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>
This commit is contained in:
commit
f8cd37ac8c
1823 changed files with 145167 additions and 0 deletions
177
scripts/formation/states/co/adapter.py
Normal file
177
scripts/formation/states/co/adapter.py
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
"""Colorado — Socrata API for name search, Playwright for filing.
|
||||
|
||||
Colorado publishes business entity data on data.colorado.gov via the
|
||||
Socrata Open Data API (SODA). Dataset ID: 4ykn-tg5h.
|
||||
This allows name availability searches WITHOUT a headless browser.
|
||||
Filing still requires Playwright against the SOS web portal.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
from scripts.formation.base import (
|
||||
StatePortal, NameSearchResult, FormationOrder, FilingResult,
|
||||
FilingStatus,
|
||||
)
|
||||
from .config import CONFIG
|
||||
|
||||
SOCRATA_BASE = "https://data.colorado.gov/resource/4ykn-tg5h.json"
|
||||
|
||||
|
||||
class COPortal(StatePortal):
|
||||
STATE_CODE = "CO"
|
||||
STATE_NAME = "Colorado"
|
||||
PORTAL_NAME = CONFIG["portal_name"]
|
||||
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 Colorado business name availability via Socrata SODA API.
|
||||
|
||||
Uses the free REST API at data.colorado.gov — no browser, no login,
|
||||
no rate-limit issues for moderate usage. Returns JSON directly.
|
||||
"""
|
||||
try:
|
||||
# SoQL query: find entities whose name contains our search term
|
||||
upper_name = name.upper().replace("'", "''")
|
||||
query = f"$where=upper(entityname) like '%25{urllib.parse.quote(upper_name)}%25'"
|
||||
query += "&$limit=20&$order=entityformdate DESC"
|
||||
url = f"{SOCRATA_BASE}?{query}"
|
||||
|
||||
self.log.info("CO Socrata API query: %s", url)
|
||||
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
headers={"Accept": "application/json", "User-Agent": "PerformanceWest/1.0"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
data = json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
# Check for exact match (case-insensitive)
|
||||
exact_match = any(
|
||||
r.get("entityname", "").upper().strip() == upper_name.strip()
|
||||
for r in data
|
||||
)
|
||||
|
||||
# Collect similar names
|
||||
similar_names = [
|
||||
r.get("entityname", "").strip()
|
||||
for r in data[:10]
|
||||
if r.get("entityname", "").strip()
|
||||
]
|
||||
|
||||
available = not exact_match
|
||||
|
||||
self.log.info(
|
||||
"CO name search: '%s' — %s (exact_match=%s, similar=%d)",
|
||||
name, "AVAILABLE" if available else "TAKEN", exact_match, len(similar_names),
|
||||
)
|
||||
|
||||
return NameSearchResult(
|
||||
available=available,
|
||||
exact_match=exact_match,
|
||||
similar_names=similar_names,
|
||||
state_code=self.STATE_CODE,
|
||||
searched_name=name,
|
||||
raw_response=json.dumps(data[:5]),
|
||||
)
|
||||
|
||||
except urllib.error.URLError as e:
|
||||
self.log.error("CO Socrata API request failed: %s", e)
|
||||
return NameSearchResult(
|
||||
available=False,
|
||||
state_code=self.STATE_CODE,
|
||||
searched_name=name,
|
||||
raw_response=f"Socrata API error: {e}",
|
||||
)
|
||||
except Exception as e:
|
||||
self.log.error("CO name search failed: %s", e)
|
||||
return NameSearchResult(
|
||||
available=False,
|
||||
state_code=self.STATE_CODE,
|
||||
searched_name=name,
|
||||
raw_response=f"Error: {e}",
|
||||
)
|
||||
|
||||
async def file_llc(self, order: FormationOrder) -> FilingResult:
|
||||
"""File LLC Articles of Organization in Colorado.
|
||||
|
||||
Colorado SOS portal: sos.state.co.us
|
||||
Filing fee: $50
|
||||
Online filing is immediate (no processing delay).
|
||||
|
||||
TODO: Implement Playwright filing flow.
|
||||
"""
|
||||
try:
|
||||
page = await self.start_browser()
|
||||
await page.goto(CONFIG["filing_url"])
|
||||
await self.human_delay()
|
||||
await self.screenshot("co_llc_start")
|
||||
|
||||
# Verify name first via Socrata API (no browser needed)
|
||||
name_result = await self.search_name(order.entity_name)
|
||||
if not name_result.available:
|
||||
return FilingResult(
|
||||
success=False,
|
||||
status=FilingStatus.NAME_UNAVAILABLE,
|
||||
state_code=self.STATE_CODE,
|
||||
entity_name=order.entity_name,
|
||||
error_message=f"Name '{order.entity_name}' is not available in Colorado. "
|
||||
f"Similar: {', '.join(name_result.similar_names[:5])}",
|
||||
)
|
||||
|
||||
# TODO: Complete filing flow once portal selectors are mapped
|
||||
return FilingResult(
|
||||
success=False,
|
||||
status=FilingStatus.ERROR,
|
||||
state_code=self.STATE_CODE,
|
||||
entity_name=order.entity_name,
|
||||
error_message="CO LLC filing: name search via API works, "
|
||||
"filing form selectors pending portal walkthrough",
|
||||
)
|
||||
except Exception as e:
|
||||
self.log.error("CO LLC filing failed: %s", e)
|
||||
return FilingResult(
|
||||
success=False,
|
||||
status=FilingStatus.ERROR,
|
||||
state_code=self.STATE_CODE,
|
||||
entity_name=order.entity_name,
|
||||
error_message=str(e),
|
||||
)
|
||||
finally:
|
||||
await self.close_browser()
|
||||
|
||||
async def file_corporation(self, order: FormationOrder) -> FilingResult:
|
||||
"""File Articles of Incorporation in Colorado ($50)."""
|
||||
try:
|
||||
page = await self.start_browser()
|
||||
await page.goto(CONFIG["filing_url"])
|
||||
await self.human_delay()
|
||||
|
||||
return FilingResult(
|
||||
success=False,
|
||||
status=FilingStatus.ERROR,
|
||||
state_code=self.STATE_CODE,
|
||||
entity_name=order.entity_name,
|
||||
error_message="CO Corp filing pending — LLC flow first",
|
||||
)
|
||||
except Exception as e:
|
||||
return FilingResult(
|
||||
success=False,
|
||||
status=FilingStatus.ERROR,
|
||||
state_code=self.STATE_CODE,
|
||||
entity_name=order.entity_name,
|
||||
error_message=str(e),
|
||||
)
|
||||
finally:
|
||||
await self.close_browser()
|
||||
|
||||
|
||||
def adapter() -> COPortal:
|
||||
return COPortal()
|
||||
Loading…
Add table
Add a link
Reference in a new issue