From 6ae16c180870c4716366ca964c21b639a2773c39 Mon Sep 17 00:00:00 2001 From: McElwain Date: Fri, 1 May 2026 17:30:24 -0500 Subject: [PATCH] feat: add flexible ratio input for line item quality ratings --- app/routes/line_items.py | 36 ++++++- app/static/app.css | 148 +++++++++++++++++++++++++++++ app/templates/base.html | 2 +- app/templates/line_items/list.html | 77 ++++++++++++--- 4 files changed, 249 insertions(+), 14 deletions(-) diff --git a/app/routes/line_items.py b/app/routes/line_items.py index 22fbf52..72a37b0 100644 --- a/app/routes/line_items.py +++ b/app/routes/line_items.py @@ -40,6 +40,15 @@ def _to_decimal(value: str | None) -> Decimal | None: return None +def _format_decimal_compact(value: Decimal | None) -> str: + if value is None: + return "" + text = format(value.normalize(), "f") + if "." in text: + text = text.rstrip("0").rstrip(".") + return text or "0" + + def _line_item_extra(item: DocumentLineItem) -> dict: return dict(item.raw_json or {}) @@ -49,6 +58,28 @@ def _line_item_quality_rating(item: DocumentLineItem) -> str: return "" if value is None else str(value) +def _line_item_quality_rating_value(item: DocumentLineItem) -> str: + extra = _line_item_extra(item) + value = extra.get("quality_rating_value") + if value is not None: + return str(value) + + raw = _to_decimal(extra.get("quality_rating")) + return "" if raw is None else _format_decimal_compact(raw) + + +def _line_item_quality_rating_scale(item: DocumentLineItem) -> str: + extra = _line_item_extra(item) + scale = extra.get("quality_rating_scale") + if scale is not None and str(scale).strip(): + return str(scale) + + raw = _to_decimal(extra.get("quality_rating")) + if raw is None: + return "10" + return "100" if raw > 10 else "10" + + def _line_item_quality_note(item: DocumentLineItem) -> str: value = _line_item_extra(item).get("quality_note") return "" if value is None else str(value) @@ -105,6 +136,8 @@ def _build_row(item: DocumentLineItem) -> dict | None: "category": item.category or "", "confidence": "", "quality_rating": _line_item_quality_rating(item), + "quality_rating_value": _line_item_quality_rating_value(item), + "quality_rating_scale": _line_item_quality_rating_scale(item), "quality_note": _line_item_quality_note(item), "quality_status": _line_item_quality_status(item), "is_reviewed": bool(_line_item_extra(item).get("reviewed_at")), @@ -260,7 +293,8 @@ def save_line_item_review( rating_min: str = Form(""), rating_max: str = Form(""), return_to: str = Form("list"), - quality_rating: str = Form(""), + quality_rating_value: str = Form(""), + quality_rating_scale: str = Form(""), quality_note: str = Form(""), is_approved: str = Form(""), is_excluded: str = Form(""), diff --git a/app/static/app.css b/app/static/app.css index 6f5d4ea..7eb6767 100644 --- a/app/static/app.css +++ b/app/static/app.css @@ -6083,3 +6083,151 @@ table { } } /* ===== end line item queue header relayout ===== */ + + + +/* ===== line item queue card polish ===== */ +.line-queue-card-head { + display: grid; + grid-template-columns: minmax(0, 1fr) auto auto; + gap: 0.72rem 0.85rem; + align-items: start; + margin-bottom: 0.62rem; +} + +.line-queue-title-block { + min-width: 0; +} + +.line-queue-kicker { + font-size: 0.82rem; + line-height: 1.12; + color: #6b7280; + margin-bottom: 0.08rem; +} + +.line-queue-title { + margin: 0 !important; + font-size: 0.96rem; + line-height: 1.02; + font-weight: 700; + color: #1f2937; + word-break: break-word; +} + +.line-queue-flags { + display: flex; + flex-direction: column; + gap: 0.16rem; + align-items: flex-start; + padding-top: 0.02rem; +} + +.line-queue-flag { + display: grid; + grid-template-columns: 0.95rem auto; + align-items: center; + column-gap: 0.38rem; + font-size: 0.78rem; + line-height: 1.02; + white-space: nowrap; + margin: 0; +} + +.line-queue-flag input { + width: 0.92rem; + height: 0.92rem; + margin: 0; +} + +.line-queue-badges { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 0.28rem; +} + +.line-queue-badges .badge { + font-size: 0.74rem; + padding: 0.2rem 0.48rem; + line-height: 1.04; + white-space: nowrap; +} + +.queue-rating-input-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto 5.2rem; + gap: 0.38rem; + align-items: center; +} + +.queue-rating-slash { + font-size: 1rem; + color: #6b7280; + text-align: center; +} + +.queue-rating-input-row input { + height: 2.25rem; + font-size: 0.82rem; +} + +.queue-rating-denominator { + border-radius: 0.92rem; + text-align: center; +} + +.queue-rating-scale-toggle { + min-width: 4.5rem; + border-radius: 0.92rem; + border: 1px solid #cfd6df; + background: #fff; + color: #111827; + padding: 0 0.85rem; + cursor: pointer; +} + +.queue-rating-scale-toggle:active { + transform: translateY(1px); +} + +@media (max-width: 900px) { + .line-queue-card-head { + grid-template-columns: minmax(0, 1fr) auto auto; + gap: 0.52rem 0.62rem; + } + + .line-queue-kicker { + font-size: 0.78rem; + } + + .line-queue-title { + font-size: 0.9rem; + } + + .line-queue-flag { + font-size: 0.74rem; + grid-template-columns: 0.9rem auto; + column-gap: 0.32rem; + } + + .line-queue-flag input { + width: 0.86rem; + height: 0.86rem; + } + + .line-queue-badges .badge { + font-size: 0.7rem; + padding: 0.18rem 0.42rem; + } + + .queue-rating-input-row input { + height: 2.15rem; + font-size: 0.8rem; + } + + .queue-rating-input-row { + grid-template-columns: minmax(0, 1fr) auto 4.7rem; + } +} +/* ===== end line item queue card polish ===== */ diff --git a/app/templates/base.html b/app/templates/base.html index 320f3de..9148c80 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -4,7 +4,7 @@ {% block title %}Document Processor{% endblock %} - + diff --git a/app/templates/line_items/list.html b/app/templates/line_items/list.html index 3f8a647..5c34be1 100644 --- a/app/templates/line_items/list.html +++ b/app/templates/line_items/list.html @@ -1,5 +1,12 @@ {% extends "base.html" %} -{% block title %}Line Items{% endblock %} +{% block title %}Line Items + + + + + + +{% endblock %} {% block content %}