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
4
scripts/formation/states/tx/__init__.py
Normal file
4
scripts/formation/states/tx/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from .config import CONFIG
|
||||
from .adapter import TXPortal
|
||||
|
||||
__all__ = ["CONFIG", "TXPortal"]
|
||||
205
scripts/formation/states/tx/adapter.py
Normal file
205
scripts/formation/states/tx/adapter.py
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
"""Texas — SOSDirect SOS portal automation.
|
||||
|
||||
SOSDirect (direct.sos.state.tx.us) is the Texas Secretary of State's
|
||||
online filing system. It requires an account login. Name search is
|
||||
available without login via the Comptroller's Taxable Entity Search.
|
||||
|
||||
Key URLs:
|
||||
Name search: https://mycpa.cpa.state.tx.us/coa/Index.html
|
||||
SOSDirect: https://direct.sos.state.tx.us
|
||||
Filing: SOSDirect → Corporations → File a Certificate of Formation
|
||||
|
||||
Fees:
|
||||
LLC: $300 (Certificate of Formation — Domestic LLC)
|
||||
Corp: $300 (Certificate of Formation — Domestic Corp)
|
||||
Expedited: +$25 (24-hour), +$50 (same-day)
|
||||
|
||||
Notes:
|
||||
- Texas uses "Certificate of Formation" (not Articles of Organization)
|
||||
- No publication requirement
|
||||
- Franchise tax applies only if revenue > $2.47M (most small carriers exempt)
|
||||
- SOSDirect uses ASP.NET with __VIEWSTATE like WY; human-pace typing required
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
from scripts.formation.base import (
|
||||
StatePortal,
|
||||
NameSearchResult,
|
||||
FormationOrder,
|
||||
FilingResult,
|
||||
FilingStatus,
|
||||
EntityType,
|
||||
)
|
||||
from .config import CONFIG
|
||||
|
||||
|
||||
class TXPortal(StatePortal):
|
||||
STATE_CODE = "TX"
|
||||
STATE_NAME = "Texas"
|
||||
PORTAL_NAME = "SOSDirect"
|
||||
PORTAL_URL = "https://direct.sos.state.tx.us"
|
||||
NWRA_ADDRESS = CONFIG["registered_agent"]["street"]
|
||||
NWRA_CITY = CONFIG["registered_agent"]["city"]
|
||||
NWRA_STATE = CONFIG["registered_agent"]["state"]
|
||||
NWRA_ZIP = CONFIG["registered_agent"]["zip"]
|
||||
|
||||
# ── Name Search (Comptroller Taxable Entity Search — no login) ──────
|
||||
|
||||
async def search_name(self, name: str) -> NameSearchResult:
|
||||
"""Search Texas business name availability via the Comptroller
|
||||
Taxable Entity Search (free, no login required).
|
||||
|
||||
URL: https://mycpa.cpa.state.tx.us/coa/Index.html
|
||||
|
||||
This searches the Comptroller's database, not the SOS. A name
|
||||
can be "available" in the Comptroller DB but reserved at SOS.
|
||||
For definitive availability, SOSDirect's name check is better —
|
||||
but requires login. We check Comptroller first (free + fast),
|
||||
then flag for SOSDirect confirmation if the customer proceeds.
|
||||
"""
|
||||
try:
|
||||
page = await self.start_browser()
|
||||
await page.goto(
|
||||
"https://mycpa.cpa.state.tx.us/coa/Index.html",
|
||||
wait_until="networkidle",
|
||||
)
|
||||
await self.human_delay(1.0, 2.0)
|
||||
|
||||
# The Comptroller search page has a text input + search button
|
||||
# Selector: input field for entity name
|
||||
await page.fill(
|
||||
'input[name="entityName"], input#entityName, input[type="text"]',
|
||||
"",
|
||||
)
|
||||
await self.type_slowly(
|
||||
page,
|
||||
'input[name="entityName"], input#entityName, input[type="text"]',
|
||||
name,
|
||||
)
|
||||
await self.human_delay(0.5, 1.0)
|
||||
|
||||
# Click search
|
||||
await page.click(
|
||||
'input[type="submit"], button[type="submit"], '
|
||||
'#searchButton, input[value="Search"]'
|
||||
)
|
||||
await page.wait_for_load_state("networkidle")
|
||||
await self.human_delay(1.0, 2.0)
|
||||
|
||||
# Parse results
|
||||
content = await page.content()
|
||||
await self.screenshot(page, f"tx_name_search_{name}")
|
||||
|
||||
# Check for "no results" indicator
|
||||
no_results = (
|
||||
"no match" in content.lower()
|
||||
or "no entities found" in content.lower()
|
||||
or "no records" in content.lower()
|
||||
or "0 results" in content.lower()
|
||||
)
|
||||
|
||||
if no_results:
|
||||
return NameSearchResult(
|
||||
available=True,
|
||||
exact_match=False,
|
||||
similar_names=[],
|
||||
state_code="TX",
|
||||
searched_name=name,
|
||||
raw_response=content[:2000],
|
||||
)
|
||||
|
||||
# Extract matching entity names from results
|
||||
similar: list[str] = []
|
||||
# Common patterns: table rows with entity names
|
||||
name_pattern = re.compile(
|
||||
r'<td[^>]*>([^<]*?' + re.escape(name[:10]) + r'[^<]*?)</td>',
|
||||
re.IGNORECASE,
|
||||
)
|
||||
for m in name_pattern.finditer(content):
|
||||
found = m.group(1).strip()
|
||||
if found and len(found) > 3:
|
||||
similar.append(found)
|
||||
|
||||
# Exact match = one of the results matches our name closely
|
||||
exact = any(
|
||||
s.upper().replace(",", "").replace(".", "").strip()
|
||||
== name.upper().replace(",", "").replace(".", "").strip()
|
||||
for s in similar
|
||||
)
|
||||
|
||||
return NameSearchResult(
|
||||
available=not exact,
|
||||
exact_match=exact,
|
||||
similar_names=similar[:10],
|
||||
state_code="TX",
|
||||
searched_name=name,
|
||||
raw_response=content[:2000],
|
||||
)
|
||||
|
||||
except Exception as exc:
|
||||
return NameSearchResult(
|
||||
available=False,
|
||||
state_code="TX",
|
||||
searched_name=name,
|
||||
raw_response=f"Error: {exc}",
|
||||
)
|
||||
|
||||
# ── LLC Filing (SOSDirect — requires login) ─────────────────────────
|
||||
|
||||
async def file_llc(self, order: FormationOrder) -> FilingResult:
|
||||
"""File a Certificate of Formation for a Texas LLC via SOSDirect.
|
||||
|
||||
SOSDirect flow:
|
||||
1. Login with SOS account credentials
|
||||
2. Navigate: Corporations → File a Document → Certificate of Formation
|
||||
3. Select entity type: Domestic Limited Liability Company (Form 205)
|
||||
4. Fill form fields (name, RA, members, management type, purpose)
|
||||
5. Pay $300 ($325 for 24hr expedited, $350 for same-day)
|
||||
6. Capture confirmation + filing number
|
||||
|
||||
Selectors need verification against live portal. The form is a
|
||||
multi-step ASP.NET WebForms wizard.
|
||||
"""
|
||||
# TODO: Verify selectors against live SOSDirect portal session
|
||||
# For now, return a stub that creates an admin todo for manual filing
|
||||
return FilingResult(
|
||||
success=False,
|
||||
status=FilingStatus.PENDING,
|
||||
state_code="TX",
|
||||
entity_name=order.entity_name,
|
||||
error_message=(
|
||||
"TX SOSDirect adapter selectors pending verification. "
|
||||
"Admin: file manually at https://direct.sos.state.tx.us "
|
||||
f"— LLC Certificate of Formation (Form 205), ${CONFIG['fees']['llc']}."
|
||||
),
|
||||
)
|
||||
|
||||
# ── Corporation Filing ───────────────────────────────────────────────
|
||||
|
||||
async def file_corporation(self, order: FormationOrder) -> FilingResult:
|
||||
"""File a Certificate of Formation for a Texas corporation.
|
||||
|
||||
Same SOSDirect flow as LLC but selects:
|
||||
Domestic For-Profit Corporation (Form 201) or
|
||||
Domestic Nonprofit Corporation (Form 202).
|
||||
"""
|
||||
return FilingResult(
|
||||
success=False,
|
||||
status=FilingStatus.PENDING,
|
||||
state_code="TX",
|
||||
entity_name=order.entity_name,
|
||||
error_message=(
|
||||
"TX SOSDirect adapter selectors pending verification. "
|
||||
"Admin: file manually at https://direct.sos.state.tx.us "
|
||||
f"— Corp Certificate of Formation, ${CONFIG['fees']['corporation']}."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def adapter() -> TXPortal:
|
||||
return TXPortal()
|
||||
75
scripts/formation/states/tx/config.py
Normal file
75
scripts/formation/states/tx/config.py
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
"""Texas Secretary of State — SOSDirect portal configuration."""
|
||||
|
||||
CONFIG = {
|
||||
"state": "Texas",
|
||||
"abbreviation": "TX",
|
||||
"agency": "Secretary of State",
|
||||
"agency_url": "https://sos.state.tx.us",
|
||||
"portal_name": "SOSDirect",
|
||||
"portal_url": "https://direct.sos.state.tx.us",
|
||||
"portal_login_required": True,
|
||||
|
||||
# Name search — Comptroller Taxable Entity Search (free, no login)
|
||||
"name_search_url": "https://mycpa.cpa.state.tx.us/coa/Index.html",
|
||||
# Alternative: SOSDirect name availability (requires login)
|
||||
"name_search_url_alt": "https://direct.sos.state.tx.us",
|
||||
|
||||
# Filing status check
|
||||
"filing_status_url": "https://webservices.sos.state.tx.us/filing-status/status.aspx",
|
||||
|
||||
# NW Registered Agent
|
||||
"registered_agent": {
|
||||
"name": "Northwest Registered Agent",
|
||||
"street": "5900 Balcones Dr Ste 100",
|
||||
"city": "Austin",
|
||||
"state": "TX",
|
||||
"zip": "78731",
|
||||
},
|
||||
|
||||
# Fees (in USD, not cents — state portal charges in dollars)
|
||||
"fees": {
|
||||
"llc": 300,
|
||||
"corporation": 300,
|
||||
"nonprofit": 25,
|
||||
"lp": 750,
|
||||
"expedited_24hr": 25, # add-on for 24-hour processing
|
||||
"expedited_same_day": 50, # add-on for same-day
|
||||
"name_reservation": 40,
|
||||
"name_reservation_days": 120,
|
||||
},
|
||||
|
||||
# Selectors — verified against live portal on YYYY-MM-DD
|
||||
# TODO: Verify during first live filing session
|
||||
"selectors": {
|
||||
# Comptroller name search page
|
||||
"search_input": 'input[name="entityName"], input#entityName',
|
||||
"search_button": 'input[type="submit"], #searchButton',
|
||||
"results_table": "table.resultsTable, table#searchResults, table",
|
||||
"no_results": ".noResults, .noMatch",
|
||||
|
||||
# SOSDirect login
|
||||
"login_username": 'input[name="username"], input#txtUserName',
|
||||
"login_password": 'input[name="password"], input#txtPassword',
|
||||
"login_submit": 'input[type="submit"], #btnLogin',
|
||||
|
||||
# SOSDirect filing form (Form 205 - LLC)
|
||||
"entity_name": 'input[name="entityName"], #txtEntityName',
|
||||
"ra_name": 'input[name="agentName"], #txtAgentName',
|
||||
"ra_street": 'input[name="agentStreet"], #txtAgentStreet',
|
||||
"ra_city": 'input[name="agentCity"], #txtAgentCity',
|
||||
"ra_state": 'select[name="agentState"], #ddlAgentState',
|
||||
"ra_zip": 'input[name="agentZip"], #txtAgentZip',
|
||||
"purpose": 'textarea[name="purpose"], #txtPurpose',
|
||||
"management_type_member": 'input[value="member"], #rbMemberManaged',
|
||||
"management_type_manager": 'input[value="manager"], #rbManagerManaged',
|
||||
"member_name": 'input[name="memberName"], #txtMemberName',
|
||||
"member_address": 'input[name="memberAddress"], #txtMemberAddress',
|
||||
},
|
||||
|
||||
"notes": (
|
||||
"Texas uses 'Certificate of Formation' (not Articles). "
|
||||
"Franchise tax applies only to revenue > $2.47M (most small carriers exempt). "
|
||||
"SOSDirect requires account creation (free). "
|
||||
"No publication requirement."
|
||||
),
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue