diff --git a/web/assets/main.css b/web/assets/main.css index 229328b..c7079de 100644 --- a/web/assets/main.css +++ b/web/assets/main.css @@ -504,8 +504,16 @@ main { display: block; } text-decoration: none; transition: color 0.12s ease, border-color 0.12s ease; } -.copy-btn:hover, -.report-btn:hover { color: #1f2328; border-color: #b9b9b9; } +.copy-btn:hover { color: #1f2328; border-color: #b9b9b9; } +/* Each feedback button is tinted by its meaning: red for "should be rejected" + (the statement is an error), amber for "propose contentious" (a judgment + call), blue for "dispute" (push back on the tag). Copy stays neutral. */ +.report-reject { color: #c0202a; } +.report-reject:hover { color: #c0202a; border-color: #e7a3a3; background: #fdf3f3; } +.report-propose { color: #b06e1f; } +.report-propose:hover { color: #b06e1f; border-color: #e3c08a; background: #fdf7ee; } +.report-dispute { color: #1c4e80; } +.report-dispute:hover { color: #1c4e80; border-color: #a9c6e6; background: #eef4fb; } /* The parser's error message for a rejected statement, under its preview. */ .fail-reason { margin: 0.2rem 0 0; diff --git a/web/src/components.rs b/web/src/components.rs index 86d3c27..42f0e61 100644 --- a/web/src/components.rs +++ b/web/src/components.rs @@ -1233,7 +1233,7 @@ fn fail_preview_row( if has_ref { if let Some(sql) = sql { a { - class: "report-btn", + class: "report-btn report-reject", href: issue_url("should-be-rejected.yml", dialect, parser, sql, reason, &[]), target: "_blank", rel: "noopener noreferrer", @@ -1244,7 +1244,7 @@ fn fail_preview_row( if let Some(rule) = rule { // Already tagged contentious: let people argue it is not. a { - class: "report-btn", + class: "report-btn report-dispute", href: issue_url("not-contentious.yml", dialect, parser, sql, reason, &[("rule", rule.title.as_str())]), target: "_blank", rel: "noopener noreferrer", @@ -1254,7 +1254,7 @@ fn fail_preview_row( } } else { a { - class: "report-btn", + class: "report-btn report-propose", href: issue_url("contentious-construct.yml", dialect, parser, sql, reason, &[]), target: "_blank", rel: "noopener noreferrer", @@ -1747,7 +1747,7 @@ fn parser_score_section(parser: &str) -> Element { "A composite of every dimension, 0 to 100, weighting correctness 40 percent, robustness 30, speed 18, memory 7, and project health 5. Computed only over the dialects this parser models. Speed and memory are ranked against the other parsers on each dialect, while correctness and health are absolute. Each badge also shows this parser's rank on that dimension among all parsers." } div { class: "meta-grid score-grid", - {score_badge(rsx! { Icon { width: 12, height: 12, fill: "currentColor".to_string(), icon: FaBullseye } }, "correctness", s.correctness, crate::score::rank(parser, |x| x.correctness), "Correctness sub-score (0 to 100): recall or acceptance, false-positive avoidance, and round-trip, averaged over the dialects this parser models.".to_string())} + {score_badge(rsx! { Icon { width: 12, height: 12, fill: "currentColor".to_string(), icon: FaBullseye } }, "correctness", s.correctness, crate::score::rank(parser, |x| x.correctness), "Correctness sub-score (0 to 100): recall (excluding contentious constructs) or acceptance, false-positive avoidance, and round-trip, averaged over the dialects this parser models.".to_string())} {score_badge(rsx! { Icon { width: 12, height: 12, fill: "currentColor".to_string(), icon: FaShieldHalved } }, "robustness", s.robustness, crate::score::rank(parser, |x| x.robustness), "Robustness sub-score (0 to 100): empirical panic rate on the real corpus, recursion-depth guarding, unsafe surface, and static panic discipline.".to_string())} {score_badge(rsx! { Icon { width: 12, height: 12, fill: "currentColor".to_string(), icon: FaGaugeHigh } }, "speed", s.speed, crate::score::rank(parser, |x| x.speed), "Speed sub-score (0 to 100): median parse time ranked against the other parsers within each dialect on a log scale, then averaged.".to_string())} {score_badge(rsx! { Icon { width: 12, height: 12, fill: "currentColor".to_string(), icon: FaMicrochip } }, "memory", s.memory, crate::score::rank(parser, |x| x.memory), "Memory sub-score (0 to 100): peak and retained per-statement footprints ranked against the field within each dialect. Shown n/a for FFI parsers, whose C-side allocations are not measured.".to_string())} @@ -2253,7 +2253,7 @@ fn col_help(name: &str) -> Option<&'static str> { "parser" => "The SQL parser library under test.", "dialect" => "The SQL dialect the row reports on.", "overall" => "Overall score (0 to 100): correctness 40 percent, robustness 30, speed 18, memory 7, project health 5, computed only over the dialects the parser models. Higher is better.", - "correctness" => "Correctness sub-score (0 to 100): recall or acceptance, false-positive avoidance, and round-trip, averaged over the parser's dialects. Higher is better.", + "correctness" => "Correctness sub-score (0 to 100): recall (excluding contentious constructs) or acceptance, false-positive avoidance, and round-trip, averaged over the parser's dialects. Higher is better.", "robustness" => "Robustness sub-score (0 to 100): empirical panic rate, recursion-depth guarding, unsafe surface, and static panic discipline. Higher is better.", "speed" => "Speed sub-score (0 to 100): median parse time ranked against the field within each dialect on a log scale, then averaged. Higher is better.", "memory" => "Memory sub-score (0 to 100): peak and retained per-statement footprints ranked against the field within each dialect. n/a for FFI parsers. Higher is better.", diff --git a/web/src/score.rs b/web/src/score.rs index 7863cef..4824f85 100644 --- a/web/src/score.rs +++ b/web/src/score.rs @@ -201,7 +201,13 @@ const PROVENANCE_DIALECT_WEIGHT: f64 = 0.4; /// (recall on reference dialects, acceptance elsewhere), plus false-positive /// avoidance and round-trip where they exist. fn correctness_in_dialect(m: &viz::ParserMetrics) -> Option { - let primary = m.recall_pct.or(m.accept_pct)?; + // Recall excluding contentious constructs, so a parser is not ranked down for + // deliberately declining an engine-accepted quirk. Falls back to strict recall + // where there is no contentious layer, then to acceptance on provenance dialects. + let primary = m + .recall_excl_contentious_pct + .or(m.recall_pct) + .or(m.accept_pct)?; let mut num = 0.5 * (primary / 100.0); let mut den = 0.5; if let Some(fp) = m.false_positive_pct {