Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
152 commits
Select commit Hold shift + click to select a range
aecb061
chore(git): ignore generated web artifacts
reiganarmstrong Sep 19, 2026
74e9b0d
chore(agents): configure repository workflows
reiganarmstrong Sep 19, 2026
21d5734
docs(evaluation): record domain and architecture decisions
reiganarmstrong Sep 19, 2026
fe861a6
docs(evaluation): add model evaluation specification
reiganarmstrong Sep 19, 2026
9cc5b95
docs(evaluation): add implementation ticket graph
reiganarmstrong Sep 19, 2026
c371979
feat(audit): add explicit request configuration
reiganarmstrong Sep 19, 2026
c395efa
style(audit): address review findings
reiganarmstrong Sep 19, 2026
aa77ee9
feat(evaluation): bootstrap private CLI workspace
reiganarmstrong Sep 19, 2026
b99017f
fix(evaluation): enforce private workspace safety
reiganarmstrong Sep 19, 2026
e9485bf
fix(evaluation): anchor private paths to worktree
reiganarmstrong Sep 19, 2026
e3de4fa
docs(agents): define ticket resolution convention
reiganarmstrong Sep 19, 2026
0bd719c
feat(evaluation): freeze immutable benchmark snapshot
reiganarmstrong Sep 19, 2026
fc630cb
fix(evaluation): harden snapshot publication
reiganarmstrong Sep 19, 2026
e4f9a55
docs(evaluation): resolve benchmark snapshot ticket
reiganarmstrong Sep 19, 2026
472500c
feat(evaluation): complete guarded model evaluation workflow
reiganarmstrong Sep 19, 2026
8bfe6af
fix(evaluation): enforce execution and review invariants
reiganarmstrong Sep 19, 2026
1cb479a
style(evaluation): wrap CLI fixture metadata
reiganarmstrong Sep 19, 2026
2129616
fix(evaluation): bind reports to verified run inputs
reiganarmstrong Sep 19, 2026
68d34d9
docs(evaluation): resolve framework implementation tickets
reiganarmstrong Sep 19, 2026
2448042
feat(evaluation): streamline run-oriented CLI workflow
reiganarmstrong Sep 19, 2026
6a1e6c0
fix(evaluation): bind execution to planned run destination
reiganarmstrong Sep 19, 2026
442c4cb
docs(evaluation): resolve run-oriented CLI workflow
reiganarmstrong Sep 19, 2026
210044c
fix(evaluation): map mandated workbook headers
reiganarmstrong Sep 20, 2026
0b9c3d2
refactor(evaluation): retire legacy audit runner
reiganarmstrong Sep 20, 2026
6ac3311
docs(evaluation): add implementation progress report
reiganarmstrong Sep 20, 2026
617a31f
feat(evaluation): removed temporal assumption metadata from evaluation
reiganarmstrong Sep 20, 2026
96c5a4f
feat(evaluation): retarget frozen audit to Haiku extended thinking
reiganarmstrong Sep 22, 2026
ffec10d
fix(evaluation): retain reported usage from interrupted streams
reiganarmstrong Sep 22, 2026
3e6e9d2
docs(evaluation): resolve Anthropic retarget spec after verification
reiganarmstrong Sep 22, 2026
7b99d16
refactor(evaluation): reduce homepage POC to run and report
reiganarmstrong Sep 23, 2026
b32eea1
fix(evaluation): close review gaps in guarded runs
reiganarmstrong Sep 23, 2026
5614390
docs(evaluation): resolve POC reduction spec
reiganarmstrong Sep 23, 2026
9285927
docs(evaluation): explain final report walkthrough
reiganarmstrong Sep 24, 2026
bf32ad9
docs(evaluation): make walkthrough work after workspace deletion
reiganarmstrong Sep 24, 2026
577a804
docs(evaluation): show run command without live execution
reiganarmstrong Sep 24, 2026
9d63037
refactor(evaluation)!: name raw and normalized findings explicitly
reiganarmstrong Sep 24, 2026
2fb478c
docs(evaluation): retain single-page limitation after rename
reiganarmstrong Sep 24, 2026
aad5204
docs(evaluation): make filling in review.csv more clear
reiganarmstrong Sep 24, 2026
28c8a86
feat(skills): add link to matt pocock skills for claude
reiganarmstrong Sep 27, 2026
9c014de
docs(evaluation): explain homepage run JSON files
reiganarmstrong Sep 27, 2026
d4a87cf
refactor(evaluation)!: name raw and normalized audit files explicitly
reiganarmstrong Sep 27, 2026
14c07a7
docs(scratch): resolve rename-audit-artifacts spec
reiganarmstrong Sep 27, 2026
25cd709
refactor(evaluation): rename pricing.v2.json to pricing.json
reiganarmstrong Sep 27, 2026
30ca95f
fix(evaluation): render original defect evidence verbatim in report.md
reiganarmstrong Sep 27, 2026
12c17ab
docs(evaluation): record plain-language report spec and tickets
reiganarmstrong Sep 29, 2026
401822c
refactor(evaluation)!: adopt Human audit and LLM vocabulary
reiganarmstrong Sep 29, 2026
61ac26c
chore(tickets): resolve Human audit vocabulary ticket
reiganarmstrong Sep 29, 2026
be15c06
feat(evaluation)!: explain catches and misses in plain language
reiganarmstrong Sep 29, 2026
bffab02
chore(tickets): resolve plain-language report ticket
reiganarmstrong Sep 29, 2026
e77b2c0
chore(evaluation): record reviewed live verification
reiganarmstrong Sep 29, 2026
2fb259a
chore(tracker): resolve live verification ticket
reiganarmstrong Sep 29, 2026
b8e3a36
fix(evaluation): clarify report checks and format explanation bullets
reiganarmstrong Oct 1, 2026
1bc12d9
feat(evaluation)!: organize supporting run artifacts
reiganarmstrong Oct 1, 2026
49b50c5
docs(tracker): resolve run directory layout spec
reiganarmstrong Oct 1, 2026
bd1d9f6
feat(evaluation): select Haiku, Opus, or Sonnet presets
reiganarmstrong Oct 1, 2026
896cceb
fix(evaluation): explain presets and cost limit in CLI help
reiganarmstrong Oct 1, 2026
5994cda
docs(evaluation): resolve model selection spec after review
reiganarmstrong Oct 1, 2026
f9e2186
fix(evaluation): use medium effort for Opus and Sonnet
reiganarmstrong Oct 1, 2026
d61ed55
docs(evaluation): record medium effort verification
reiganarmstrong Oct 1, 2026
605a694
tests: consolidate evaluator suite and make scratch tracker local
reiganarmstrong Oct 1, 2026
ce345d1
tests: cover declined approval and excluded-row claims after review
reiganarmstrong Oct 1, 2026
b93586d
fix(evaluation): parse LLM responses exactly as the API does
reiganarmstrong Oct 1, 2026
ab42455
fix(evaluation): catch exactly the parse errors the API skips
reiganarmstrong Oct 1, 2026
77c791b
fix(evaluation): count LLM findings with the tool's per-check rules
reiganarmstrong Oct 1, 2026
8a59f67
fix(evaluation): keep table evidence intact and reject text issue lists
reiganarmstrong Oct 1, 2026
eabee40
docs(readme): add review prompt and move evaluator section to end
reiganarmstrong Oct 1, 2026
b219345
docs(readme): clarify evaluator response handling
reiganarmstrong Oct 3, 2026
b83a09b
chore(readme): wording and formatting
reiganarmstrong Oct 3, 2026
9789af8
fix(llm-client): honor explicit OpenAI retry policy
reiganarmstrong Oct 9, 2026
9689932
feat(llm-client): integrate explicit OpenAI retry policy
reiganarmstrong Oct 9, 2026
4a9065d
feat(evaluation): add GPT and Haiku model presets
reiganarmstrong Oct 9, 2026
f396b45
feat(evaluation): integrate GPT and Haiku model presets
reiganarmstrong Oct 9, 2026
fa79408
feat(evaluation): estimate costs per request and context tier
reiganarmstrong Oct 9, 2026
b33251c
feat(evaluation): integrate request and context tier pricing
reiganarmstrong Oct 9, 2026
8fd8e3f
refactor(evaluation): centralize OpenAI preset membership
reiganarmstrong Oct 9, 2026
5c03c52
refactor(evaluation): integrate model support review fixes
reiganarmstrong Oct 9, 2026
a9c73b9
feat(evaluation): default to Haiku 5.5 and remove Haiku 4.5
reiganarmstrong Oct 10, 2026
9836f03
docs(evaluation): record full evaluation domain and scoring rules
reiganarmstrong Oct 10, 2026
ddd3b1d
refactor(evaluation): share page preparation and reviewed reports
reiganarmstrong Oct 10, 2026
d1deb2d
feat(evaluation): capture listed Benchmark snapshots
reiganarmstrong Oct 10, 2026
7d7da8d
Merge ticket 01: shared human-audit page evaluation
reiganarmstrong Oct 10, 2026
8f7948a
Merge ticket 02: persistent full-evaluation snapshot capture
reiganarmstrong Oct 10, 2026
c870fc3
feat(evaluation): preview all Human audit pages from saved snapshots
reiganarmstrong Oct 10, 2026
54c7022
Merge ticket 03: deterministic full-evaluation preview
reiganarmstrong Oct 10, 2026
e8429a5
feat(evaluation): execute all pages with one cost allowance
reiganarmstrong Oct 10, 2026
9bbd095
Merge ticket 04: approved execution with shared cost allowance
reiganarmstrong Oct 10, 2026
49fdc4b
test(evaluation): isolate missing provider credentials from dotenv
reiganarmstrong Oct 10, 2026
3de396a
Merge credential-test isolation from local dotenv configuration
reiganarmstrong Oct 10, 2026
3c3000e
feat(evaluation): report combined detection for every listed page
reiganarmstrong Oct 10, 2026
a180a18
Merge ticket 05: combined detection reporting for all evaluated pages
reiganarmstrong Oct 11, 2026
51378c5
refactor(evaluation): address full evaluation review findings
reiganarmstrong Oct 11, 2026
e298869
Merge final full-evaluation review fixes
reiganarmstrong Oct 11, 2026
3e262ac
chore(evaluator): add Ruff and fix lint issues
reiganarmstrong Oct 15, 2026
83af377
feat(evaluator): generate standalone single-page HTML reports
reiganarmstrong Oct 15, 2026
d163391
docs(evaluator): record installed axe-core version
reiganarmstrong Oct 15, 2026
f04897d
Merge single-page HTML evaluation reports
reiganarmstrong Oct 15, 2026
b0204c4
feat(evaluator): generate standalone Full HTML reports
reiganarmstrong Oct 15, 2026
43a672a
docs(evaluator): record complete Full report evidence check
reiganarmstrong Oct 15, 2026
6a14c8c
Merge self-contained Full evaluation HTML reports
reiganarmstrong Oct 15, 2026
5305e2c
feat(evaluator): visualize detection rates in offline reports
reiganarmstrong Oct 15, 2026
1e54ad0
Merge detection performance charts
reiganarmstrong Oct 15, 2026
c95b1f9
feat(evaluator): search and filter embedded matching evidence
reiganarmstrong Oct 15, 2026
7300deb
Merge branch 'feat/html-evaluation-reports' into feat/html-report-04
reiganarmstrong Oct 15, 2026
1b09dc6
docs(evaluator): record offline filtering verification
reiganarmstrong Oct 15, 2026
36d7ec7
Merge report evidence search and filters
reiganarmstrong Oct 15, 2026
7bb039b
feat(evaluator): print complete offline evaluation reports
reiganarmstrong Oct 15, 2026
deeedf0
Merge complete report printing
reiganarmstrong Oct 15, 2026
37db71d
refactor(evaluator): share HTML report document layout
reiganarmstrong Oct 15, 2026
f28dea1
Merge shared report layout review fix
reiganarmstrong Oct 15, 2026
c66778e
refactor(evaluator): suffix report templates with .jinja
reiganarmstrong Oct 15, 2026
d5c2900
refactor(evaluator): separate static report assets from templates
reiganarmstrong Oct 15, 2026
d3b7b6a
docs: define self-contained evaluation reports
reiganarmstrong Oct 15, 2026
7ae314e
feat(reports): clarify findings and highlight cost and detection
reiganarmstrong Oct 15, 2026
b77bd94
feat(reports): simplified wording for llm detection rates for each pa…
reiganarmstrong Oct 15, 2026
7de54ab
feat(reports): simplify filters and explain overall counts
reiganarmstrong Oct 15, 2026
c5ece05
feat(reports): connect findings filters and table across screen sizes
reiganarmstrong Oct 15, 2026
53269d2
feat(reports): improve accessibility and simplify report output
reiganarmstrong Oct 15, 2026
eb5aca1
fix(reports): hide unmatched evidence fields and redundant text
reiganarmstrong Oct 16, 2026
62fc97c
chore(lint): fix Ruff findings across the repository
reiganarmstrong Oct 16, 2026
a9d650d
refactor(evaluation): group supporting modules by responsibility
reiganarmstrong Oct 16, 2026
ba420df
fix(evaluation): type homepage scope set for literal narrowing
reiganarmstrong Oct 16, 2026
af175b7
refactor: remove unused helpers, metadata, and arguments
reiganarmstrong Oct 16, 2026
584f5a1
docs(evaluation): record shared Human audit decisions
reiganarmstrong Oct 22, 2026
5a229ad
feat(evaluation): enforce approved single-page audit decisions
reiganarmstrong Oct 22, 2026
b7104f8
Merge branch 'feat/evaluation-baseline' into feat/baseline-single-page
reiganarmstrong Oct 22, 2026
0cfcf28
Merge ticket 02 baseline single-page workflow
reiganarmstrong Oct 22, 2026
982e38d
feat(evaluation): approve decisions for all original Human findings
reiganarmstrong Oct 22, 2026
8a07be4
Merge ticket 04 approved Human audit decisions
reiganarmstrong Oct 22, 2026
6ee14c9
feat(evaluation): enforce shared decisions across full runs
reiganarmstrong Oct 22, 2026
03719da
Merge branch 'feat/evaluation-baseline' into feat/baseline-full-evalu…
reiganarmstrong Oct 22, 2026
0104744
Merge ticket 03 shared-baseline full evaluation
reiganarmstrong Oct 22, 2026
742fe57
docs(evaluation): explain corrected saved-run detection counts
reiganarmstrong Oct 22, 2026
75b8652
feat(evaluation): correct saved full runs with verified backups
reiganarmstrong Oct 22, 2026
18b0cdd
Merge branch 'feat/evaluation-baseline' into feat/baseline-saved-corr…
reiganarmstrong Oct 22, 2026
7b46464
Merge ticket 05 saved evaluation correction
reiganarmstrong Oct 22, 2026
433c385
refactor(evaluation): share saved baseline integrity checks
reiganarmstrong Oct 22, 2026
7480840
Merge final evaluation baseline review fixes
reiganarmstrong Oct 22, 2026
b6fca0c
feat(evaluation): author required baselines through CSV review
reiganarmstrong Oct 22, 2026
9d722b8
chore(evaluation): keep approved baseline data local
reiganarmstrong Oct 22, 2026
3ce5a20
docs: remove evaluation records and untrack skill support docs
reiganarmstrong Oct 24, 2026
bb162cc
test(evaluation): prune suite to core regression contracts
reiganarmstrong Oct 24, 2026
0579243
refactor(evaluation): rename baseline to human finding mapping
reiganarmstrong Oct 24, 2026
1fba5d8
chore(gitignore): clean up uneeded documents from branch
reiganarmstrong Oct 24, 2026
31fe256
fix(llm): restore prompt element type metadata
reiganarmstrong Oct 24, 2026
97dc8e5
fix(api): restore existing exception handling
reiganarmstrong Oct 24, 2026
182517a
chore: ignore local CONTEXT.md files
reiganarmstrong Oct 24, 2026
6645f8e
refactor(evaluation): rename baseline modules to human finding mapping
reiganarmstrong Oct 24, 2026
b4c99e8
refactor(evaluation)!: remove baseline compatibility and legacy tests
reiganarmstrong Oct 24, 2026
2eca2ec
feat(evaluation): simplify generated review directories
reiganarmstrong Oct 25, 2026
829bd9d
docs(evaluation): streamline setup and clarify audit terminology
reiganarmstrong Oct 25, 2026
1407162
chore(readme): fix wording
reiganarmstrong Oct 25, 2026
8b17668
chore(evaluation): remove compatibility entry point and fix wording
reiganarmstrong Oct 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ permissions:

jobs:
checks:
name: Dependencies, imports, pipeline
name: Dependencies, lint, imports, pipeline
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
Expand Down Expand Up @@ -53,6 +53,12 @@ jobs:
print('all entry points import cleanly')
"

- name: Evaluator lint
run: uv run ruff check vision_aid/evaluation tests

- name: Network-free test suite
run: uv run pytest -q

# Exercises extractors, slicers, prompt templates and the programmatic
# checkers end to end. --dry-run makes no API calls, so this needs no key
# and costs nothing.
Expand Down
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -212,5 +212,27 @@ output/
# CI smoke-test output
ci-output/

# Private model-evaluation inputs and derived artifacts
/Pristine Accessibility Defect Report.xlsx
/.model-evaluation/
/vision_aid/evaluation/human_finding_mappings/

# Node.js / Next.js generated files
node_modules/
.next/
next-env.d.ts
*.tsbuildinfo
web/prisma/generated/

# Stray git worktrees — never belong in the index
.claude/worktrees/

# Local-only issue tracker and detailed evaluator tests
/.scratch/
/.local-tests/

# Local architecture decision records
/docs/adr/

# Local agent context
CONTEXT.md
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"python.terminal.useEnvFile": true
}
13 changes: 13 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Agent skills

### Issue tracker

Issues are tracked as local Markdown files under `.scratch/`. See `docs/agents/issue-tracker.md`.

### Triage labels

The default five-role triage vocabulary is used. See `docs/agents/triage-labels.md`.

### Domain docs

This repository uses a single-context domain-doc layout. See `docs/agents/domain.md`.
15 changes: 15 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
This project builds LLM-powered tools that analyze websites for WCAG accessibility issues and generate structured remediation reports. It is a Computing for Good course project at Georgia Tech (OMSCS) partnered with the Vision Aid Digital Accessibility Testing Team.

The system has three planned stages:

1. Automated webpage accessibility analysis → structured CSV reports
2. Code remediation suggestions based on those reports
3. (Stretch) Chrome extension for on-the-fly accessibility fixes
Expand Down Expand Up @@ -116,3 +117,17 @@ Docker image → GHCR (`ghcr.io/c4g/va-dat`) → Coolify at `https://va-dat.c4g.
- HTML test files can be enormous (500K+ tokens) — do not read them fully into context.
- Some files may have non-UTF-8 encoding; always use `errors='replace'`.
- Model defaults differ by entry point: `run_pipeline.py` uses `claude-sonnet-5`, `api_server.py` uses `claude-haiku-4-5-20251001`.

## Agent skills

### Issue tracker

Issues are tracked as local Markdown files under `.scratch/`. See `docs/agents/issue-tracker.md`.

### Triage labels

The default five-role triage vocabulary is used. See `docs/agents/triage-labels.md`.

### Domain docs

This repository uses a single-context domain-doc layout. See `docs/agents/domain.md`.
274 changes: 223 additions & 51 deletions README.md

Large diffs are not rendered by default.

32 changes: 20 additions & 12 deletions entry_points/api_server.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"""

import json
import logging
import os
import re
import shutil
Expand All @@ -33,11 +34,12 @@
PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))

from entry_points.run_pipeline import run_pipeline # noqa: E402
from entry_points.generate_report import generate_report # noqa: E402
from vision_aid.ingestion.file_crawler import fetch_page, fetch_pages_nested # noqa: E402
from processing_scripts.llm_client.client import is_openai_model, is_gemini_model # noqa: E402
from entry_points.generate_report import generate_report
from entry_points.run_pipeline import run_pipeline
from processing_scripts.llm_client.client import is_gemini_model, is_openai_model
from vision_aid.ingestion.file_crawler import fetch_page, fetch_pages_nested

logger = logging.getLogger(__name__)

# ── Multi-page splitting ─────────────────────────────────────────────────────

Expand Down Expand Up @@ -150,9 +152,14 @@ def run_audit(html_content: str, api_key: str, model: str, progress_callback=Non
"wcag_criteria": data.get("wcag_criteria", []),
"status": status,
"error": (api_result or {}).get("error"),
"failure": (api_result or {}).get("failure"),
"parsed": parsed,
"provider": (api_result or {}).get("provider"),
"request": (api_result or {}).get("request"),
"usage": usage,
"input_tokens": usage.get("input_tokens"),
"output_tokens": usage.get("output_tokens"),
"stop_reason": (api_result or {}).get("stop_reason"),
"duration_seconds": (api_result or {}).get("duration_seconds"),
}

Expand All @@ -177,8 +184,8 @@ def run_audit(html_content: str, api_key: str, model: str, progress_callback=Non
"stage": "report_complete",
"message": "Report ready",
})
except Exception as csv_err:
print(f" Warning: CSV generation failed: {csv_err}")
except Exception:
logger.exception("CSV generation failed")

return {
"success": True,
Expand All @@ -202,6 +209,7 @@ def run_audit(html_content: str, api_key: str, model: str, progress_callback=Non
}

except Exception as exc:
logger.exception("Audit failed")
return {"success": False, "error": str(exc)}

finally:
Expand All @@ -223,7 +231,7 @@ def _try_parse_json(text: str):
class AuditHandler(BaseHTTPRequestHandler):
"""Handle static file serving and the /api/audit endpoint."""

def log_message(self, fmt, *args): # noqa: N802
def log_message(self, fmt, *args):
sys.stderr.write(f"[{self.address_string()}] {fmt % args}\n")

# CORS helpers ─────────────────────────────────────────────────────────────
Expand All @@ -233,15 +241,15 @@ def _cors_headers(self):
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")

def do_OPTIONS(self): # noqa: N802
def do_OPTIONS(self):
"""Handle CORS preflight requests."""
self.send_response(200)
self._cors_headers()
self.end_headers()

# GET ──────────────────────────────────────────────────────────────────────

def do_GET(self): # noqa: N802
def do_GET(self):
path = self.path.split("?")[0]
if path in ("/", "/index.html"):
self._serve_file(STATIC_DIR / "index.html", "text/html; charset=utf-8")
Expand All @@ -264,7 +272,7 @@ def _serve_file(self, file_path: Path, content_type: str):

# POST ─────────────────────────────────────────────────────────────────────

def do_POST(self): # noqa: N802
def do_POST(self):
if self.path == "/api/audit":
self._handle_audit()
elif self.path == "/api/audit/url":
Expand Down Expand Up @@ -520,8 +528,8 @@ def _handle_url_audit(self, nested: bool):

def _handle_validate_key(self):
"""Validate an API key with a lightweight call to the provider."""
import urllib.request
import urllib.error
import urllib.request

length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(length)
Expand Down Expand Up @@ -599,7 +607,7 @@ def main():
parser.add_argument(
"--port",
type=int,
default=int(os.getenv("PORT", 8000)),
default=int(os.getenv("PORT", "8000")),
help="Port to listen on (default: $PORT or 8000)",
)
parser.add_argument(
Expand Down
46 changes: 24 additions & 22 deletions entry_points/generate_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
import argparse
import csv
import json
import logging
import re
from dataclasses import dataclass, fields
from pathlib import Path

from bs4 import BeautifulSoup

logger = logging.getLogger(__name__)

# ── Deduplication prompt ─────────────────────────────────────────────────────

_DEDUP_PROMPT = """\
Expand Down Expand Up @@ -280,10 +284,7 @@ def _is_link_fp(row: ReportRow, nav_link_texts: set[str]) -> bool:
return True

# Links inside <nav> or <footer> are clear in context
if link_text.lower() in nav_link_texts:
return True

return False
return link_text.lower() in nav_link_texts


def _is_decorative_img_fp(row: ReportRow, soup: BeautifulSoup | None) -> bool:
Expand Down Expand Up @@ -320,7 +321,7 @@ def _is_decorative_img_fp(row: ReportRow, soup: BeautifulSoup | None) -> bool:
return False


def _is_svg_fp(row: ReportRow, soup: BeautifulSoup | None) -> bool:
def _is_svg_fp(soup: BeautifulSoup | None) -> bool:
"""Return True if an SVG accessibility finding is a likely false positive.

SVGs inside links or buttons that already have visible text are decorative
Expand Down Expand Up @@ -397,7 +398,7 @@ def filter_false_positives(

# Check SVG false positives
elif "SVG" in category:
is_fp = _is_svg_fp(row, soup)
is_fp = _is_svg_fp(soup)

if is_fp:
suppressed.append(row)
Expand Down Expand Up @@ -554,7 +555,7 @@ def normalize_programmatic(findings: list[dict], page_title: str,

# ── LLM prompt normalizers ─────────────────────────────────────────────────

def _norm_page_title(data: dict, wcag: str, **ctx) -> list[ReportRow]:
def _norm_page_title(data: dict, wcag: str) -> list[ReportRow]:
rows = []
for issue in data.get("issues", []):
rows.append(ReportRow(
Expand All @@ -571,7 +572,7 @@ def _norm_page_title(data: dict, wcag: str, **ctx) -> list[ReportRow]:
return rows


def _norm_heading_structure(data: dict, wcag: str, **ctx) -> list[ReportRow]:
def _norm_heading_structure(data: dict, wcag: str) -> list[ReportRow]:
rows = []
for issue in data.get("issues", []):
rows.append(ReportRow(
Expand Down Expand Up @@ -600,7 +601,7 @@ def _norm_heading_structure(data: dict, wcag: str, **ctx) -> list[ReportRow]:
return rows


def _norm_link_clarity(data: list, wcag: str, **ctx) -> list[ReportRow]:
def _norm_link_clarity(data: list, wcag: str) -> list[ReportRow]:
rows = []
for item in data:
if item.get("is_clear", True):
Expand All @@ -620,7 +621,7 @@ def _norm_link_clarity(data: list, wcag: str, **ctx) -> list[ReportRow]:
return rows


def _norm_iframe_titles(data: list, wcag: str, **ctx) -> list[ReportRow]:
def _norm_iframe_titles(data: list, wcag: str) -> list[ReportRow]:
rows = []
for item in data:
if item.get("is_descriptive", True):
Expand All @@ -640,7 +641,7 @@ def _norm_iframe_titles(data: list, wcag: str, **ctx) -> list[ReportRow]:
return rows


def _norm_landmark_structure(data: dict, wcag: str, **ctx) -> list[ReportRow]:
def _norm_landmark_structure(data: dict, wcag: str) -> list[ReportRow]:
rows = []
for issue in data.get("issues", []):
rows.append(ReportRow(
Expand All @@ -657,7 +658,7 @@ def _norm_landmark_structure(data: dict, wcag: str, **ctx) -> list[ReportRow]:
return rows


def _norm_label_quality(data: list, wcag: str, **ctx) -> list[ReportRow]:
def _norm_label_quality(data: list, wcag: str) -> list[ReportRow]:
rows = []
for item in data:
if item.get("is_descriptive", True):
Expand All @@ -680,7 +681,7 @@ def _norm_label_quality(data: list, wcag: str, **ctx) -> list[ReportRow]:
return rows


def _norm_required_field_indicators(data: list, wcag: str, **ctx) -> list[ReportRow]:
def _norm_required_field_indicators(data: list, wcag: str) -> list[ReportRow]:
rows = []
for item in data:
issues = item.get("issues", [])
Expand All @@ -702,7 +703,7 @@ def _norm_required_field_indicators(data: list, wcag: str, **ctx) -> list[Report
return rows


def _norm_informative_alt_quality(data: list, wcag: str, **ctx) -> list[ReportRow]:
def _norm_informative_alt_quality(data: list, wcag: str) -> list[ReportRow]:
rows = []
for item in data:
issues = item.get("issues", [])
Expand All @@ -724,7 +725,7 @@ def _norm_informative_alt_quality(data: list, wcag: str, **ctx) -> list[ReportRo
return rows


def _norm_decorative_verification(data: list, wcag: str, **ctx) -> list[ReportRow]:
def _norm_decorative_verification(data: list, wcag: str) -> list[ReportRow]:
rows = []
for item in data:
if item.get("likely_decorative", True):
Expand All @@ -744,7 +745,7 @@ def _norm_decorative_verification(data: list, wcag: str, **ctx) -> list[ReportRo
return rows


def _norm_actionable_image_alt(data: list, wcag: str, **ctx) -> list[ReportRow]:
def _norm_actionable_image_alt(data: list, wcag: str) -> list[ReportRow]:
rows = []
for item in data:
issues = item.get("issues", [])
Expand All @@ -767,7 +768,7 @@ def _norm_actionable_image_alt(data: list, wcag: str, **ctx) -> list[ReportRow]:
return rows


def _norm_svg_accessibility(data: list, wcag: str, **ctx) -> list[ReportRow]:
def _norm_svg_accessibility(data: list, wcag: str) -> list[ReportRow]:
rows = []
for item in data:
issues = item.get("issues", [])
Expand All @@ -788,7 +789,7 @@ def _norm_svg_accessibility(data: list, wcag: str, **ctx) -> list[ReportRow]:
return rows


def _norm_icon_font_accessibility(data: list, wcag: str, **ctx) -> list[ReportRow]:
def _norm_icon_font_accessibility(data: list, wcag: str) -> list[ReportRow]:
rows = []
for item in data:
issues = item.get("issues", [])
Expand Down Expand Up @@ -880,6 +881,7 @@ def deduplicate_with_llm(
response_text = response.choices[0].message.content or "[]"
else:
import anthropic

from processing_scripts.llm_client.client import supports_temperature

client = anthropic.Anthropic(api_key=api_key)
Expand All @@ -897,7 +899,7 @@ def deduplicate_with_llm(

to_remove: list[int] = safe_parse_json(response_text)
if not isinstance(to_remove, list):
raise ValueError(f"Expected list, got {type(to_remove)}")
raise TypeError(f"Expected list, got {type(to_remove)}")

remove_set = {int(i) for i in to_remove}
original_count = len(rows)
Expand All @@ -907,8 +909,8 @@ def deduplicate_with_llm(
print(f" LLM deduplication removed: {removed} duplicate(s)")
return deduped

except Exception as exc:
print(f" WARNING: LLM deduplication failed ({exc}), skipping.")
except Exception:
logger.exception("LLM deduplication failed, skipping.")
return rows


Expand Down Expand Up @@ -1086,4 +1088,4 @@ def main():


if __name__ == "__main__":
main()
main()
Loading
Loading