# -*- coding: utf-8 -*- """시즌 등록부. 시즌마다 대상 물질군·게이트·배점이 다르다. 한 프로세스가 모든 시즌을 서빙하므로 설정은 **환경변수가 아니라 인자**로 흐른다 - 환경변수로 두면 시즌 하나의 설정이 다른 시즌 요청에 새어 들어간다. """ SEASONS = [ { "number": 1, "name": "Open Materials Challenge", "topic": "Solid-State Battery Electrolyte", "topic_ko": "전고체 배터리 전해질", "mobile_ion": "Li", "opens": "2026-08-21", "closes": "2026-11-30", "prize": 0, "open": True, "prefix": "", "anchors": "anchor_scores.json", # 게이트는 자격만 본다. 성능은 점수로 반영한다. "gate": { "gap_min": 1.0, # 전자전도체는 전지를 단락시킨다 - 진짜 요건 "hull_max": 0.10, # 합성 가능성 상한 "ea_max": 1.0, # 리튬 관통 경로 존재 여부. 이진 판정 "max_atoms": 60, "require_elements": ("Li",), }, # 리튬 이동은 게이트에서만 본다 (관통 경로가 있는가). 순위 축으로 쓰지 않는다. "weights": { "oxidation": 40, # 산화 안정성 "li_stability": 35, # 리튬금속 안정성 "novelty": 25, # 용도 신규성 (조성 신규성이 아니다) }, }, ] BY_NUMBER = {s["number"]: s for s in SEASONS} DEFAULT = SEASONS[-1] # 공개면에 내보내지 않는 키. 채점기 내부 사정이 새면 표적화된 제출이 들어온다. _PRIVATE = ("prefix", "anchors", "gate") def get(n=None): if n in (None, ""): return DEFAULT try: return BY_NUMBER.get(int(n), DEFAULT) except (TypeError, ValueError): return DEFAULT def gate(s): return dict(s.get("gate") or {}) def path(s, name): return (s.get("prefix") or "") + name def public(s): out = {k: v for k, v in s.items() if k not in _PRIVATE} g = s.get("gate") or {} # 참가자가 알아야 반려를 피할 수 있는 값만 공개한다. out["limits"] = {"max_atoms": g.get("max_atoms"), "hull_max": g.get("hull_max"), "gap_min": g.get("gap_min")} return out def all_public(): return [public(s) for s in SEASONS]