92 lines
2.7 KiB
Python
92 lines
2.7 KiB
Python
import json
|
|
from collections.abc import Iterator, Sequence
|
|
from dataclasses import dataclass
|
|
from functools import cached_property
|
|
from pathlib import Path
|
|
|
|
from my.core import Res, datetime_aware, get_files
|
|
from my.core.compat import fromisoformat
|
|
from my.experimental.destructive_parsing import Manager
|
|
|
|
from my.config import topcoder as config # type: ignore[attr-defined] # isort: skip
|
|
|
|
|
|
def inputs() -> Sequence[Path]:
|
|
return get_files(config.export_path)
|
|
|
|
|
|
@dataclass
|
|
class Competition:
|
|
contest_id: str
|
|
contest: str
|
|
percentile: float
|
|
date_str: str
|
|
|
|
@cached_property
|
|
def uid(self) -> str:
|
|
return self.contest_id
|
|
|
|
@cached_property
|
|
def when(self) -> datetime_aware:
|
|
return fromisoformat(self.date_str)
|
|
|
|
@classmethod
|
|
def make(cls, j) -> Iterator[Res['Competition']]:
|
|
assert isinstance(j.pop('rating'), float)
|
|
assert isinstance(j.pop('placement'), int)
|
|
|
|
cid = j.pop('challengeId')
|
|
cname = j.pop('challengeName')
|
|
percentile = j.pop('percentile')
|
|
date_str = j.pop('date')
|
|
|
|
yield cls(
|
|
contest_id=cid,
|
|
contest=cname,
|
|
percentile=percentile,
|
|
date_str=date_str,
|
|
)
|
|
|
|
|
|
def _parse_one(p: Path) -> Iterator[Res[Competition]]:
|
|
d = json.loads(p.read_text())
|
|
|
|
# TODO manager should be a context manager?
|
|
m = Manager()
|
|
|
|
h = m.helper(d)
|
|
h.pop_if_primitive('version', 'id')
|
|
|
|
h = h.zoom('result')
|
|
h.check('success', expected=True)
|
|
h.check('status', 200)
|
|
h.pop_if_primitive('metadata')
|
|
|
|
h = h.zoom('content')
|
|
h.pop_if_primitive('handle', 'handleLower', 'userId', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy')
|
|
|
|
# NOTE at the moment it's empty for me, but it will result in an error later if there is some data here
|
|
h.zoom('DEVELOP').zoom('subTracks')
|
|
|
|
h = h.zoom('DATA_SCIENCE')
|
|
# TODO multi zoom? not sure which axis, e.g.
|
|
# zoom('SRM', 'history') or zoom('SRM', 'MARATHON_MATCH')
|
|
# or zoom(('SRM', 'history'), ('MARATHON_MATCH', 'history'))
|
|
srms = h.zoom('SRM').zoom('history')
|
|
mms = h.zoom('MARATHON_MATCH').zoom('history')
|
|
|
|
for c in srms.item + mms.item:
|
|
# NOTE: so here we are actually just using pure dicts in .make method
|
|
# this is kinda ok since it will be checked by parent Helper
|
|
# but also expects cooperation from .make method (e.g. popping items from the dict)
|
|
# could also wrap in helper and pass to .make .. not sure
|
|
# an argument could be made that .make isn't really a class methond..
|
|
# it's pretty specific to this parser only
|
|
yield from Competition.make(j=c)
|
|
|
|
yield from m.check()
|
|
|
|
|
|
def data() -> Iterator[Res[Competition]]:
|
|
*_, last = inputs()
|
|
return _parse_one(last)
|