add criteria-first discovery search provider

This commit is contained in:
Marco0300
2026-09-03 20:04:08 +02:00
parent e3257f1ce9
commit 725ef0db9f
8 changed files with 202 additions and 16 deletions
+16 -7
View File
@@ -1,8 +1,9 @@
"""Bounded, public-only prospect discovery using an explicit seed allowlist.
"""Bounded, public-only prospect discovery with optional criteria search.
There is deliberately no general web search or arbitrary URL input here: callers provide
at most a small set of public seed pages. Only links found on those seeds become candidate
sites, and subsequent crawling is same-origin, bounded, and SSRF-checked by the scanner.
at most a small set of public seed pages. When seeds are omitted, a configured search
provider supplies bounded seed URLs. Links found on those seeds become candidate sites,
and subsequent crawling is same-origin, bounded, and SSRF-checked by the scanner.
"""
from __future__ import annotations
@@ -12,6 +13,7 @@ from urllib.parse import urljoin, urldefrag, urlparse
from .contact_extractor import extract_contacts
from .website_scanner import MAX_BYTES, MAX_REDIRECTS, _fetch, validate_url
from .search_provider import search as search_provider
MAX_SEEDS = 5
MAX_PAGES = 20
@@ -61,14 +63,21 @@ def _criteria_match(text, criteria):
return not keywords or all(str(k).strip().lower() in haystack for k in keywords if str(k).strip())
def discover(criteria, seed_urls, *, max_pages=MAX_PAGES, max_candidates=MAX_CANDIDATES):
def discover(criteria, seed_urls=None, *, max_pages=MAX_PAGES, max_candidates=MAX_CANDIDATES):
if not isinstance(criteria, dict) or len(criteria) > 20: raise ValueError("invalid_criteria")
if not isinstance(seed_urls, list) or not 0 < len(seed_urls) <= MAX_SEEDS: raise ValueError("seed_urls_required")
try: max_pages = int(max_pages); max_candidates = int(max_candidates)
except (TypeError, ValueError): raise ValueError("invalid_limits")
if not 1 <= max_pages <= MAX_PAGES or not 1 <= max_candidates <= MAX_CANDIDATES: raise ValueError("invalid_limits")
if seed_urls is None:
seeds = search_provider(criteria, max_candidates)
mechanism = "criteria_search_provider"
else:
if not isinstance(seed_urls, list) or not 0 < len(seed_urls) <= MAX_SEEDS: raise ValueError("seed_urls_required")
seeds = list(seed_urls)
mechanism = "explicit_seed_allowlist"
raw_seeds = list(seeds)
seeds = []
for raw in seed_urls:
for raw in raw_seeds:
try: safe = validate_url(raw)
except ValueError as exc: raise ValueError("unsafe_seed_url") from exc
if safe not in seeds: seeds.append(safe)
@@ -125,5 +134,5 @@ def discover(criteria, seed_urls, *, max_pages=MAX_PAGES, max_candidates=MAX_CAN
evidence.append({"kind": "discovery_page", "url": page["url"], "claim": claim, "provenance": "scoped_discovery"})
deduped = {(x["kind"], x["value"]): x for x in contacts}
name = next((x["headings"][0] for x in pages if x["headings"]), next((x["title"] for x in pages if x["title"]), domain))
results.append({"name": name[:200], "website": root, "website_domain": domain, "description": text[:1000], "contacts": list(deduped.values())[:100], "evidence": evidence, "pages": pages, "pages_crawled": len(pages), "provenance": {"mechanism": "explicit_seed_allowlist", "seed_urls": seeds, "root_url": root}})
results.append({"name": name[:200], "website": root, "website_domain": domain, "description": text[:1000], "contacts": list(deduped.values())[:100], "evidence": evidence, "pages": pages, "pages_crawled": len(pages), "provenance": {"mechanism": mechanism, "seed_urls": seeds, "root_url": root}})
return {"candidates": results, "seeds": seeds, "pages_limit": max_pages, "candidate_limit": max_candidates}