linvest21's picture
download
raw
30.8 kB
from __future__ import annotations
import html
import json
import os
import re
import urllib.parse
import urllib.request
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from n21.config import write_json
from n21.config import load_structured
from n21.settings import CONFIG_ROOT
from observability.audit_log import utc_now
from data_pipeline.source_quality_certifier import certify_source_candidate
OFFICIAL_DOMAIN_HINTS = {
"sec.gov": "US government public information",
"investor.gov": "US government public information",
"federalreserve.gov": "US government public information",
"treasury.gov": "US government public information",
"fasb.org": "Publicly available standards-source page; license terms must be reviewed before training use",
"cfainstitute.org": "Publicly available education page; license terms must be reviewed before training use",
"stern.nyu.edu": "Publicly available academic page; license terms must be reviewed before training use",
}
ROLE_TERMS = {
"researcher": [
"10-K 10-Q annual report financial statement analysis",
"EDGAR public company disclosure MD&A risk factors",
"equity valuation earnings per share price earnings ratio",
],
"portfolio_manager": [
"portfolio construction asset allocation equity portfolio management",
"diversification risk return rebalancing public investor guide",
],
"risk_manager": [
"market risk liquidity risk financial statement red flags public company",
"risk factors disclosure internal controls financial reporting",
],
"performance_manager": [
"investment performance attribution benchmark risk adjusted return",
"portfolio performance measurement tracking error information ratio",
],
"client_portfolio_manager": [
"client investment reporting suitability disclosure portfolio risk",
"investor education diversification risk tolerance asset allocation",
],
"chief_investment_officer": [
"capital market assumptions asset allocation investment policy",
"market outlook financial stability risk premium investment committee",
],
}
ASSET_TERMS = {
"equity": ["equity research", "public companies", "stock analysis"],
"fixed_income": ["fixed income", "bond yield credit risk", "interest rates"],
"multi_asset": ["multi asset", "asset allocation", "cross asset risk"],
}
RELEVANCE_TERMS = {
"equity": [
"10-k",
"10-q",
"8-k",
"annual report",
"financial statement",
"edgar",
"earnings",
"valuation",
"stock",
"public compan",
"disclosure",
],
"fixed_income": [
"bond",
"yield",
"duration",
"credit",
"interest rate",
"treasury",
"fixed income",
"default",
"spread",
],
"multi_asset": [
"asset allocation",
"diversification",
"portfolio",
"risk",
"financial stability",
"capital market",
"cross asset",
],
}
DEFAULT_SEED_PAGES = {
"equity": [
"https://www.investor.gov/search?keys=10-k",
"https://www.investor.gov/search?keys=10-q",
"https://www.investor.gov/search?keys=edgar",
"https://www.investor.gov/search?keys=financial%20statements",
"https://www.sec.gov/investor/pubs_annote.shtml",
],
"fixed_income": [
"https://www.investor.gov/search?keys=bonds",
"https://www.investor.gov/search?keys=fixed%20income",
"https://www.federalreserve.gov/publications.htm",
"https://home.treasury.gov/policy-issues/financing-the-government/interest-rate-statistics",
],
"multi_asset": [
"https://www.investor.gov/search?keys=asset%20allocation",
"https://www.investor.gov/search?keys=diversification",
"https://www.federalreserve.gov/publications/financial-stability-report.htm",
],
}
SEC_API_SEED_CIKS = {
"equity": [
"0000320193", # Apple
"0000789019", # Microsoft
"0001045810", # NVIDIA
"0001018724", # Amazon
"0001318605", # Tesla
]
}
DIRECT_FALLBACK_SOURCES = {
("equity", "researcher"): [
{
"title": "Investor.gov Form 10-K",
"url": "https://www.investor.gov/additional-resources/general-resources/glossary/form-10-k",
"source_type": "html",
"rationale": "Official Investor.gov glossary source explaining Form 10-K annual reports and audited financial statements.",
},
{
"title": "Investor.gov Form 10-Q",
"url": "https://www.investor.gov/additional-resources/general-resources/glossary/form-10-q",
"source_type": "html",
"rationale": "Official Investor.gov glossary source explaining quarterly reports, unaudited financial statements, and EDGAR filtering.",
},
{
"title": "Investor.gov Using EDGAR to Research Public Companies",
"url": "https://www.investor.gov/researching-managing-investments/researching-investments/using-edgar-researching-public-companies",
"source_type": "html",
"rationale": "Official Investor.gov guide to researching 10-K, 10-Q, 8-K, proxy, and ownership filings.",
},
{
"title": "Investor.gov Form 8-K",
"url": "https://www.investor.gov/introduction-investing/investing-basics/glossary/form-8-k",
"source_type": "html",
"rationale": "Official Investor.gov source for material current-report events used in equity research monitoring.",
},
{
"title": "Investor.gov Market Capitalization",
"url": "https://www.investor.gov/additional-resources/general-resources/glossary/market-capitalization",
"source_type": "html",
"rationale": "Official Investor.gov source for market capitalization, a core public-equity sizing and valuation concept.",
},
{
"title": "Investor.gov Net Income",
"url": "https://www.investor.gov/introduction-investing/investing-basics/glossary/net-income",
"source_type": "html",
"rationale": "Official Investor.gov source for net income, a core profitability and valuation input.",
},
],
("equity", "all"): [
{
"title": "Investor.gov Diversification",
"url": "https://www.investor.gov/introduction-investing/investing-basics/glossary/diversification",
"source_type": "html",
"rationale": "Official Investor.gov source for diversification across equity super-agent workflows.",
},
{
"title": "Investor.gov Risk",
"url": "https://www.investor.gov/introduction-investing/investing-basics/glossary/risk",
"source_type": "html",
"rationale": "Official Investor.gov source for investment risk terminology.",
},
],
("fixed_income", "all"): [
{
"title": "Investor.gov Bonds",
"url": "https://www.investor.gov/introduction-investing/investing-basics/investment-products/bonds-or-fixed-income-products/bonds",
"source_type": "html",
"rationale": "Official Investor.gov source for bond structure, prices, yields, and fixed-income risks.",
},
{
"title": "Investor.gov Interest Rate Risk",
"url": "https://www.investor.gov/introduction-investing/investing-basics/glossary/interest-rate-risk",
"source_type": "html",
"rationale": "Official Investor.gov source for interest-rate risk in fixed-income analysis.",
},
],
("multi_asset", "all"): [
{
"title": "Investor.gov Asset Allocation",
"url": "https://www.investor.gov/introduction-investing/investing-basics/glossary/asset-allocation",
"source_type": "html",
"rationale": "Official Investor.gov source for asset allocation and portfolio construction.",
},
{
"title": "Investor.gov Diversification",
"url": "https://www.investor.gov/introduction-investing/investing-basics/glossary/diversification",
"source_type": "html",
"rationale": "Official Investor.gov source for diversification across asset classes.",
},
],
}
@dataclass(frozen=True)
class Candidate:
title: str
url: str
source_type: str
license_hint: str
rationale: str
query: str
provider: str
rank: int
score: float
ai_certification: dict[str, Any]
def _request_text(url: str, *, timeout: int = 20) -> str:
user_agent = os.environ.get(
"SHFT_HTTP_USER_AGENT",
"Linvest21-SHFT-live-source-discovery/1.0 (public-source transparency manifest; contact=linvest21)",
)
request = urllib.request.Request(
url,
headers={
"User-Agent": user_agent,
"Accept": "text/html,application/xhtml+xml,text/plain,*/*;q=0.8",
},
)
with urllib.request.urlopen(request, timeout=timeout) as response:
return response.read(2_000_000).decode("utf-8", errors="ignore")
def _extract_links(markup: str) -> list[tuple[str, str]]:
links: list[tuple[str, str]] = []
for match in re.finditer(r'<a\b[^>]*href=["\']([^"\']+)["\'][^>]*>(.*?)</a>', markup, flags=re.I | re.S):
href = html.unescape(match.group(1))
text = re.sub(r"<[^>]+>", " ", match.group(2))
text = re.sub(r"\s+", " ", html.unescape(text)).strip()
if href.startswith("/l/?") or "duckduckgo.com/l/?" in href:
parsed = urllib.parse.urlparse(href)
params = urllib.parse.parse_qs(parsed.query)
href = params.get("uddg", [href])[0]
links.append((href, text))
return links
def _source_type(url: str) -> str:
path = urllib.parse.urlparse(url).path.lower()
if path.endswith(".pdf"):
return "pdf"
if path.endswith(".txt"):
return "txt"
if path.endswith(".md"):
return "md"
if path.endswith(".jsonl"):
return "jsonl"
return "html"
def _domain(url: str) -> str:
host = urllib.parse.urlparse(url).netloc.lower()
if host.startswith("www."):
host = host[4:]
return host
def _normalize_url(url: str) -> str:
return url.split("#", 1)[0].strip()
def _absolute_url(base_url: str, href: str) -> str:
return _normalize_url(urllib.parse.urljoin(base_url, href))
def _license_hint(url: str) -> str:
host = _domain(url)
for domain, hint in OFFICIAL_DOMAIN_HINTS.items():
if host == domain or host.endswith(f".{domain}"):
return hint
return "Publicly available source; license terms must be reviewed before training use"
def _allowed_domain(url: str, domains: list[str]) -> bool:
host = _domain(url)
return any(host == domain or host.endswith(f".{domain}") for domain in domains)
def _relevant_to_asset_role(url: str, title: str, asset_class: str, role: str) -> bool:
haystack = f"{url} {title}".lower().replace("%20", " ")
asset_hits = RELEVANCE_TERMS.get(asset_class, [asset_class.replace("_", " ")])
role_hits = ROLE_TERMS.get(role, [role.replace("_", " ")])
if any(term.lower() in haystack for term in asset_hits):
return True
compact_role_terms = [token for phrase in role_hits for token in re.findall(r"[a-z0-9-]{4,}", phrase.lower())]
return any(token in haystack for token in compact_role_terms[:12])
def _looks_like_navigation_or_index(url: str, title: str) -> bool:
parsed = urllib.parse.urlparse(url)
path = parsed.path.lower().rstrip("/")
query = urllib.parse.parse_qs(parsed.query.lower())
title_norm = re.sub(r"\s+", " ", title.lower()).strip()
if path.endswith("/search") or path == "/search":
return True
if "keys" in query and "page" in query:
return True
if title_norm in {"skip to main content", "current page 1"}:
return True
if re.fullmatch(r"page \d+", title_norm):
return True
if title_norm in {"online publications", "filings forms", "sec filings", "company filings"}:
return True
if "formulario" in title_norm or "/espanol/" in path:
return True
return False
def _score(url: str, title: str, query: str, domains: list[str]) -> float:
host = _domain(url)
score = 0.0
if _allowed_domain(url, domains):
score += 5.0
if host in {"sec.gov", "investor.gov"} or host.endswith(".sec.gov") or host.endswith(".investor.gov"):
score += 3.0
if _source_type(url) == "pdf":
score += 1.0
haystack = f"{title} {url}".lower()
for term in re.findall(r"[a-z0-9-]{3,}", query.lower()):
if term in haystack:
score += 0.15
return round(score, 4)
def _fallback_sources_for(asset_class: str, role: str) -> list[dict[str, Any]]:
sources: list[dict[str, Any]] = []
for key in ((asset_class, role), (asset_class, "all")):
sources.extend(DIRECT_FALLBACK_SOURCES.get(key, []))
return sources
def _reasoning_frame_terms(asset_class: str, role: str) -> list[str]:
path = CONFIG_ROOT / "data" / "reasoning_frames.json"
if not path.exists():
return []
try:
frames = load_structured(path)
except Exception:
return []
terms = frames.get(asset_class, {}).get(role, {}).get("discovery_terms", [])
return [str(term) for term in terms if str(term).strip()]
def build_queries(
asset_class: str,
role: str,
policy: dict[str, Any],
quality_errors: list[str] | None = None,
discovery_attempt: int = 0,
) -> list[str]:
discovery = policy.get("live_discovery", {})
templates = discovery.get("query_templates") or [
"site:{domain} {asset_terms} {role_terms}",
"site:{domain} investor bulletin {asset_terms} {role_terms}",
"site:{domain} PDF {asset_terms} {role_terms}",
]
domains = discovery.get("preferred_domains") or ["sec.gov", "investor.gov"]
asset_terms = ASSET_TERMS.get(asset_class, [asset_class.replace("_", " ")])
role_terms = ROLE_TERMS.get(role, [role.replace("_", " ")])
frame_terms = _reasoning_frame_terms(asset_class, role)
blockers = " ".join(quality_errors or [])
if "critical_pass" in blockers:
role_terms = [
*frame_terms,
*role_terms,
"red flags checklist case study pass fail reasoning",
"worked examples critical decision financial statement analysis",
"reject approve because accounting quality warning signs",
]
if discovery_attempt > 0:
retry_terms = discovery.get("retry_query_terms") or [
"annual report MD&A risk factors audited financial statements",
"investor bulletin financial statements valuation disclosure",
"public company analysis cash flow return on invested capital",
"equity research accounting quality capital allocation moat",
]
# Rotate broader retry terms forward after failed/no-trainable attempts so
# later searches are not just the same exhausted candidate list.
rotate = (discovery_attempt - 1) % len(retry_terms)
ordered_retry_terms = list(retry_terms[rotate:]) + list(retry_terms[:rotate])
role_terms = [*ordered_retry_terms[:3], *role_terms]
queries: list[str] = []
for domain in domains:
for asset_term in asset_terms[:2]:
for role_term in role_terms[:3]:
for template in templates:
queries.append(
template.format(
domain=domain,
asset_terms=asset_term,
role_terms=role_term,
asset_class=asset_class.replace("_", " "),
role=role.replace("_", " "),
)
)
seen: set[str] = set()
unique: list[str] = []
for query in queries:
compact = re.sub(r"\s+", " ", query).strip()
if compact and compact.lower() not in seen:
seen.add(compact.lower())
unique.append(compact)
return unique[: int(discovery.get("max_queries", 24))]
def search_duckduckgo(query: str, *, max_results: int) -> list[tuple[str, str]]:
url = "https://duckduckgo.com/html/?" + urllib.parse.urlencode({"q": query})
markup = _request_text(url)
links: list[tuple[str, str]] = []
seen: set[str] = set()
for href, text in _extract_links(markup):
if not href.startswith("http://") and not href.startswith("https://"):
continue
clean = href.split("#", 1)[0]
if clean.lower() in seen:
continue
seen.add(clean.lower())
links.append((clean, text or clean))
if len(links) >= max_results:
break
return links
def search_sec_submissions_api(asset_class: str, role: str, *, max_results: int) -> list[tuple[str, str]]:
results: list[tuple[str, str]] = []
for cik in SEC_API_SEED_CIKS.get(asset_class, [])[: max(1, max_results)]:
api_url = f"https://data.sec.gov/submissions/CIK{cik}.json"
try:
payload = json.loads(_request_text(api_url))
except Exception:
continue
filings = payload.get("filings", {}).get("recent", {})
forms = filings.get("form", [])
accessions = filings.get("accessionNumber", [])
primary_docs = filings.get("primaryDocument", [])
company = str(payload.get("name") or cik)
for form, accession, primary_doc in zip(forms, accessions, primary_docs):
if form not in {"10-K", "10-Q", "8-K"}:
continue
accession_compact = str(accession).replace("-", "")
filing_url = f"https://www.sec.gov/Archives/edgar/data/{int(cik)}/{accession_compact}/{primary_doc}"
title = f"SEC EDGAR {company} {form} filing"
results.append((filing_url, title))
if len(results) >= max_results:
return results
return results
def discover_public_sources(
*,
asset_class: str,
role: str,
policy: dict[str, Any],
output_dir: Path,
exclude_urls: set[str] | None = None,
quality_errors: list[str] | None = None,
discovery_attempt: int = 0,
) -> dict[str, Any]:
discovery = policy.get("live_discovery", {})
output_dir.mkdir(parents=True, exist_ok=True)
preferred_domains = list(discovery.get("preferred_domains") or ["sec.gov", "investor.gov"])
max_results = int(discovery.get("max_results", 40))
max_results_per_query = int(discovery.get("max_results_per_query", 8))
exclude = {url.strip().lower() for url in (exclude_urls or set()) if url.strip()}
candidates: list[Candidate] = []
search_errors: list[dict[str, Any]] = []
query_diagnostics: list[dict[str, Any]] = []
fallback_diagnostics: list[dict[str, Any]] = []
provider_name = str(discovery.get("provider", "sec_api_first")).lower()
duckduckgo_enabled = bool(
discovery.get("duckduckgo_fallback_enabled", provider_name in {"duckduckgo", "duckduckgo_html", "search"})
)
queries = build_queries(
asset_class,
role,
policy,
quality_errors=quality_errors,
discovery_attempt=discovery_attempt,
)
seen: set[str] = set(exclude)
def consider_url(
*,
url: str,
title: str,
query: str,
provider: str,
rank: int,
require_relevance: bool = False,
diagnostics: dict[str, Any] | None = None,
rationale: str | None = None,
source_type: str | None = None,
) -> bool:
clean = _normalize_url(url)
key = clean.lower()
if key in seen:
if diagnostics is not None:
diagnostics["skipped_duplicate_or_excluded"] = diagnostics.get("skipped_duplicate_or_excluded", 0) + 1
return False
parsed = urllib.parse.urlparse(clean)
if parsed.scheme != "https":
if diagnostics is not None:
diagnostics["skipped_non_https"] = diagnostics.get("skipped_non_https", 0) + 1
seen.add(key)
return False
if not _allowed_domain(clean, preferred_domains):
if diagnostics is not None:
diagnostics["skipped_domain"] = diagnostics.get("skipped_domain", 0) + 1
seen.add(key)
return False
if _looks_like_navigation_or_index(clean, title):
if diagnostics is not None:
diagnostics["skipped_navigation_or_index"] = diagnostics.get("skipped_navigation_or_index", 0) + 1
seen.add(key)
return False
if any(hint in clean.lower() for hint in policy.get("source_risk", {}).get("disallowed_url_hints", [])):
if diagnostics is not None:
diagnostics["skipped_disallowed_url_hint"] = diagnostics.get("skipped_disallowed_url_hint", 0) + 1
seen.add(key)
return False
if require_relevance and not _relevant_to_asset_role(clean, title, asset_class, role):
if diagnostics is not None:
diagnostics["skipped_low_relevance"] = diagnostics.get("skipped_low_relevance", 0) + 1
seen.add(key)
return False
seen.add(key)
score = _score(clean, title, query, preferred_domains)
if provider == "direct_official_fallback":
score += 10.0
source_rationale = rationale or f"Live-discovered public source for {asset_class}/{role}; query={query}"
ai_certification = certify_source_candidate(
asset_class=asset_class,
role=role,
title=title,
url=clean,
source_type=source_type or _source_type(clean),
rationale=source_rationale,
quality_errors=quality_errors,
policy=policy,
)
if ai_certification.get("intended_use") == "reject":
if diagnostics is not None:
diagnostics["skipped_ai_rejected"] = diagnostics.get("skipped_ai_rejected", 0) + 1
return False
candidates.append(
Candidate(
title=title[:140] or parsed.path.rsplit("/", 1)[-1] or parsed.netloc,
url=clean,
source_type=source_type or _source_type(clean),
license_hint=_license_hint(clean),
rationale=source_rationale,
query=query,
provider=provider,
rank=rank,
score=score,
ai_certification=ai_certification,
)
)
if diagnostics is not None:
diagnostics["accepted"] = diagnostics.get("accepted", 0) + 1
return True
for query in queries:
if provider_name in {"sec_api_first", "api_first", "sec_api"}:
diagnostics = {
"query": query,
"provider": "sec_submissions_api",
"raw_result_count": 0,
"accepted": 0,
"skipped_duplicate_or_excluded": 0,
"skipped_non_https": 0,
"skipped_domain": 0,
"skipped_navigation_or_index": 0,
"skipped_disallowed_url_hint": 0,
"skipped_ai_rejected": 0,
}
try:
results = search_sec_submissions_api(asset_class, role, max_results=max_results_per_query)
except Exception as exc:
search_errors.append({"query": query, "provider": "sec_submissions_api", "error": str(exc)})
diagnostics["error"] = str(exc)
results = []
diagnostics["raw_result_count"] = len(results)
for rank, (url, title) in enumerate(results, start=1):
consider_url(
url=url,
title=title,
query=query,
provider="sec_submissions_api",
rank=rank,
diagnostics=diagnostics,
)
query_diagnostics.append(diagnostics)
candidates.sort(key=lambda item: item.score, reverse=True)
if len(candidates) >= max_results:
break
if not duckduckgo_enabled:
continue
diagnostics = {
"query": query,
"provider": "duckduckgo_html",
"raw_result_count": 0,
"accepted": 0,
"skipped_duplicate_or_excluded": 0,
"skipped_non_https": 0,
"skipped_domain": 0,
"skipped_navigation_or_index": 0,
"skipped_disallowed_url_hint": 0,
"skipped_ai_rejected": 0,
}
try:
results = search_duckduckgo(query, max_results=max_results_per_query)
except Exception as exc: # network/search failure is transparent, not fatal
search_errors.append({"query": query, "provider": "duckduckgo_html", "error": str(exc)})
diagnostics["error"] = str(exc)
query_diagnostics.append(diagnostics)
continue
diagnostics["raw_result_count"] = len(results)
for rank, (url, title) in enumerate(results, start=1):
consider_url(
url=url,
title=title,
query=query,
provider="duckduckgo_html",
rank=rank,
diagnostics=diagnostics,
)
query_diagnostics.append(diagnostics)
candidates.sort(key=lambda item: item.score, reverse=True)
if len(candidates) >= max_results:
break
if len(candidates) < max_results:
diagnostics = {
"provider": "direct_official_fallback",
"raw_source_count": 0,
"accepted": 0,
"skipped_duplicate_or_excluded": 0,
"skipped_non_https": 0,
"skipped_domain": 0,
"skipped_navigation_or_index": 0,
"skipped_disallowed_url_hint": 0,
"skipped_ai_rejected": 0,
}
direct_sources = _fallback_sources_for(asset_class, role)
diagnostics["raw_source_count"] = len(direct_sources)
for rank, item in enumerate(direct_sources, start=1):
consider_url(
url=str(item.get("url") or ""),
title=str(item.get("title") or ""),
query="direct_official_fallback",
provider="direct_official_fallback",
rank=rank,
diagnostics=diagnostics,
rationale=str(item.get("rationale") or f"Direct official fallback source for {asset_class}/{role}"),
source_type=str(item.get("source_type") or "") or None,
)
if len(candidates) >= max_results:
break
fallback_diagnostics.append(diagnostics)
candidates.sort(key=lambda item: item.score, reverse=True)
if len(candidates) < max_results:
seed_pages = list(discovery.get("seed_pages") or DEFAULT_SEED_PAGES.get(asset_class, []))
max_seed_links = int(discovery.get("max_seed_links_per_page", 30))
for seed_index, seed_url in enumerate(seed_pages, start=1):
diagnostics = {
"seed_url": seed_url,
"provider": "official_seed_page",
"raw_link_count": 0,
"accepted": 0,
"skipped_duplicate_or_excluded": 0,
"skipped_non_https": 0,
"skipped_domain": 0,
"skipped_navigation_or_index": 0,
"skipped_disallowed_url_hint": 0,
"skipped_low_relevance": 0,
"skipped_ai_rejected": 0,
}
try:
markup = _request_text(seed_url)
links = _extract_links(markup)
except Exception as exc:
search_errors.append({"seed_url": seed_url, "provider": "official_seed_page", "error": str(exc)})
diagnostics["error"] = str(exc)
fallback_diagnostics.append(diagnostics)
continue
diagnostics["raw_link_count"] = len(links)
for rank, (href, title) in enumerate(links[:max_seed_links], start=1):
consider_url(
url=_absolute_url(seed_url, href),
title=title or href,
query=f"seed:{seed_url}",
provider="official_seed_page",
rank=rank,
require_relevance=True,
diagnostics=diagnostics,
rationale=f"Official seed-page link for {asset_class}/{role}; seed={seed_url}",
)
if len(candidates) >= max_results:
break
fallback_diagnostics.append(diagnostics)
candidates.sort(key=lambda item: item.score, reverse=True)
if len(candidates) >= max_results:
break
sources = [
{
"asset_class": asset_class,
"role": role,
"title": candidate.title,
"url": candidate.url,
"source_type": candidate.source_type,
"license_hint": candidate.license_hint,
"rationale": candidate.rationale,
"discovery": {
"provider": candidate.provider,
"query": candidate.query,
"rank": candidate.rank,
"score": candidate.score,
},
"ai_certification": candidate.ai_certification,
}
for candidate in candidates[:max_results]
]
catalog = {"schema_version": "public_source_catalog_v1", "sources": sources}
manifest = {
"schema_version": "live_source_discovery_manifest_v1",
"asset_class": asset_class,
"role": role,
"discovery_attempt": discovery_attempt,
"queries": queries,
"preferred_domains": preferred_domains,
"candidate_count": len(sources),
"catalog_path": str(output_dir / "live_discovered_public_source_catalog.json"),
"query_diagnostics": query_diagnostics,
"fallback_diagnostics": fallback_diagnostics,
"errors": search_errors,
"created_at": utc_now(),
}
write_json(output_dir / "live_discovered_public_source_catalog.json", catalog)
write_json(output_dir / "live_source_discovery_manifest.json", manifest)
return {"catalog": catalog, "manifest": manifest}

Xet Storage Details

Size:
30.8 kB
·
Xet hash:
6ef99390df31d992403179b723cfe8f586f55c38196ac523e4a65e92660365af

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.