From 9f3b122ffea34b7c47c08ff0ac7f355681a176a4 Mon Sep 17 00:00:00 2001 From: PrashantUnity Date: Mon, 8 Jun 2026 23:53:20 +0530 Subject: [PATCH 1/4] Lets Burn --- .../013_crawl_discovery_and_link_edges.py | 56 +++ docs/GLOSSARY.md | 5 +- docs/MCP.md | 15 +- input.txt.example | 16 + pipeline-config.example.txt | 16 + scripts/local-test.sh | 5 + src/website_profiling/analysis/page.py | 16 + .../commands/pipeline_cmd.py | 19 + src/website_profiling/common.py | 53 ++- src/website_profiling/crawl/axe_runner.py | 29 ++ src/website_profiling/crawl/crawler.py | 168 ++++++- src/website_profiling/crawl/discovery.py | 40 ++ src/website_profiling/crawl/extraction.py | 65 +++ .../crawl/fetchers/browser.py | 21 +- .../crawl/fetchers/browser_diagnostics.py | 3 + .../crawl/fetchers/factory.py | 29 ++ src/website_profiling/db/crawl_store.py | 98 +++- .../integrations/google/rich_results.py | 152 +++++++ .../integrations/keywords/competitor_csv.py | 32 ++ src/website_profiling/reporting/builder.py | 100 +++++ .../reporting/issue_impact.py | 85 ++++ .../reporting/link_edges_report.py | 37 ++ .../reporting/optional_audits.py | 286 ++++++++++++ .../tools/audit_tools/crawl.py | 6 + .../tools/audit_tools/export_extras.py | 46 ++ .../tools/audit_tools/links.py | 47 ++ .../tools/audit_tools/llm_tools.py | 8 +- .../tools/audit_tools/registry.py | 9 + .../tools/audit_tools/report.py | 6 + .../tools/audit_tools/tool_catalog.py | 16 +- .../tools/export_crawl_workbook.py | 106 +++++ src/website_profiling/tools/export_sitemap.py | 28 ++ tests/test_audit_tools_expanded.py | 2 +- tests/test_audit_tools_links_extras.py | 93 ++++ tests/test_config_schema_keys.py | 16 + tests/test_crawl_discovery.py | 79 ++++ tests/test_crawl_fetchers.py | 15 + tests/test_crawl_gap_coverage.py | 422 ++++++++++++++++++ tests/test_crawler_deep.py | 24 +- tests/test_crawler_unit.py | 6 +- tests/test_export_sitemap.py | 34 ++ tests/test_export_workbook.py | 63 +++ tests/test_extraction.py | 16 + tests/test_issue_impact.py | 30 ++ tests/test_link_edges.py | 58 +++ tests/test_mcp_registry.py | 2 +- tests/test_mcp_server_helpers.py | 4 +- tests/test_optional_audits.py | 49 ++ tests/test_reporting_gaps.py | 271 +++++++++++ tests/test_rich_results.py | 41 ++ web/app/api/filters/route.ts | 37 ++ .../api/keywords/competitor-import/route.ts | 65 +++ web/app/api/report/export-sitemap/route.ts | 60 +++ web/app/api/report/export-workbook/route.ts | 69 +++ .../compare/CompareUrlMetadataTable.tsx | 39 ++ .../content/RichResultsValidationPanel.tsx | 65 +++ .../CompetitorKeywordGapPanel.tsx | 41 ++ .../CompetitorKeywordImport.tsx | 68 +++ .../ContentTemplatesPanel.tsx | 166 +++++++ .../keywordsExplorer/KeywordTabBanner.tsx | 6 + .../keywordsExplorer/TopicMapPanel.tsx | 35 ++ .../keywordsExplorer/keywordTabMeta.ts | 5 +- web/src/components/links/InspectorTabs.tsx | 9 +- .../components/links/LinkAttributesPanel.tsx | 73 +++ web/src/components/links/PageImprovePanel.tsx | 116 +++++ .../components/links/SavedCrawlFiltersBar.tsx | 96 ++++ web/src/components/links/SerpPreview.tsx | 31 ++ .../links/explorer/LinksExplorerTableTab.tsx | 34 ++ .../components/links/tabs/SeoSocialTab.tsx | 5 + .../components/overview/OverviewHealthTab.tsx | 2 + .../overview/PortfolioBenchmarkCard.tsx | 54 +++ .../siteStructure/CrawlMapPanel.tsx | 67 +++ web/src/lib/customFields.test.ts | 18 + web/src/lib/customFields.ts | 28 ++ web/src/lib/exportAudit.ts | 14 + web/src/lib/pipelineConfigSchema.ts | 92 ++++ web/src/lib/reportCompare.ts | 39 ++ web/src/server/exportSitemapRoute.test.ts | 39 ++ web/src/server/filtersRoute.test.ts | 50 +++ .../keywordsCompetitorImportRoute.test.ts | 56 +++ web/src/server/savedFiltersDb.ts | 56 +++ web/src/strings.json | 42 +- web/src/types/index.ts | 3 + web/src/types/report.ts | 62 +++ web/src/utils/linkExport.ts | 13 +- web/src/views/CompareReports.tsx | 28 +- web/src/views/ContentAnalytics.tsx | 9 + web/src/views/ExportReport.tsx | 20 +- web/src/views/Issues.tsx | 7 +- web/src/views/KeywordsExplorer.tsx | 33 +- web/src/views/Links.tsx | 32 ++ web/src/views/SiteStructure.tsx | 15 +- 92 files changed, 4565 insertions(+), 77 deletions(-) create mode 100644 alembic/versions/013_crawl_discovery_and_link_edges.py create mode 100644 src/website_profiling/crawl/axe_runner.py create mode 100644 src/website_profiling/crawl/discovery.py create mode 100644 src/website_profiling/crawl/extraction.py create mode 100644 src/website_profiling/integrations/google/rich_results.py create mode 100644 src/website_profiling/integrations/keywords/competitor_csv.py create mode 100644 src/website_profiling/reporting/issue_impact.py create mode 100644 src/website_profiling/reporting/link_edges_report.py create mode 100644 src/website_profiling/reporting/optional_audits.py create mode 100644 src/website_profiling/tools/audit_tools/export_extras.py create mode 100644 src/website_profiling/tools/export_crawl_workbook.py create mode 100644 src/website_profiling/tools/export_sitemap.py create mode 100644 tests/test_audit_tools_links_extras.py create mode 100644 tests/test_crawl_discovery.py create mode 100644 tests/test_crawl_gap_coverage.py create mode 100644 tests/test_export_sitemap.py create mode 100644 tests/test_export_workbook.py create mode 100644 tests/test_extraction.py create mode 100644 tests/test_issue_impact.py create mode 100644 tests/test_link_edges.py create mode 100644 tests/test_optional_audits.py create mode 100644 tests/test_reporting_gaps.py create mode 100644 tests/test_rich_results.py create mode 100644 web/app/api/filters/route.ts create mode 100644 web/app/api/keywords/competitor-import/route.ts create mode 100644 web/app/api/report/export-sitemap/route.ts create mode 100644 web/app/api/report/export-workbook/route.ts create mode 100644 web/src/components/compare/CompareUrlMetadataTable.tsx create mode 100644 web/src/components/content/RichResultsValidationPanel.tsx create mode 100644 web/src/components/keywordsExplorer/CompetitorKeywordGapPanel.tsx create mode 100644 web/src/components/keywordsExplorer/CompetitorKeywordImport.tsx create mode 100644 web/src/components/keywordsExplorer/ContentTemplatesPanel.tsx create mode 100644 web/src/components/keywordsExplorer/TopicMapPanel.tsx create mode 100644 web/src/components/links/LinkAttributesPanel.tsx create mode 100644 web/src/components/links/PageImprovePanel.tsx create mode 100644 web/src/components/links/SavedCrawlFiltersBar.tsx create mode 100644 web/src/components/links/SerpPreview.tsx create mode 100644 web/src/components/overview/PortfolioBenchmarkCard.tsx create mode 100644 web/src/components/siteStructure/CrawlMapPanel.tsx create mode 100644 web/src/lib/customFields.test.ts create mode 100644 web/src/lib/customFields.ts create mode 100644 web/src/server/exportSitemapRoute.test.ts create mode 100644 web/src/server/filtersRoute.test.ts create mode 100644 web/src/server/keywordsCompetitorImportRoute.test.ts create mode 100644 web/src/server/savedFiltersDb.ts diff --git a/alembic/versions/013_crawl_discovery_and_link_edges.py b/alembic/versions/013_crawl_discovery_and_link_edges.py new file mode 100644 index 00000000..a666036c --- /dev/null +++ b/alembic/versions/013_crawl_discovery_and_link_edges.py @@ -0,0 +1,56 @@ +"""Crawl discovery mode on crawl_runs and rich link_edges table. + +Revision ID: 013_crawl_discovery_edges +Revises: 012_chat_sessions +""" +from __future__ import annotations + +from alembic import op + +revision = "013_crawl_discovery_edges" +down_revision = "012_chat_sessions" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.execute(""" + ALTER TABLE crawl_runs + ADD COLUMN IF NOT EXISTS discovery_mode TEXT DEFAULT 'spider'; + + CREATE TABLE IF NOT EXISTS link_edges ( + crawl_run_id BIGINT NOT NULL REFERENCES crawl_runs(id) ON DELETE CASCADE, + from_url TEXT NOT NULL, + to_url TEXT NOT NULL, + anchor_text TEXT NOT NULL DEFAULT '', + rel TEXT NOT NULL DEFAULT '', + is_nofollow BOOLEAN NOT NULL DEFAULT FALSE, + is_sponsored BOOLEAN NOT NULL DEFAULT FALSE, + is_ugc BOOLEAN NOT NULL DEFAULT FALSE, + link_type TEXT NOT NULL DEFAULT 'internal', + PRIMARY KEY (crawl_run_id, from_url, to_url, anchor_text, rel) + ); + CREATE INDEX IF NOT EXISTS idx_link_edges_run_from + ON link_edges(crawl_run_id, from_url); + CREATE INDEX IF NOT EXISTS idx_link_edges_run_to + ON link_edges(crawl_run_id, to_url); + + CREATE TABLE IF NOT EXISTS saved_crawl_filters ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + property_id BIGINT NOT NULL REFERENCES properties(id) ON DELETE CASCADE, + name TEXT NOT NULL, + filter_json JSONB NOT NULL DEFAULT '{}', + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + UNIQUE (property_id, name) + ); + CREATE INDEX IF NOT EXISTS idx_saved_crawl_filters_property + ON saved_crawl_filters(property_id); + """) + + +def downgrade() -> None: + op.execute(""" + DROP TABLE IF EXISTS saved_crawl_filters CASCADE; + DROP TABLE IF EXISTS link_edges CASCADE; + ALTER TABLE crawl_runs DROP COLUMN IF EXISTS discovery_mode; + """) diff --git a/docs/GLOSSARY.md b/docs/GLOSSARY.md index bc8c9ca0..796760b8 100644 --- a/docs/GLOSSARY.md +++ b/docs/GLOSSARY.md @@ -49,7 +49,8 @@ UI terms agencies recognize, mapped to internal keys and data sources. | UI | Field | Source | |----|-------|--------| -| Inlinks | `inlinks` | Crawl graph | +| Impact score | `impact_score` on issues | GSC clicks + GA4 sessions + priority weight (see below) | +| Link edges | `link_edges`, `link_rel_summary` | Crawl anchor/rel attributes | | Outlinks | `outlinks` | Crawl graph | | Status code | `status` | HTTP | | Crawl rendering | `crawl_render_mode` on run; `fetch_method` per URL | `static`, `javascript`, or `auto` crawl config; `static` vs `rendered` per page | @@ -62,6 +63,8 @@ UI terms agencies recognize, mapped to internal keys and data sources. | On-site frequency | `volume` (heuristic) | Estimated from crawl | | Sessions | GA4 metrics | Analytics | +**Impact score:** `priority_weight + (gsc_clicks × 10) + (ga4_sessions × 5)` with Critical=1000, High=100, Medium=10, Low=1. + ## Provenance badges | Badge | Meaning | diff --git a/docs/MCP.md b/docs/MCP.md index dd473f8e..d3ba6f94 100644 --- a/docs/MCP.md +++ b/docs/MCP.md @@ -43,11 +43,11 @@ Add to `.cursor/mcp.json` (or Cursor MCP settings): | `audit://glossary` | Excerpt from `docs/GLOSSARY.md` | | `audit://tools` | Tool catalog grouped by SEO domain | -## Tools (171 read-only + export) +## Tools (176 read-only + export) ### Export and deliverables -`export_audit_report`, `export_compare_csv`, `export_list_as_csv`, `compose_custom_report`, `export_custom_report`, `list_export_formats` +`export_audit_report`, `export_compare_csv`, `export_list_as_csv`, `export_sitemap_xml`, `validate_rich_results`, `compose_custom_report`, `export_custom_report`, `list_export_formats` Full audit exports reuse the same generators as the Export view (PDF requires `reportlab`). Export tools store files as artifacts (24h TTL); in-app chat renders download buttons via `/api/chat/artifacts/{id}`. @@ -79,7 +79,7 @@ Size-based tools require `probe_image_inventory=true` in pipeline config when bu ### Links and architecture -`list_orphan_pages`, `get_top_linked_pages`, `get_top_crawled_pages`, `get_outbound_link_domains`, `get_link_graph_summary`, `get_url_fingerprints`, `list_broken_link_sources`, `get_mime_type_breakdown`, `get_title_length_distribution`, `get_domain_link_distribution`, `get_outlink_distribution` +`get_link_rel_summary`, `get_inlink_anchors`, `list_nofollow_internal_links`, `list_orphan_pages`, `get_top_linked_pages`, `get_top_crawled_pages`, `get_outbound_link_domains`, `get_link_graph_summary`, `get_url_fingerprints`, `list_broken_link_sources`, `get_mime_type_breakdown`, `get_title_length_distribution`, `get_domain_link_distribution`, `get_outlink_distribution` ### Indexation and international @@ -115,12 +115,13 @@ Size-based tools require `probe_image_inventory=true` in pipeline config when bu ## Future pipeline items (not yet exposed as tools) -These require additional crawl or third-party integrations before dedicated tools are useful: +These require additional third-party integrations or product scope beyond current crawl data: -- Google Rich Results / schema validation API -- Full backlink index and anchor-text analytics -- axe / color-contrast accessibility audits +- Full backlink index and anchor-text analytics (beyond GSC Links import) - SERP rank tracking beyond GSC position snapshots +- Standalone Google Rich Results Test API (current `validate_rich_results` uses crawl heuristics + GSC URL Inspection) + +Already available: `validate_rich_results`, `export_sitemap_xml`, workbook export, axe audits via `enable_axe` on browser crawls. ## Example prompts diff --git a/input.txt.example b/input.txt.example index 4d1b4d0e..c1c72d0e 100644 --- a/input.txt.example +++ b/input.txt.example @@ -18,6 +18,16 @@ content_excerpt_max_chars = 4096 preserve_crawl_history = true crawl_stream_to_db = false crawl_exclude_urls = +crawl_discovery_mode = spider +crawl_url_list = +crawl_user_agent_preset = default +crawl_user_agent_custom = +crawl_auth_username = +crawl_auth_password = +crawl_extra_headers = +crawl_cookies = +crawl_robots_txt_override = +custom_extractors = # crawl_render_mode: static | javascript | auto (auto = static first, browser when SPA heuristics match) crawl_render_mode = static crawl_js_concurrency = 3 @@ -56,6 +66,12 @@ lighthouse_iterations = 1 run_lighthouse = true run_lighthouse_on_pages = true enable_crux = false +enable_rich_results_validation = false +enable_axe = false +enable_spell_check = false +enable_html_validation = false +enable_amp_audit = false +enable_wayback_lookup = false competitor_domains = bing_webmaster_api_key = serp_api_key = diff --git a/pipeline-config.example.txt b/pipeline-config.example.txt index b1c384b3..d9cc39ae 100644 --- a/pipeline-config.example.txt +++ b/pipeline-config.example.txt @@ -19,6 +19,16 @@ content_excerpt_max_chars = 4096 preserve_crawl_history = true crawl_stream_to_db = false crawl_exclude_urls = +crawl_discovery_mode = spider +crawl_url_list = +crawl_user_agent_preset = default +crawl_user_agent_custom = +crawl_auth_username = +crawl_auth_password = +crawl_extra_headers = +crawl_cookies = +crawl_robots_txt_override = +custom_extractors = # crawl_render_mode: static | javascript | auto crawl_render_mode = static crawl_js_concurrency = 3 @@ -57,6 +67,12 @@ lighthouse_iterations = 1 run_lighthouse = true run_lighthouse_on_pages = true enable_crux = false +enable_rich_results_validation = false +enable_axe = false +enable_spell_check = false +enable_html_validation = false +enable_amp_audit = false +enable_wayback_lookup = false competitor_domains = bing_webmaster_api_key = serp_api_key = diff --git a/scripts/local-test.sh b/scripts/local-test.sh index 960683fb..f2f7f3b0 100755 --- a/scripts/local-test.sh +++ b/scripts/local-test.sh @@ -123,6 +123,8 @@ run_pytest_reporting() { tests/test_crawl_segments.py \ tests/test_terminology.py \ tests/test_compare_payload.py \ + tests/test_optional_audits.py \ + tests/test_reporting_gaps.py \ --cov=website_profiling.reporting \ --cov-config=.coveragerc.reporting \ --cov-report=term-missing \ @@ -143,6 +145,7 @@ run_pytest_tools() { tests/test_audit_tools_expanded.py \ tests/test_audit_tools_coverage.py \ tests/test_audit_tools_dispatch_coverage.py \ + tests/test_audit_tools_links_extras.py \ tests/test_export_custom_coverage.py \ tests/test_export_artifacts_coverage.py \ tests/test_export_compare_coverage.py \ @@ -151,6 +154,8 @@ run_pytest_tools() { tests/test_export_custom.py \ tests/test_export_artifacts.py \ tests/test_export_compare.py \ + tests/test_export_workbook.py \ + tests/test_export_sitemap.py \ tests/test_mcp_registry.py \ tests/test_mcp_resources.py \ --cov=website_profiling.tools \ diff --git a/src/website_profiling/analysis/page.py b/src/website_profiling/analysis/page.py index 6ae3612e..35ba87ad 100644 --- a/src/website_profiling/analysis/page.py +++ b/src/website_profiling/analysis/page.py @@ -363,5 +363,21 @@ def _script_in_head(sc) -> bool: "Use