"""Node base classes + helpers.""" from __future__ import annotations from abc import ABC, abstractmethod from typing import Any, ClassVar import pandas as pd class ScoreNode(ABC): name: ClassVar[str] label: ClassVar[str] default_params: ClassVar[dict] param_schema: ClassVar[dict] @abstractmethod def compute(self, ctx: "Any", params: dict) -> pd.Series: """returns Series indexed by ticker, 0..100 float.""" class GateNode(ABC): name: ClassVar[str] label: ClassVar[str] default_params: ClassVar[dict] param_schema: ClassVar[dict] @abstractmethod def filter(self, ctx: "Any", params: dict) -> pd.Index: """returns surviving tickers.""" def percentile_rank(series: pd.Series) -> pd.Series: """Percentile rank in [0, 100]. All-equal → 50. NaN preserved.""" if series.empty: return series.astype(float) if series.dropna().nunique() == 1: return pd.Series(50.0, index=series.index) ranked = series.rank(pct=True, na_option="keep") * 100.0 return ranked