feat(analyzer): score_combination에 weights 파라미터 추가 (None=기존 fixed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -170,7 +170,11 @@ def build_number_weights(cache: Dict[str, Any]) -> Dict[int, float]:
|
|||||||
return weights
|
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 범위 정규화).
|
6개 번호 조합의 통계적 품질 점수 계산 (0~1 범위 정규화).
|
||||||
|
|
||||||
@@ -181,6 +185,13 @@ def score_combination(numbers: List[int], cache: Dict[str, Any]) -> Dict[str, fl
|
|||||||
- score_cooccur (15%): 공동 출현 기댓값 대비
|
- score_cooccur (15%): 공동 출현 기댓값 대비
|
||||||
- score_diversity (10%): 연속번호, 범위, 구간 다양성
|
- 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:
|
Returns:
|
||||||
{"score_total": ..., "score_frequency": ..., ...}
|
{"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_total = (
|
||||||
score_frequency * 0.25
|
score_frequency * weights[0]
|
||||||
+ score_fingerprint * 0.30
|
+ score_fingerprint * weights[1]
|
||||||
+ score_gap * 0.20
|
+ score_gap * weights[2]
|
||||||
+ score_cooccur * 0.15
|
+ score_cooccur * weights[3]
|
||||||
+ score_diversity * 0.10
|
+ score_diversity * weights[4]
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
45
lotto/tests/test_analyzer_weighted.py
Normal file
45
lotto/tests/test_analyzer_weighted.py
Normal file
@@ -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])
|
||||||
Reference in New Issue
Block a user