Pretty print
This commit is contained in:
103
src/beaky/cli.py
103
src/beaky/cli.py
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user