This commit is contained in:
@@ -16,12 +16,14 @@ if __package__ in (None, ""):
|
||||
from app.scoring import DEFAULT_RULES, signals_for_business, evaluate_score, score_business_opportunity, SCORE_VERSION
|
||||
from app.ai_assistance import generate as generate_ai, input_fingerprint, evidence_hashes, provider_status, MAX_INPUT_ITEMS, MAX_OUTPUT_CHARS
|
||||
from app.ai_opportunity import assess_opportunity, DETERMINISTIC_ASSESSMENT_THRESHOLD, ASSESSMENT_SCHEMA_VERSION
|
||||
from app.enrichment import enrich_website, enrich_domain, enrich_contacts
|
||||
from app.discovery import discover as scoped_discover
|
||||
from app.ai_research import provider_status as ai_research_provider_status, configure_db as configure_ai_research_db, validate_criteria as validate_ai_research_criteria, AIResearchConfigError
|
||||
from app.search_provider import provider_status as search_provider_status
|
||||
from app.config import load_config
|
||||
from app.provider_config import validate_payload as validate_remote_provider, encrypt as encrypt_provider_secret, decrypt as decrypt_provider_secret, safe_status as remote_provider_status, test_connectivity as test_remote_connectivity
|
||||
else:
|
||||
from .enrichment import enrich_website, enrich_domain, enrich_contacts
|
||||
from .domain import deduplication_key, deduplicate_businesses, is_suppressed, normalize_business, normalize_domain, normalize_phone, match_businesses
|
||||
from .sources import adapter_for, contains_secret, available_adapters, normalize_record, circuit_is_open, DISCOVERY_CRITERIA_FIELDS, GoogleBrowserSearchBlocked
|
||||
from .domain_intelligence import normalize_registrable_domain, resolve_domain, generate_candidate_domains
|
||||
@@ -961,6 +963,39 @@ class ApiHandler(BaseHTTPRequestHandler):
|
||||
self.audit(db,user,"domain.availability.checked",str(bid)); db.commit()
|
||||
return self.send_json(200,{"business_id":bid,"status":"unknown","reason":"not_configured","provider_configured":False,"items":[{"domain":d,"status":"unknown","reason":"not_configured"} for d in domains]})
|
||||
|
||||
def enrich_business(self, bid, payload, db, user):
|
||||
"""Post-discovery website/domain/contact enrichment for one business."""
|
||||
org = user["organization_id"]
|
||||
business_row = self.business(db, bid, org)
|
||||
if not business_row:
|
||||
return self.send_json(404, {"error": "not_found"})
|
||||
business = dict(business_row)
|
||||
url = str(business.get("website") or payload.get("url", "")).strip()
|
||||
domain = business.get("website_domain") or normalize_registrable_domain(url)
|
||||
suppressions=[dict(r) for r in db.execute("SELECT kind,value FROM suppressions WHERE organization_id=? AND active=1",(org,))]
|
||||
enrichment = {
|
||||
"business_id": bid,
|
||||
"website": None,
|
||||
"domain": enrich_domain(domain) if domain else None,
|
||||
"contacts": [],
|
||||
}
|
||||
if url:
|
||||
scan = enrich_website(url, fetch=lambda u, timeout=5.0, max_bytes=256*1024: scan_website(u, max_pages=1))
|
||||
enrichment["website"] = scan
|
||||
html = scan.get("html") or ""
|
||||
if not html and scan.get("body"):
|
||||
body = scan["body"]
|
||||
html = body.decode("utf-8", "replace") if isinstance(body, bytes) else str(body)
|
||||
if html and isinstance(html, str):
|
||||
contacts = enrich_contacts(html, scan.get("final_url") or url, suppressions=suppressions, max_results=25)
|
||||
enrichment["contacts"] = contacts
|
||||
for contact in contacts:
|
||||
key = hashlib.sha256((str(bid) + contact["source_url"] + contact["kind"] + contact["value"]).encode()).hexdigest()
|
||||
db.execute("INSERT OR IGNORE INTO contact_extractions(organization_id,business_id,website_scan_id,extraction_key,kind,value,label,classification,confidence,source_url,public_business,mx_status,suppressed,do_not_contact,provenance) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",(org,bid,None,key,contact["kind"],contact["value"],contact["label"],contact["classification"],contact["confidence"],contact["source_url"],1,contact["mx_status"],int(contact["suppressed"]),int(contact["do_not_contact"]),contact["provenance"]))
|
||||
db.commit()
|
||||
self.audit(db, user, "business.enriched", str(bid)); db.commit()
|
||||
return self.send_json(200, enrichment)
|
||||
|
||||
def list_jobs(self, db, org, query):
|
||||
try:
|
||||
limit=max(1,min(int(query.get("page_size",[50])[0]),JOB_PAGE_SIZE)); page=max(1,int(query.get("page",[1])[0])); offset=max(0,int(query.get("offset",[0])[0]))+(page-1)*limit
|
||||
@@ -1220,6 +1255,7 @@ class ApiHandler(BaseHTTPRequestHandler):
|
||||
if path=="/api/v1/businesses/bulk-review": return self.bulk_review(payload,db,user)
|
||||
bits_ai=path.split("/")
|
||||
if len(bits_ai)==7 and bits_ai[:4]==["","api","v1","businesses"] and bits_ai[5]=="ai" and bits_ai[6]=="opportunity-assessment": return self.assess_ai_opportunity(int(bits_ai[4]) if bits_ai[4].isdigit() else -1,payload,db,user)
|
||||
if len(bits_ai)==6 and bits_ai[:4]==["","api","v1","businesses"] and bits_ai[5]=="enrichment": return self.enrich_business(int(bits_ai[4]) if bits_ai[4].isdigit() else -1,payload,db,user)
|
||||
if len(bits_ai)==7 and bits_ai[:4]==["","api","v1","businesses"] and bits_ai[5]=="ai" and bits_ai[6]=="suggest": return self.suggest_ai(int(bits_ai[4]) if bits_ai[4].isdigit() else -1,payload,db,user)
|
||||
if len(bits_ai)==6 and bits_ai[:4]==["","api","v1","ai-runs"] and bits_ai[4].isdigit() and bits_ai[5] in {"approve","reject"}: return self.decide_ai(int(bits_ai[4]),bits_ai[5],db,user)
|
||||
if path=="/api/v1/score-rules": return self.create_score_rule(payload,db,user)
|
||||
|
||||
Reference in New Issue
Block a user