Spaces:
Sleeping
Sleeping
File size: 5,279 Bytes
7d7841f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | from app.benchmarks import build_benchmark_comparison
from app.models import AgentReadinessSummary, AuditResult, AuditScope, CheckResult, PillarMax, PillarScores
from app.checks.locked import locked_checks
from app.presets import ResolvedScope, _resolve_legacy
_PILLAR_MAX = PillarMax(off_site=30, scrapability=40, seo=30)
# keep the API version constant in one place so the same value flows
# through scoring, models, and OpenAPI metadata. Bumping this is the contract
# signal used by agents to detect additive changes vs. breaking ones.
API_VERSION = "1.0"
_PILLAR_CHECKS: dict[str, list[str]] = {
"off_site": [
"social",
"wikipedia",
"earned_mentions_backlinks",
"owned_social_presence",
"social_traction_reviews",
"ai_citation_share",
],
"scrapability": [
"robots_txt",
"bot_access",
"html_structure",
"schema_ld",
"llms_txt",
"ssr",
"machine_surfaces",
"extraction_fidelity",
"agent_task_simulation",
],
"seo": [
"pagespeed",
"canonical",
"indexing",
"search_discovery",
"multi_engine_index_coverage",
"core_web_vitals",
"keyword_competitor_gap",
],
}
def _pillar_score(checks: list[CheckResult], pillar: str, cap: int) -> int:
total = sum(c.score for c in checks if c.pillar == pillar)
return min(total, cap)
def _scope_label(scope: ResolvedScope) -> str:
"""Compose the user-facing scope label.
Preset wins: when a preset is applied, use its display label verbatim.
Otherwise, fall back to the legacy "General website + extras" string so
older callers keep seeing familiar copy.
"""
if scope.preset:
return scope.preset_label
legacy = _resolve_legacy(
scope.include_protocols,
scope.include_account_auth,
scope.include_ecommerce,
)
return legacy.preset_label
def build_result(
url: str,
checks: list[CheckResult],
agent_readiness: AgentReadinessSummary,
scope: ResolvedScope | None = None,
include_protocols: bool = False,
include_account_auth: bool = False,
include_ecommerce: bool = False,
) -> AuditResult:
"""Assemble the final AuditResult from individual check results.
The ``scope`` argument is the preferred path. The three legacy
booleans remain supported for backwards-compatible callers (and are what
the existing tests use); when ``scope`` is None, the legacy booleans are
promoted into a synthesised :class:`ResolvedScope` so the rest of the
pipeline only has one source of truth.
"""
if scope is None:
scope = _resolve_legacy(
include_protocols, include_account_auth, include_ecommerce
)
checks = checks + locked_checks(scope.include_ecommerce)
included_optional_surfaces = [
"Core discoverability",
"Content accessibility",
"Bot access control",
"Structured data",
"Search discovery hints",
]
excluded_optional_surfaces = []
if scope.include_protocols:
included_optional_surfaces.append("API and protocol discovery")
else:
excluded_optional_surfaces.append("API and protocol discovery")
if scope.include_account_auth:
included_optional_surfaces.append("Account/auth discovery")
else:
excluded_optional_surfaces.append("Account/auth discovery")
if scope.include_ecommerce:
included_optional_surfaces.extend(["Commerce protocol metadata", "Catalog JSON"])
else:
excluded_optional_surfaces.extend(["Commerce protocol metadata", "Catalog JSON"])
audit_scope = AuditScope(
include_protocols=scope.include_protocols,
include_account_auth=scope.include_account_auth,
include_ecommerce=scope.include_ecommerce,
label=_scope_label(scope),
included_optional_surfaces=included_optional_surfaces,
excluded_optional_surfaces=excluded_optional_surfaces,
preset_applied=scope.preset,
overrides_applied=dict(scope.overrides),
included_families=list(scope.included_families),
excluded_families=list(scope.excluded_families),
machine_surfaces_scope=scope.machine_surfaces,
)
pillar_scores = PillarScores(
off_site=_pillar_score(checks, "off_site", _PILLAR_MAX.off_site),
scrapability=_pillar_score(checks, "scrapability", _PILLAR_MAX.scrapability),
seo=_pillar_score(checks, "seo", _PILLAR_MAX.seo),
)
overall = pillar_scores.off_site + pillar_scores.scrapability + pillar_scores.seo
sorted_checks = sorted(
checks,
key=lambda c: (c.max_score - c.score) / max({"low": 1, "medium": 2, "high": 3}.get(c.effort, 2), 1),
reverse=True,
)
return AuditResult(
api_version=API_VERSION,
url=url,
scope=audit_scope,
overall_score=overall,
pillar_scores=pillar_scores,
pillar_max=_PILLAR_MAX,
agent_readiness=agent_readiness,
benchmark=build_benchmark_comparison(
checks,
scope.include_protocols,
scope.include_account_auth,
scope.include_ecommerce,
),
checks=sorted_checks,
) |