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
175
scripts/formation/test_name_search.py
Normal file
175
scripts/formation/test_name_search.py
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test name search automation for any state.
|
||||
|
||||
Usage:
|
||||
python -m scripts.formation.test_name_search WY "Test Business LLC"
|
||||
python -m scripts.formation.test_name_search CO "Acme Holdings"
|
||||
python -m scripts.formation.test_name_search DE "First State Corp"
|
||||
python -m scripts.formation.test_name_search --all "Test Business LLC"
|
||||
|
||||
Reports:
|
||||
- Whether name search succeeded
|
||||
- Whether the portal was reachable
|
||||
- What selectors worked
|
||||
- What barriers were encountered (CAPTCHA, login, WAF, etc.)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(name)s] %(levelname)s %(message)s",
|
||||
)
|
||||
LOG = logging.getLogger("test_name_search")
|
||||
|
||||
|
||||
async def test_state(state_code: str, name: str) -> dict:
|
||||
"""Test name search for a single state."""
|
||||
from scripts.formation.states import get_adapter, get_config, STATES
|
||||
|
||||
state_code = state_code.upper()
|
||||
if state_code not in STATES:
|
||||
return {"state": state_code, "status": "ERROR", "error": f"Unknown state: {state_code}"}
|
||||
|
||||
config = get_config(state_code)
|
||||
state_name = STATES[state_code]["name"]
|
||||
search_method = config.get("search_method", "playwright")
|
||||
|
||||
LOG.info("Testing %s (%s) — method: %s, name: '%s'", state_code, state_name, search_method, name)
|
||||
start = time.time()
|
||||
|
||||
result = {
|
||||
"state": state_code,
|
||||
"state_name": state_name,
|
||||
"search_method": search_method,
|
||||
"portal_url": config.get("name_search_url", ""),
|
||||
"test_name": name,
|
||||
"started_at": datetime.utcnow().isoformat(),
|
||||
}
|
||||
|
||||
try:
|
||||
adapter = get_adapter(state_code)
|
||||
search_result = await adapter.search_name(name)
|
||||
|
||||
result["status"] = "SUCCESS" if search_result.searched_name else "PARTIAL"
|
||||
result["available"] = search_result.available
|
||||
result["exact_match"] = search_result.exact_match
|
||||
result["similar_names"] = search_result.similar_names[:5]
|
||||
result["raw_response_length"] = len(search_result.raw_response)
|
||||
|
||||
if "not yet configured" in search_result.raw_response.lower():
|
||||
result["status"] = "NOT_CONFIGURED"
|
||||
result["barrier"] = "Selectors not configured in config.py"
|
||||
elif "captcha" in search_result.raw_response.lower():
|
||||
result["status"] = "BLOCKED_CAPTCHA"
|
||||
result["barrier"] = "CAPTCHA detected"
|
||||
elif "error" in search_result.raw_response.lower() and not search_result.available:
|
||||
result["status"] = "ERROR"
|
||||
result["error"] = search_result.raw_response[:200]
|
||||
|
||||
except Exception as e:
|
||||
result["status"] = "ERROR"
|
||||
result["error"] = str(e)
|
||||
LOG.error("Test failed for %s: %s", state_code, e)
|
||||
|
||||
elapsed = time.time() - start
|
||||
result["elapsed_seconds"] = round(elapsed, 2)
|
||||
result["finished_at"] = datetime.utcnow().isoformat()
|
||||
|
||||
# Status emoji for readability
|
||||
status_icon = {
|
||||
"SUCCESS": "✓",
|
||||
"PARTIAL": "~",
|
||||
"NOT_CONFIGURED": "○",
|
||||
"BLOCKED_CAPTCHA": "⚠",
|
||||
"BLOCKED_LOGIN": "🔒",
|
||||
"ERROR": "✗",
|
||||
}.get(result["status"], "?")
|
||||
|
||||
LOG.info(
|
||||
"%s %s (%s): %s — available=%s, elapsed=%.1fs",
|
||||
status_icon, state_code, state_name, result["status"],
|
||||
result.get("available", "?"), elapsed,
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def test_all_states(name: str) -> list[dict]:
|
||||
"""Test name search for all 51 states sequentially."""
|
||||
from scripts.formation.states import STATES
|
||||
|
||||
results = []
|
||||
for code in sorted(STATES.keys()):
|
||||
result = await test_state(code, name)
|
||||
results.append(result)
|
||||
|
||||
# Brief pause between states
|
||||
if result["status"] not in ("NOT_CONFIGURED", "ERROR"):
|
||||
await asyncio.sleep(2)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def print_summary(results: list[dict]):
|
||||
"""Print a summary table of test results."""
|
||||
print("\n" + "=" * 80)
|
||||
print("STATE NAME SEARCH TEST RESULTS")
|
||||
print("=" * 80)
|
||||
print(f"{'State':<6} {'Name':<25} {'Status':<20} {'Available':<10} {'Time':<8} {'Barrier'}")
|
||||
print("-" * 80)
|
||||
|
||||
for r in results:
|
||||
barrier = r.get("barrier", r.get("error", ""))[:30]
|
||||
print(
|
||||
f"{r['state']:<6} {r.get('state_name','?'):<25} {r['status']:<20} "
|
||||
f"{str(r.get('available','?')):<10} {r.get('elapsed_seconds',0):>6.1f}s {barrier}"
|
||||
)
|
||||
|
||||
# Summary counts
|
||||
statuses = {}
|
||||
for r in results:
|
||||
s = r["status"]
|
||||
statuses[s] = statuses.get(s, 0) + 1
|
||||
|
||||
print("-" * 80)
|
||||
print("Summary:", " | ".join(f"{k}: {v}" for k, v in sorted(statuses.items())))
|
||||
print(f"Total: {len(results)} states tested")
|
||||
|
||||
|
||||
def main():
|
||||
args = sys.argv[1:]
|
||||
|
||||
if len(args) < 2:
|
||||
print("Usage:")
|
||||
print(" python -m scripts.formation.test_name_search WY 'Test Business LLC'")
|
||||
print(" python -m scripts.formation.test_name_search --all 'Test Business LLC'")
|
||||
sys.exit(1)
|
||||
|
||||
if args[0] == "--all":
|
||||
name = " ".join(args[1:])
|
||||
results = asyncio.run(test_all_states(name))
|
||||
print_summary(results)
|
||||
|
||||
# Save results to JSON
|
||||
output_path = "/tmp/name_search_test_results.json"
|
||||
with open(output_path, "w") as f:
|
||||
json.dump(results, f, indent=2)
|
||||
print(f"\nFull results saved to: {output_path}")
|
||||
else:
|
||||
state_code = args[0]
|
||||
name = " ".join(args[1:])
|
||||
result = asyncio.run(test_state(state_code, name))
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue