Pretty print

This commit is contained in:
2026-03-22 11:38:46 +01:00
parent 207d6565c7
commit c77ccf1eb9

View File

@@ -1,10 +1,13 @@
import argparse import argparse
import re as _re
from datetime import datetime
import yaml import yaml
from pydantic import ValidationError from pydantic import ValidationError
from beaky import _ansi from beaky import _ansi
from beaky.config import Config from beaky.config import Config
from beaky.datamodels.ticket import Bet, Ticket
from beaky.image_classifier.classifier import img_classify from beaky.image_classifier.classifier import img_classify
from beaky.link_classifier.classifier import LinkClassifier from beaky.link_classifier.classifier import LinkClassifier
from beaky.resolvers.resolver import TicketResolver, TicketVerdict from beaky.resolvers.resolver import TicketResolver, TicketVerdict
@@ -23,6 +26,98 @@ def _verdict_str(verdict: TicketVerdict) -> str:
return _ansi.gray(text) return _ansi.gray(text)
_FC = 14 # field column visual width
_VC = 24 # value column visual width
_FIELD_LABELS: dict[str, str] = {
"team1Name": "team1",
"team2Name": "team2",
}
_FIELD_ORDER = ["type", "team1Name", "team2Name", "date", "league"]
_SKIP_FIELDS = {"ticketType"}
def _vlen(text: str) -> int:
"""Visual length of a string — strips ANSI escape codes."""
return len(_re.sub(r"\033\[[^m]*m", "", text))
def _vpad(text: str, width: int) -> str:
"""Pad text to visual width, accounting for ANSI codes."""
return text + " " * max(0, width - _vlen(text))
def _bet_fields(bet: Bet) -> dict[str, str]:
fields: dict[str, str] = {"type": type(bet).__name__}
for k, v in vars(bet).items():
if k in _SKIP_FIELDS:
continue
if k == "date" and isinstance(v, datetime):
fields[k] = v.strftime("%Y-%m-%d %H:%M")
else:
fields[k] = str(v)
return fields
def _tbl_row(field: str, lval: str, ival: str) -> str:
label = _FIELD_LABELS.get(field, field)
return f"{_vpad(label, _FC)}{_vpad(lval, _VC)}{_vpad(ival, _VC)}"
def _tbl_divider(left: str, mid: str, right: str, fill: str = "") -> str:
return f" {left}{fill * (_FC + 2)}{mid}{fill * (_VC + 2)}{mid}{fill * (_VC + 2)}{right}"
def _print_bet_compare(idx: int, link_bet: Bet | None, img_bet: Bet | None) -> None:
print(f"\n {_ansi.bold(_ansi.cyan(f' Bet {idx} '))}")
link_fields = _bet_fields(link_bet) if link_bet is not None else {}
img_fields = _bet_fields(img_bet) if img_bet is not None else {}
all_keys = link_fields.keys() | img_fields.keys()
keys = [k for k in _FIELD_ORDER if k in all_keys] + [k for k in all_keys if k not in _FIELD_ORDER]
print(_tbl_divider("", "", ""))
print(_tbl_row("field", _ansi.bold("link classifier"), _ansi.bold("image classifier")))
print(_tbl_divider("", "", ""))
for key in keys:
lval_raw = link_fields.get(key, "")
ival_raw = img_fields.get(key, "")
match = lval_raw == ival_raw
lval = _ansi.gray("") if lval_raw == "" else (lval_raw if match else _ansi.yellow(lval_raw))
ival = _ansi.gray("") if ival_raw == "" else (ival_raw if match else _ansi.yellow(ival_raw))
# truncate raw value if too long before applying color
if len(lval_raw) > _VC:
lval_raw = lval_raw[:_VC - 1] + ""
lval = lval_raw if match else _ansi.yellow(lval_raw)
if len(ival_raw) > _VC:
ival_raw = ival_raw[:_VC - 1] + ""
ival = ival_raw if match else _ansi.yellow(ival_raw)
print(_tbl_row(key, lval, ival))
print(_tbl_divider("", "", ""))
def _print_compare(link_ticket: Ticket, img_ticket: Ticket) -> None:
n_link = len(link_ticket.bets)
n_img = len(img_ticket.bets)
header = f" Ticket {link_ticket.id} — link: {n_link} bet{'s' if n_link != 1 else ''} │ img: {n_img} bet{'s' if n_img != 1 else ''}"
total_w = _FC + _VC * 2 + 10
print(f"\n{'' * total_w}")
print(_ansi.bold(header))
print(f"{'' * total_w}")
for i, (lb, ib) in enumerate(zip(link_ticket.bets, img_ticket.bets), start=1):
_print_bet_compare(i, lb, ib)
for i, lb in enumerate(link_ticket.bets[n_img:], start=n_img + 1):
_print_bet_compare(i, lb, None)
for i, ib in enumerate(img_ticket.bets[n_link:], start=n_link + 1):
_print_bet_compare(i, None, ib)
def load_config(path: str) -> Config | None: def load_config(path: str) -> Config | None:
with open(path) as f: with open(path) as f:
config_dict = yaml.safe_load(f) config_dict = yaml.safe_load(f)
@@ -80,11 +175,9 @@ def main() -> None:
print(f"ERROR: ticket id {args.id} not found") print(f"ERROR: ticket id {args.id} not found")
return return
for link in links: for link in links:
linkClass = linkclassifier.classify(link) link_ticket = linkclassifier.classify(link)
imgClass = img_classify(["./data/screenshots/{link.id}.png"], ticket_id=link.id) img_ticket = img_classify([f"./data/screenshots/{link.id}.png"], ticket_id=link.id)
_print_compare(link_ticket, img_ticket)
print(linkClass)
print(imgClass)
if args.mode == "resolve": if args.mode == "resolve":
classifier = LinkClassifier() classifier = LinkClassifier()