From 1694823129adef4c7b733810324eda8d6b2c3df6 Mon Sep 17 00:00:00 2001 From: gahusb Date: Fri, 22 May 2026 03:06:26 +0900 Subject: [PATCH] =?UTF-8?q?feat(analyzer):=20score=5Fcombination=EC=97=90?= =?UTF-8?q?=20weights=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(None=3D=EA=B8=B0=EC=A1=B4=20fixed)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- lotto/app/analyzer.py | 27 ++++++++++++---- lotto/tests/test_analyzer_weighted.py | 45 +++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 6 deletions(-) create mode 100644 lotto/tests/test_analyzer_weighted.py diff --git a/lotto/app/analyzer.py b/lotto/app/analyzer.py index 36405fe..ed7e8ed 100644 --- a/lotto/app/analyzer.py +++ b/lotto/app/analyzer.py @@ -170,7 +170,11 @@ def build_number_weights(cache: Dict[str, Any]) -> Dict[int, float]: return weights -def score_combination(numbers: List[int], cache: Dict[str, Any]) -> Dict[str, float]: +def score_combination( + numbers: List[int], + cache: Dict[str, Any], + weights: Optional[List[float]] = None, +) -> Dict[str, float]: """ 6개 번호 조합의 통계적 품질 점수 계산 (0~1 범위 정규화). @@ -181,6 +185,13 @@ def score_combination(numbers: List[int], cache: Dict[str, Any]) -> Dict[str, fl - score_cooccur (15%): 공동 출현 기댓값 대비 - score_diversity (10%): 연속번호, 범위, 구간 다양성 + Args: + numbers: 6개 번호 리스트 + cache: build_analysis_cache() 반환 딕셔너리 + weights: 5가지 기법별 가중치 리스트 [frequency, fingerprint, gap, cooccur, diversity]. + None이면 기본값 [0.25, 0.30, 0.20, 0.15, 0.10] 사용. + 길이가 5가 아니면 ValueError 발생. + Returns: {"score_total": ..., "score_frequency": ..., ...} """ @@ -282,12 +293,16 @@ def score_combination(numbers: List[int], cache: Dict[str, Any]) -> Dict[str, fl ) # ── 최종 가중 합산 ──────────────────────────────────────────────────────── + if weights is None: + weights = [0.25, 0.30, 0.20, 0.15, 0.10] + if len(weights) != 5: + raise ValueError("weights must have 5 elements") score_total = ( - score_frequency * 0.25 - + score_fingerprint * 0.30 - + score_gap * 0.20 - + score_cooccur * 0.15 - + score_diversity * 0.10 + score_frequency * weights[0] + + score_fingerprint * weights[1] + + score_gap * weights[2] + + score_cooccur * weights[3] + + score_diversity * weights[4] ) return { diff --git a/lotto/tests/test_analyzer_weighted.py b/lotto/tests/test_analyzer_weighted.py new file mode 100644 index 0000000..6d8a862 --- /dev/null +++ b/lotto/tests/test_analyzer_weighted.py @@ -0,0 +1,45 @@ +import sys +import os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "app")) + +import pytest +from analyzer import score_combination, build_analysis_cache + + +@pytest.fixture +def cache(): + # build_analysis_cache expects [(drw_no, [n1,n2,n3,n4,n5,n6]), ...] tuples + fake_draws = [ + (1, [1, 2, 3, 4, 5, 6]), + (2, [7, 8, 9, 10, 11, 12]), + ] + return build_analysis_cache(fake_draws) + + +def test_score_default_uses_fixed_weights(cache): + """weights=None은 기존 fixed [0.25, 0.30, 0.20, 0.15, 0.10]과 동등.""" + s = score_combination([1, 2, 3, 4, 5, 6], cache) + assert "score_total" in s + assert 0.0 <= s["score_total"] <= 2.0 + for k in ("score_frequency", "score_fingerprint", "score_gap", + "score_cooccur", "score_diversity"): + assert k in s + + +def test_score_with_custom_weights_sums_correctly(cache): + """weights=[1,0,0,0,0]은 score_total == score_frequency.""" + s = score_combination([1, 2, 3, 4, 5, 6], cache, weights=[1.0, 0.0, 0.0, 0.0, 0.0]) + assert s["score_total"] == pytest.approx(s["score_frequency"], rel=1e-3) + + +def test_score_with_uniform_weights(cache): + """weights=[0.2]*5는 단순 평균.""" + s = score_combination([1, 2, 3, 4, 5, 6], cache, weights=[0.2] * 5) + expected = 0.2 * (s["score_frequency"] + s["score_fingerprint"] + + s["score_gap"] + s["score_cooccur"] + s["score_diversity"]) + assert s["score_total"] == pytest.approx(expected, rel=1e-3) + + +def test_score_weights_wrong_length_raises(cache): + with pytest.raises((ValueError, AssertionError)): + score_combination([1, 2, 3, 4, 5, 6], cache, weights=[0.5, 0.5])