refactor: share official records parser
This commit is contained in:
+10
-48
@@ -7,9 +7,9 @@ from dataclasses import asdict, dataclass
|
||||
from datetime import date, datetime, time, timezone
|
||||
|
||||
import httpx
|
||||
from bs4 import BeautifulSoup, Tag
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
from rf4_research.official_parser import RecordsContractError, parse_official_records
|
||||
|
||||
from .models import (
|
||||
Bait, BaitKind, CatchReport, Fish, ImportStatus, ModerationStatus,
|
||||
@@ -60,54 +60,16 @@ def external_id(record: RawRecord) -> str:
|
||||
return hashlib.sha256("|".join(normalize(part) for part in parts).encode()).hexdigest()
|
||||
|
||||
|
||||
def parse_weight(raw: str) -> int:
|
||||
value = " ".join(raw.replace("\xa0", " ").split()).lower()
|
||||
match = re.fullmatch(r"([\d .,'’]+)\s*(kg|g)", value)
|
||||
if not match:
|
||||
raise ImportSourceError(f"unsupported weight {raw!r}")
|
||||
number, unit = match.groups()
|
||||
number = number.replace(" ", "").replace("'", "").replace("’", "")
|
||||
if unit == "g":
|
||||
return int(number.replace(".", "").replace(",", ""))
|
||||
if "," in number and "." not in number:
|
||||
number = number.replace(",", ".")
|
||||
return round(float(number) * 1000)
|
||||
|
||||
|
||||
def _text(node: Tag | None) -> str:
|
||||
return node.get_text(" ", strip=True) if node else ""
|
||||
|
||||
|
||||
def parse_html(html: str, *, region: str, category: str) -> list[RawRecord]:
|
||||
soup = BeautifulSoup(html, "html.parser")
|
||||
table = soup.select_one("div.records.flex_table")
|
||||
if table is None:
|
||||
raise ImportSourceError("records table not found")
|
||||
header = table.select_one(":scope > .row.header")
|
||||
expected = ["fish", "weight", "location", "bait", "gamername", "data"]
|
||||
cells = header.find_all("div", recursive=False) if header else []
|
||||
actual = [next((key for key in expected if key in cell.get("class", [])), "") for cell in cells]
|
||||
if actual != expected:
|
||||
raise ImportSourceError(f"record columns changed: {actual}")
|
||||
|
||||
records: list[RawRecord] = []
|
||||
for group in table.select(":scope > .rows > .row > .records_subtable"):
|
||||
group_header = group.select_one(":scope > .row.header")
|
||||
if group_header is None:
|
||||
continue
|
||||
fish = _text(group_header.select_one(".fish .text"))
|
||||
rows = [group_header, *group.select(":scope > .rows > .row")]
|
||||
for row in rows:
|
||||
bait_node = row.select_one(".bait_icon")
|
||||
bait = bait_node.get("title", "").strip() if bait_node else ""
|
||||
try:
|
||||
record_date = datetime.strptime(_text(row.select_one(".data")), "%d.%m.%y").date()
|
||||
except ValueError as exc:
|
||||
raise ImportSourceError("record date format changed") from exc
|
||||
records.append(RawRecord(region.upper(), category, _text(row.select_one(".gamername")) or None, fish, parse_weight(_text(row.select_one(".weight"))), _text(row.select_one(".location")), bait or None, record_date))
|
||||
if not records:
|
||||
raise ImportSourceError("records table is empty")
|
||||
return records
|
||||
try:
|
||||
rows = parse_official_records(html)
|
||||
except RecordsContractError as exc:
|
||||
raise ImportSourceError(str(exc)) from exc
|
||||
return [RawRecord(
|
||||
region=region.upper(), category=category, player=row.player, fish=row.fish,
|
||||
weight_g=row.weight_g, waterbody=row.waterbody, bait=row.bait,
|
||||
record_date=row.record_date,
|
||||
) for row in rows]
|
||||
|
||||
|
||||
def fetch_records(
|
||||
|
||||
Reference in New Issue
Block a user