From bf152d90fd57367e55bc2912ef58c7fb40e278a2 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Tue, 28 Apr 2026 23:58:38 +0200 Subject: [PATCH] feat(mcp-macro): add compute_percentile + classify_extreme pure helpers --- services/mcp-macro/src/mcp_macro/cot.py | 37 +++++++++++++++++++++ services/mcp-macro/tests/test_cot.py | 44 +++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 services/mcp-macro/src/mcp_macro/cot.py create mode 100644 services/mcp-macro/tests/test_cot.py diff --git a/services/mcp-macro/src/mcp_macro/cot.py b/services/mcp-macro/src/mcp_macro/cot.py new file mode 100644 index 0000000..2d0a373 --- /dev/null +++ b/services/mcp-macro/src/mcp_macro/cot.py @@ -0,0 +1,37 @@ +"""Pure-logic helpers per COT report parsing e analytics. + +Niente HTTP qui — orchestrazione fetch sta in fetchers.py. Tutto testabile +in isolamento. +""" +from __future__ import annotations + +from typing import Literal + +ExtremeSignal = Literal["extreme_short", "extreme_long", "neutral"] + + +def compute_percentile(value: float, history: list[float]) -> float | None: + """Percentile di `value` rispetto ad `history` (0-100, inclusive). + + Restituisce None se history vuoto. Clipped a [0, 100] se value fuori range. + """ + if not history: + return None + n = len(history) + below_or_eq = sum(1 for h in history if h <= value) + pct = 100.0 * below_or_eq / n + return max(0.0, min(100.0, pct)) + + +def classify_extreme(percentile: float | None, threshold: float = 5.0) -> ExtremeSignal: + """Classifica un percentile come estremo short/long o neutral. + + threshold default 5 → flagga ≤ 5 come short, ≥ 100-5=95 come long. + """ + if percentile is None: + return "neutral" + if percentile <= threshold: + return "extreme_short" + if percentile >= 100.0 - threshold: + return "extreme_long" + return "neutral" diff --git a/services/mcp-macro/tests/test_cot.py b/services/mcp-macro/tests/test_cot.py new file mode 100644 index 0000000..9ed678e --- /dev/null +++ b/services/mcp-macro/tests/test_cot.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from mcp_macro.cot import classify_extreme, compute_percentile + + +def test_compute_percentile_basic(): + history = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + assert compute_percentile(50, history) == 50.0 + assert compute_percentile(10, history) == 10.0 + assert compute_percentile(100, history) == 100.0 + + +def test_compute_percentile_value_below_min(): + history = [10, 20, 30] + assert compute_percentile(5, history) == 0.0 + + +def test_compute_percentile_value_above_max(): + history = [10, 20, 30] + assert compute_percentile(40, history) == 100.0 + + +def test_compute_percentile_empty_history(): + assert compute_percentile(50, []) is None + + +def test_classify_extreme_below_threshold(): + assert classify_extreme(3.0) == "extreme_short" + assert classify_extreme(5.0) == "extreme_short" # boundary inclusive + + +def test_classify_extreme_above_threshold(): + assert classify_extreme(96.0) == "extreme_long" + assert classify_extreme(95.0) == "extreme_long" # boundary inclusive + + +def test_classify_extreme_neutral(): + assert classify_extreme(50.0) == "neutral" + assert classify_extreme(94.99) == "neutral" + assert classify_extreme(5.01) == "neutral" + + +def test_classify_extreme_none_input(): + assert classify_extreme(None) == "neutral"