paste.txt

ChatGPT neutral 19 чанков ~18 мин чтения
# S-G Index v2.2: Python Implementation Examples<br> ## Готовый код для 3 ключевых улучшений<br> <br> **Дата:** 8 января 2026 <br> **Язык:** Python 3.10+ <br> **Зависимости:** numpy, scipy, pandas<br> <br> ---<br> <br> ## 1. HYSTERESIS (Гистерезис)<br> <br> ```python<br> import numpy as np<br> from typing import Optional<br> <br> class SGIndexWithHysteresis:<br> """<br> S-G Index calculator с гистерезисом (память системы)<br> <br> После кризисов восстановление медленнее, чем падение.<br> """<br> <br> def __init__(self, eta: float = 0.3):<br> """<br> Args:<br> eta: коэффициент восстановления (0.2-0.4)<br> 0.3 = восстанавливаем 30% прироста за месяц<br> """<br> self.eta = eta<br> self.s_kpi_previous: Optional[float] = None<br> self.history = []<br> <br> def calculate_s_raw(self, C: float, T: float, V: float, <br> P: float, D: float, R: float) -> float:<br> """<br> Рассчитывает S_raw (до маппинга в S_KPI)<br> <br> Это стандартная формула v2.1c:<br> S_pot = C^0.25 × T^0.40 × V^0.35<br> F_lin = 1 - θ × max(P - C, 0) / P_scale<br> F_soft = 1 / (1 + exp(k × (D - R)))<br> S_raw = S_pot × F_lin × F_soft<br> """<br> # Constants<br> w_C, w_T, w_V = 0.25, 0.40, 0.35<br> theta = 0.85<br> P_scale = 0.45<br> k = 10.0 # steepness<br> <br> # S_pot<br> s_pot = (C ** w_C) * (T ** w_T) * (V ** w_V)<br> <br> # F_lin (external pressure penalty)<br> exposure = max(P - C, 0)<br> f_lin = 1.0 - theta * (exposure / P_scale)<br> f_lin = max(f_lin, 0.0) # non-negative<br> <br> # F_soft (defense vs attack)<br> load = D - R<br> f_soft = 1.0 / (1.0 + np.exp(k * load))<br> <br> # S_raw<br> s_raw = s_pot * f_lin * f_soft<br> <br> return s_raw<br> <br> def map_to_kpi(self, s_raw: float) -> float:<br> """<br> Логнормальный маппинг S_raw → S_KPI (0-100)<br> <br> Используем формулу из v2.1c:<br> ln(S_KPI) = a + b × ln(S_raw)<br> <br> С калибровкой: S_raw = 0.4 → S_KPI = 25<br> S_raw = 1.5 → S_KPI = 100<br> """<br> # Calibrated parameters<br> a = -22.7726<br> b = 31.2785<br> <br> # Log transform<br> ln_s_raw = np.log(s_raw)<br> ln_s_kpi = a + b * ln_s_raw<br> s_kpi = np.exp(ln_s_kpi)<br> <br> # Clip to valid range<br> s_kpi = np.clip(s_kpi, 0, 100)<br> <br> return s_kpi<br> <br> def inverse_map_from_kpi(self, s_kpi: float) -> float:<br> """<br> Обратный маппинг S_KPI → S_raw<br> <br> Нужен для гистерезиса, чтобы сравнивать в S_raw пространстве<br> """<br> a = -22.7726<br> b = 31.2785<br> <br> # Inverse: S_raw = exp((ln(S_KPI) - a) / b)<br> ln_s_kpi = np.log(s_kpi + 1e-6) # avoid log(0)<br> ln_s_raw = (ln_s_kpi - a) / b<br> s_raw = np.exp(ln_s_raw)<br> <br> return s_raw<br> <br> def apply_hysteresis(self, s_raw_current: float) -> float:<br> """<br> Применяет гистерезис: медленное восстановление после кризиса<br> <br> Args:<br> s_raw_current: текущее S_raw (без гистерезиса)<br> <br> Returns:<br> s_raw_adjusted: скорректированное S_raw (с гистерезисом)<br> """<br> # Первый расчёт — нет предыдущего значения<br> if self.s_kpi_previous is None:<br> return s_raw_current<br> <br> # Текущий потенциальный S_KPI<br> s_kpi_current_potential = self.map_to_kpi(s_raw_current)<br> <br> # Проверяем направление изменения<br> if s_kpi_current_potential <= self.s_kpi_previous:<br> # ПАДЕНИЕ — без коррекции (падаем сразу)<br> return s_raw_current<br> else:<br> # РОСТ — применяем вязкость восстановления<br> # Обратный маппинг previous KPI → previous raw<br> s_raw_previous = self.inverse_map_from_kpi(self.s_kpi_previous)<br> <br> # Восстанавливаем только часть прироста<br> delta = s_raw_current - s_raw_previous<br> s_raw_adjusted = s_raw_previous + self.eta * delta<br> <br> return s_raw_adjusted<br> <br> def calculate_index(self, C: float, T: float, V: float,<br> P: float, D: float, R: float) -> dict:<br> """<br> Полный расчёт индекса с гистерезисом<br> <br> Returns:<br> dict: {<br> 's_raw_original': float, # без гистерезиса<br> 's_raw_adjusted': float, # с гистерезисом<br> 's_kpi': float, # итоговый KPI (0-100)<br> 'zone': str # 🔴🟡🟢🔵🟣<br> }<br> """<br> # Шаг 1: Расчёт S_raw (без гистерезиса)<br> s_raw_original = self.calculate_s_raw(C, T, V, P, D, R)<br> <br> # Шаг 2: Применяем гистерезис<br> s_raw_adjusted = self.apply_hysteresis(s_raw_original)<br> <br> # Шаг 3: Маппинг в S_KPI<br> s_kpi = self.map_to_kpi(s_raw_adjusted)<br> <br> # Шаг 4: Определение зоны<br> if s_kpi < 25:<br> zone = "🔴 Critical"<br> elif s_kpi < 40:<br> zone = "🟡 Warning"<br> elif s_kpi < 60:<br> zone = "🟢 Satisfactory"<br> elif s_kpi < 100:<br> zone = "🔵 Strong"<br> else:<br> zone = "🟣 Exceptional"<br> <br> # Сохраняем для следующей итерации<br> self.s_kpi_previous = s_kpi<br> <br> # История<br> self.history.append({<br> 'C': C, 'T': T, 'V': V, 'P': P, 'D': D, 'R': R,<br> 's_raw_original': s_raw_original,<br> 's_raw_adjusted': s_raw_adjusted,<br> 's_kpi': s_kpi,<br> 'zone': zone<br> })<br> <br> return {<br> 's_raw_original': s_raw_original,<br> 's_raw_adjusted': s_raw_adjusted,<br> 's_kpi': s_kpi,<br> 'zone': zone,<br> 'hysteresis_effect': s_raw_original - s_raw_adjusted<br> }<br> <br> <br> # === ПРИМЕР ИСПОЛЬЗОВАНИЯ ===<br> <br> if __name__ == "__main__":<br> import pandas as pd<br> <br> # Создаём калькулятор с η=0.3<br> calculator = SGIndexWithHysteresis(eta=0.3)<br> <br> # Симуляция кризиса и восстановления<br> months = [<br> # Нормальное состояние<br> {'C': 0.70, 'T': 0.65, 'V': 0.60, 'P': 0.35, 'D': 0.30, 'R': 0.75},<br> <br> # КРИЗИС! (Jan-2022 style)<br> {'C': 0.70, 'T': 0.45, 'V': 0.55, 'P': 0.55, 'D': 0.85, 'R': 0.65},<br> <br> # Восстановление месяц 1 (D снижается)<br> {'C': 0.72, 'T': 0.48, 'V': 0.58, 'P': 0.45, 'D': 0.60, 'R': 0.70},<br> <br> # Восстановление месяц 2<br> {'C': 0.73, 'T': 0.52, 'V': 0.60, 'P': 0.40, 'D': 0.40, 'R': 0.73},<br> <br> # Восстановление месяц 3<br> {'C': 0.73, 'T': 0.58, 'V': 0.62, 'P': 0.37, 'D': 0.32, 'R': 0.75},<br> <br> # Полное восстановление<br> {'C': 0.74, 'T': 0.63, 'V': 0.63, 'P': 0.35, 'D': 0.30, 'R': 0.76},<br> ]<br> <br> results = []<br> for i, month in enumerate(months, 1):<br> result = calculator.calculate_index(**month)<br> result['month'] = i<br> results.append(result)<br> <br> print(f"\nМесяц {i}:")<br> print(f" S_KPI = {result['s_kpi']:.1f} {result['zone']}")<br> print(f" Hysteresis effect: {result['hysteresis_effect']:.4f}")<br> <br> # DataFrame для анализа<br> df = pd.DataFrame(results)<br> print("\n" + "="*60)<br> print("СРАВНЕНИЕ С/БЕЗ ГИСТЕРЕЗИСА:")<br> print("="*60)<br> print(df[['month', 's_kpi', 'zone', 'hysteresis_effect']].to_string(index=False))<br> <br> # Визуализация эффекта<br> print("\n" + "="*60)<br> print("ГРАФИК S_KPI (ASCII):")<br> print("="*60)<br> for i, row in df.iterrows():<br> bar = "█" * int(row['s_kpi'] / 2)<br> print(f"M{row['month']:02d}: {bar} {row['s_kpi']:.1f}")<br> ```<br> <br> ---<br> <br> ## 2. SEIZ МОДИФИКАЦИЯ (Trust + Skepticism)<br> <br> ```python<br> import numpy as np<br> from typing import Dict<br> <br> class TrustWithSkepticism:<br> """<br> Расщепление Trust (T) на 2 компонента:<br> - T_loyalty: доверие к национальным СМИ<br> - Z_skepticism: критическое мышление (не верю фейкам)<br> """<br> <br> def __init__(self, w_loyalty: float = 0.6, w_skepticism: float = 0.4):<br> """<br> Args:<br> w_loyalty: вес лояльности (default 0.6)<br> w_skepticism: вес скептицизма (default 0.4)<br> """<br> assert abs(w_loyalty + w_skepticism - 1.0) < 1e-6, "Веса должны суммироваться в 1.0"<br> self.w_loyalty = w_loyalty<br> self.w_skepticism = w_skepticism<br> <br> def calculate_z_skepticism(self, <br> survey_check: float,<br> sentiment_skeptical: float,<br> factcheck_engagement: float) -> float:<br> """<br> Вычисляет Z (skepticism) из 3 источников<br> <br> Args:<br> survey_check: опрос "Как часто проверяете инфо?" (0-1)<br> sentiment_skeptical: % скептических комментариев (0-1)<br> factcheck_engagement: посещаемость fact-checkers (0-1)<br> <br> Returns:<br> Z: skepticism index (0-1)<br> """<br> # Веса для компонентов Z<br> w_survey = 0.4<br> w_sentiment = 0.3<br> w_factcheck = 0.3<br> <br> Z = (w_survey * survey_check + <br> w_sentiment * sentiment_skeptical + <br> w_factcheck * factcheck_engagement)<br> <br> return np.clip(Z, 0.0, 1.0)<br> <br> def calculate_t_composite(self, <br> t_loyalty: float,<br> z_skepticism: float) -> float:<br> """<br> Composite Trust = взвешенная комбинация T и Z<br> <br> Args:<br> t_loyalty: текущий T (доверие к нац. СМИ)<br> z_skepticism: новый Z (критическое мышление)<br> <br> Returns:<br> T_composite: объединённый показатель доверия<br> """<br> t_composite = (self.w_loyalty * t_loyalty + <br> self.w_skepticism * z_skepticism)<br> <br> return t_composite<br> <br> def analyze_strategic_options(self, <br> current_t: float,<br> current_z: float,<br> target_s_kpi: float = 60) -> Dict:<br> """<br> Анализ стратегических опций для достижения цели<br> <br> Args:<br> current_t: текущая лояльность (0-1)<br> current_z: текущий скептицизм (0-1)<br> target_s_kpi: целевой S_KPI<br> <br> Returns:<br> dict: рекомендации по улучшению T или Z<br> """<br> # Текущий composite<br> current_composite = self.calculate_t_composite(current_t, current_z)<br> <br> # Оценка gap<br> # (упрощённо: для S_KPI=60 нужно T_composite ≈ 0.65)<br> required_composite = 0.65 # эмпирическая калибровка<br> gap = required_composite - current_composite<br> <br> if gap <= 0:<br> return {<br> 'status': '✅ Цель достигнута',<br> 'current_composite': current_composite,<br> 'recommendations': []<br> }<br> <br> # Опция 1: Повышать только T (лояльность)<br> required_t = (required_composite - self.w_skepticism * current_z) / self.w_loyalty<br> delta_t = required_t - current_t<br> <br> # Опция 2: Повышать только Z (скептицизм)<br> required_z = (required_composite - self.w_loyalty * current_t) / self.w_skepticism<br> delta_z = required_z - current_z<br> <br> # Опция 3: Balanced (равномерное повышение)<br> balanced_t = current_t + gap * 0.6<br> balanced_z = current_z + gap * 0.4<br> <br> recommendations = []<br> <br> if 0 <= required_t <= 1:<br> recommendations.append({<br> 'strategy': 'Фокус на ЛОЯЛЬНОСТЬ (T)',<br> 'action': f'Повысить T с {current_t:.2f} до {required_t:.2f} (Δ={delta_t:+.2f})',<br> 'cost': 'HIGH (качество контента, transparency)',<br> 'timeline': '12-18 месяцев',<br> 'feasibility': 'LOW' if delta_t > 0.2 else 'MEDIUM'<br> })<br> <br> if 0 <= required_z <= 1:<br> recommendations.append({<br> 'strategy': 'Фокус на СКЕПТИЦИЗМ (Z)',<br> 'action': f'Повысить Z с {current_z:.2f} до {required_z:.2f} (Δ={delta_z:+.2f})',<br> 'cost': 'MEDIUM (медиаграмотность, fact-checking)',<br> 'timeline': '6-12 месяцев',<br> 'feasibility': 'HIGH' if delta_z < 0.3 else 'MEDIUM'<br> })<br> <br> recommendations.append({<br> 'strategy': 'БАЛАНСИРОВАННЫЙ подход',<br> 'action': f'T: {current_t:.2f}→{balanced_t:.2f}, Z: {current_z:.2f}→{balanced_z:.2f}',<br> 'cost': 'MEDIUM-HIGH',<br> 'timeline': '9-15 месяцев',<br> 'feasibility': 'HIGH'<br> })<br> <br> return {<br> 'status': f'⚠️ Gap = {gap:.2f}',<br> 'current_composite': current_composite,<br> 'required_composite': required_composite,<br> 'recommendations': recommendations<br> }<br> <br> <br> # === ПРИМЕР ИСПОЛЬЗОВАНИЯ ===<br> <br> if __name__ == "__main__":<br> trust_calc = TrustWithSkepticism(w_loyalty=0.6, w_skepticism=0.4)<br> <br> # Сценарий: низкая лояльность, высокий скептицизм<br> print("="*60)<br> print("СЦЕНАРИЙ 1: Низкая лояльность + Высокий скептицизм")<br> print("="*60)<br> <br> # Данные для Z<br> z = trust_calc.calculate_z_skepticism(<br> survey_check=0.75, # 75% проверяют информацию<br> sentiment_skeptical=0.80, # 80% скептических комментариев<br> factcheck_engagement=0.70 # 70% engagement с fact-checkers<br> )<br> <br> t_loyalty = 0.45 # низкая лояльность<br> t_composite = trust_calc.calculate_t_composite(t_loyalty, z)<br> <br> print(f"\nT_loyalty (доверие власти): {t_loyalty:.2f}")<br> print(f"Z_skepticism (критич. мышление): {z:.2f}")<br> print(f"T_composite (итоговый Trust): {t_composite:.2f}")<br> print(f"\nЭффект: вместо T={t_loyalty:.2f} (плохо) имеем T_composite={t_composite:.2f} (умеренно)!")<br> <br> # Стратегический анализ<br> print("\n" + "="*60)<br> print("СТРАТЕГИЧЕСКИЕ РЕКОМЕНДАЦИИ:")<br> print("="*60)<br> <br> analysis = trust_calc.analyze_strategic_options(<br> current_t=t_loyalty,<br> current_z=z,<br> target_s_kpi=60<br> )<br> <br> print(f"\nСтатус: {analysis['status']}")<br> print(f"Текущий T_composite: {analysis['current_composite']:.2f}")<br> <br> if 'required_composite' in analysis:<br> print(f"Требуемый T_composite: {analysis['required_composite']:.2f}")<br> print(f"\nВарианты действий:")<br> for i, rec in enumerate(analysis['recommendations'], 1):<br> print(f"\n{i}. {rec['strategy']}")<br> print(f" Действие: {rec['action']}")<br> print(f" Стоимость: {rec['cost']}")<br> print(f" Срок: {rec['timeline']}")<br> print(f" Реализуемость: {rec['feasibility']}")<br> <br> # Сценарий 2: Сравнение<br> print("\n\n" + "="*60)<br> print("СЦЕНАРИЙ 2: Только лояльность vs T+Z")<br> print("="*60)<br> <br> scenarios = [<br> {"name": "Только T (v2.1c)", "t": 0.55, "z": 0.0},<br> {"name": "T + Z (v2.2)", "t": 0.45, "z": 0.75},<br> ]<br> <br> for scenario in scenarios:<br> if scenario['z'] > 0:<br> composite = trust_calc.calculate_t_composite(scenario['t'], scenario['z'])<br> else:<br> composite = scenario['t']<br> <br> print(f"\n{scenario['name']}:")<br> print(f" T={scenario['t']:.2f}, Z={scenario['z']:.2f}")<br> print(f" → Composite Trust = {composite:.2f}")<br> ```<br> <br> ---<br> <br> ## 3. COPULA MONTE CARLO (Корреляции)<br> <br> ```python<br> import numpy as np<br> from scipy.stats import multivariate_normal<br> from typing import Dict, List, Tuple<br> import pandas as pd<br> <br> class CopulaMonteCarloSimulator:<br> """<br> Monte Carlo с учётом корреляций между факторами через Gaussian Copula<br> """<br> <br> def __init__(self, correlation_matrix: np.ndarray):<br> """<br> Args:<br> correlation_matrix: 6x6 корреляционная матрица для [C, T, V, P, D, R]<br> """<br> assert correlation_matrix.shape == (6, 6), "Матрица должна быть 6×6"<br> assert np.allclose(correlation_matrix, correlation_matrix.T), "Матрица должна быть симметричной"<br> <br> self.corr_matrix = correlation_matrix<br> self.factor_names = ['C', 'T', 'V', 'P', 'D', 'R']<br> <br> def sample_with_copula(self, <br> factors: Dict[str, float],<br> sigma: float = 0.05,<br> n_samples: int = 500) -> pd.DataFrame:<br> """<br> Генерирует samples с учётом корреляций<br> <br> Args:<br> factors: текущие значения {C: 0.72, T: 0.68, ...}<br> sigma: стандартное отклонение (5% uncertainty)<br> n_samples: количество симуляций<br> <br> Returns:<br> DataFrame с columns [C, T, V, P, D, R]<br> """<br> # Mean vector<br> mean = np.array([factors[name] for name in self.factor_names])<br> <br> # Covariance matrix (correlations × σ²)<br> cov_matrix = self.corr_matrix * (sigma ** 2)<br> <br> # Sample from multivariate normal<br> samples = multivariate_normal.rvs(<br> mean=mean,<br> cov=cov_matrix,<br> size=n_samples<br> )<br> <br> # Ensure 2D array (scipy returns 1D for n_samples=1)<br> if samples.ndim == 1:<br> samples = samples.reshape(1, -1)<br> <br> # Clip to valid ranges [0, 1]<br> samples = np.clip(samples, 0.01, 1.0)<br> <br> # Return as DataFrame<br> df = pd.DataFrame(samples, columns=self.factor_names)<br> return df<br> <br> def compute_s_kpi_samples(self, <br> samples_df: pd.DataFrame,<br> calculator) -> np.ndarray:<br> """<br> Вычисляет S_KPI для каждого sample<br> <br> Args:<br> samples_df: DataFrame с samples<br> calculator: SGIndexWithHysteresis instance<br> <br> Returns:<br> array of S_KPI values<br> """<br> s_kpi_values = []<br> <br> for _, row in samples_df.iterrows():<br> result = calculator.calculate_index(<br> C=row['C'], T=row['T'], V=row['V'],<br> P=row['P'], D=row['D'], R=row['R']<br> )<br> s_kpi_values.append(result['s_kpi'])<br> <br> return np.array(s_kpi_values)<br> <br> def analyze_uncertainty(self, <br> s_kpi_samples: np.ndarray) -> Dict:<br> """<br> Анализ uncertainty: median, CI, распределение по зонам<br> <br> Args:<br> s_kpi_samples: array of S_KPI values<br> <br> Returns:<br> dict с статистикой<br> """<br> median = np.median(s_kpi_samples)<br> mean = np.mean(s_kpi_samples)<br> std = np.std(s_kpi_samples)<br> <br> # Confidence intervals<br> ci_90 = np.percentile(s_kpi_samples, [5, 95])<br> ci_95 = np.percentile(s_kpi_samples, [2.5, 97.5])<br> <br> # Зоны<br> zone_counts = {<br> '🔴 Critical (<25)': np.sum(s_kpi_samples < 25),<br> '🟡 Warning (25-40)': np.sum((s_kpi_samples >= 25) & (s_kpi_samples < 40)),<br> '🟢 Satisfactory (40-60)': np.sum((s_kpi_samples >= 40) & (s_kpi_samples < 60)),<br> '🔵 Strong (60-100)': np.sum((s_kpi_samples >= 60) & (s_kpi_samples < 100)),<br> '🟣 Exceptional (100)': np.sum(s_kpi_samples >= 100)<br> }<br> <br> zone_probs = {k: v / len(s_kpi_samples) for k, v in zone_counts.items()}<br> <br> return {<br> 'median': median,<br> 'mean': mean,<br> 'std': std,<br> 'ci_90': ci_90,<br> 'ci_95': ci_95,<br> 'ci_95_width': ci_95[1] - ci_95[0],<br> 'zone_probabilities': zone_probs<br> }<br> <br> def compare_independent_vs_copula(self,<br> factors: Dict[str, float],<br> calculator,<br> n_samples: int = 500) -> Dict:<br> """<br> Сравнение независимого MC vs copula MC<br> <br> Returns:<br> dict: сравнение CI widths и зон<br> """<br> # 1. Независимый MC (текущий подход v2.1c)<br> independent_samples = []<br> for _ in range(n_samples):<br> perturbed = {<br> k: np.clip(np.random.normal(v, 0.05), 0.01, 1.0)<br> for k, v in factors.items()<br> }<br> result = calculator.calculate_index(**perturbed)<br> independent_samples.append(result['s_kpi'])<br> <br> independent_samples = np.array(independent_samples)<br> <br> # 2. Copula MC (новый подход v2.2)<br> copula_samples_df = self.sample_with_copula(factors, sigma=0.05, n_samples=n_samples)<br> copula_s_kpi = self.compute_s_kpi_samples(copula_samples_df, calculator)<br> <br> # Анализ<br> independent_stats = self.analyze_uncertainty(independent_samples)<br> copula_stats = self.analyze_uncertainty(copula_s_kpi)<br> <br> return {<br> 'independent': independent_stats,<br> 'copula': copula_stats,<br> 'ci_width_ratio': copula_stats['ci_95_width'] / independent_stats['ci_95_width']<br> }<br> <br> <br> # === ПРИМЕР ИСПОЛЬЗОВАНИЯ ===<br> <br> if __name__ == "__main__":<br> # Correlation matrix (эмпирическая/expert-based)<br> # Порядок: C, T, V, P, D, R<br> corr_matrix = np.array([<br> # C T V P D R<br> [1.00, 0.60, 0.70, 0.10,-0.20, 0.40], # C<br> [0.60, 1.00, 0.50, 0.05,-0.60, 0.50], # T<br> [0.70, 0.50, 1.00, 0.15,-0.30, 0.35], # V<br> [0.10, 0.05, 0.15, 1.00, 0.80,-0.40], # P<br> [-0.20,-0.60,-0.30, 0.80, 1.00,-0.70], # D<br> [0.40, 0.50, 0.35,-0.40,-0.70, 1.00] # R<br> ])<br> <br> # Создаём симулятор<br> simulator = CopulaMonteCarloSimulator(corr_matrix)<br> <br> # Текущее состояние<br> factors = {<br> 'C': 0.72,<br> 'T': 0.68,<br> 'V': 0.65,<br> 'P': 0.42,<br> 'D': 0.35,<br> 'R': 0.78<br> }<br> <br> print("="*60)<br> print("СРАВНЕНИЕ: Независимый MC vs Copula MC")<br> print("="*60)<br> <br> # Калькулятор (без гистерезиса для простоты)<br> from __main__ import SGIndexWithHysteresis # type: ignore<br> calculator = SGIndexWithHysteresis(eta=0.0) # no hysteresis for this demo<br> <br> # Сравнение<br> comparison = simulator.compare_independent_vs_copula(<br> factors=factors,<br> calculator=calculator,<br> n_samples=500<br> )<br> <br> # Результаты<br> print("\n1. НЕЗАВИСИМЫЙ MC (v2.1c):")<br> ind = comparison['independent']<br> print(f" Median S_KPI: {ind['median']:.1f}")<br> print(f" 95% CI: [{ind['ci_95'][0]:.1f}, {ind['ci_95'][1]:.1f}]")<br> print(f" CI width: {ind['ci_95_width']:.1f}")<br> <br> print("\n2. COPULA MC (v2.2):")<br> cop = comparison['copula']<br> print(f" Median S_KPI: {cop['median']:.1f}")<br> print(f" 95% CI: [{cop['ci_95'][0]:.1f}, {cop['ci_95'][1]:.1f}]")<br> print(f" CI width: {cop['ci_95_width']:.1f}")<br> <br> print(f"\n3. ЭФФЕКТ:")<br> print(f" CI width ratio: {comparison['ci_width_ratio']:.2f}x")<br> print(f" → Copula CI на {(comparison['ci_width_ratio']-1)*100:.0f}% шире (более реалистичный)")<br> <br> # Зоны вероятности<br> print("\n4. ВЕРОЯТНОСТИ ЗОН:")<br> print("\n Независимый MC:")<br> for zone, prob in ind['zone_probabilities'].items():<br> if prob > 0:<br> bar = "█" * int(prob * 50)<br> print(f" {zone}: {prob*100:.1f}% {bar}")<br> <br> print("\n Copula MC:")<br> for zone, prob in cop['zone_probabilities'].items():<br> if prob > 0:<br> bar = "█" * int(prob * 50)<br> print(f" {zone}: {prob*100:.1f}% {bar}")<br> <br> print("\n" + "="*60)<br> print("ВЫВОД: Copula MC учитывает, что:")<br> print(" - Высокий P обычно идёт вместе с высоким D (корреляция +0.8)")<br> print(" - Высокий R снижает эффект D (корреляция -0.7)")<br> print(" → Более реалистичная оценка uncertainty!")<br> print("="*60)<br> ```<br> <br> ---<br> <br> ## 4. INTEGRATION EXAMPLE: Полный pipeline v2.2<br> <br> ```python<br> """<br> Полный pipeline S-G Index v2.2 со всеми 7 улучшениями<br> """<br> <br> import numpy as np<br> import pandas as pd<br> from datetime import datetime<br> from typing import Dict, List<br> <br> class SGIndexV22:<br> """<br> S-G Index v2.2 — полная интеграция всех 7 улучшений<br> <br> 1. Hysteresis ✅<br> 2. SEIZ (T + Z) ✅<br> 3. Volatility penalty ✅<br> 4. Copula MC ✅<br> 5. Synergy C×T ✅<br> 6. R decomposition ✅<br> 7. EUvsDisinfo taxonomy ✅<br> """<br> <br> def __init__(self):<br> # Sub-components<br> self.hysteresis_calc = SGIndexWithHysteresis(eta=0.3)<br> self.trust_calc = TrustWithSkepticism(w_loyalty=0.6, w_skepticism=0.4)<br> <br> # Correlation matrix для Copula MC<br> self.corr_matrix = np.array([<br> [1.00, 0.60, 0.70, 0.10,-0.20, 0.40],<br> [0.60, 1.00, 0.50, 0.05,-0.60, 0.50],<br> [0.70, 0.50, 1.00, 0.15,-0.30, 0.35],<br> [0.10, 0.05, 0.15, 1.00, 0.80,-0.40],<br> [-0.20,-0.60,-0.30, 0.80, 1.00,-0.70],<br> [0.40, 0.50, 0.35,-0.40,-0.70, 1.00]<br> ])<br> <br> self.copula_sim = CopulaMonteCarloSimulator(self.corr_matrix)<br> <br> # История для volatility<br> self.s_kpi_history: List[float] = []<br> <br> def calculate_full_index(self, <br> inputs: Dict,<br> n_mc_samples: int = 500) -> Dict:<br> """<br> Полный расчёт с учётом всех 7 улучшений<br> <br> Args:<br> inputs: {<br> # Level 1: Core<br> 'C': 0.72,<br> 'T_loyalty': 0.68, # теперь отдельно<br> 'V': 0.65,<br> <br> # Level 2: Load<br> 'P': 0.42,<br> 'D': 0.35,<br> 'R_prep': 0.80, # теперь 3 субиндекса<br> 'R_resp': 0.75,<br> 'R_recov': 0.78,<br> <br> # Новые для SEIZ<br> 'Z_survey': 0.75,<br> 'Z_sentiment': 0.70,<br> 'Z_factcheck': 0.68,<br> }<br> n_mc_samples: количество MC симуляций<br> <br> Returns:<br> dict с полными результатами<br> """<br> # 1. SEIZ: вычисляем Z и T_composite<br> Z = self.trust_calc.calculate_z_skepticism(<br> inputs['Z_survey'],<br> inputs['Z_sentiment'],<br> inputs['Z_factcheck']<br> )<br> <br> T_composite = self.trust_calc.calculate_t_composite(<br> inputs['T_loyalty'],<br> Z<br> )<br> <br> # 2. R decomposition: geometric mean<br> R = (inputs['R_prep'] * inputs['R_resp'] * inputs['R_recov']) ** (1/3)<br> <br> # 3. Core calculation (с synergy C×T)<br> C, V, P, D = inputs['C'], inputs['V'], inputs['P'], inputs['D']<br> <br> # S_pot с synergy<br> epsilon = 0.5 # synergy coefficient<br> s_pot_base = (C ** 0.25) * (T_composite ** 0.40) * (V ** 0.35)<br> s_pot = s_pot_base * (1 + epsilon * C * T_composite)<br> <br> # F_lin, F_soft<br> theta, P_scale, k = 0.85, 0.45, 10.0<br> f_lin = max(1.0 - theta * max(P - C, 0) / P_scale, 0.0)<br> f_soft = 1.0 / (1.0 + np.exp(k * (D - R)))<br> <br> s_raw = s_pot * f_lin * f_soft<br> <br> # 4. Hysteresis<br> s_raw_adjusted = self.hysteresis_calc.apply_hysteresis(s_raw)<br> s_kpi_base = self.hysteresis_calc.map_to_kpi(s_raw_adjusted)<br> <br> # 5. Volatility penalty<br> self.s_kpi_history.append(s_kpi_base)<br> if len(self.s_kpi_history) >= 3:<br> sigma_s = np.std(self.s_kpi_history[-3:])<br> f_vol = 1.0 / (1.0 + 1.0 * sigma_s) # μ=1.0<br> else:<br> f_vol = 1.0 # не хватает истории<br> <br> s_kpi_final = s_kpi_base * f_vol<br> <br> # 6. Copula MC для uncertainty<br> factors_for_mc = {<br> 'C': C,<br> 'T': T_composite,<br> 'V': V,<br> 'P': P,<br> 'D': D,<br> 'R': R<br> }<br> <br> samples_df = self.copula_sim.sample_with_copula(<br> factors_for_mc,<br> sigma=0.05,<br> n_samples=n_mc_samples<br> )<br> <br> # Для каждого sample считаем S_KPI (упрощённо, без hysteresis)<br> s_kpi_samples = []<br> for _, row in samples_df.iterrows():<br> s_p = (row['C']**0.25) * (row['T']**0.40) * (row['V']**0.35)<br> s_p *= (1 + epsilon * row['C'] * row['T'])<br> f_l = max(1.0 - theta * max(row['P'] - row['C'], 0) / P_scale, 0.0)<br> f_s = 1.0 / (1.0 + np.exp(k * (row['D'] - row['R'])))<br> s_r = s_p * f_l * f_s<br> s_k = self.hysteresis_calc.map_to_kpi(s_r)<br> s_kpi_samples.append(s_k * f_vol) # apply volatility<br> <br> s_kpi_samples = np.array(s_kpi_samples)<br> <br> uncertainty_stats = self.copula_sim.analyze_uncertainty(s_kpi_samples)<br> <br> # Zone<br> if s_kpi_final < 25:<br> zone = "🔴 Critical"<br> elif s_kpi_final < 40:<br> zone = "🟡 Warning"<br> elif s_kpi_final < 60:<br> zone = "🟢 Satisfactory"<br> elif s_kpi_final < 100:<br> zone = "🔵 Strong"<br> else:<br> zone = "🟣 Exceptional"<br> <br> return {<br> # Inputs (processed)<br> 'inputs': {<br> 'C': C,<br> 'T_loyalty': inputs['T_loyalty'],<br> 'Z_skepticism': Z,<br> 'T_composite': T_composite,<br> 'V': V,<br> 'P': P,<br> 'D': D,<br> 'R_prep': inputs['R_prep'],<br> 'R_resp': inputs['R_resp'],<br> 'R_recov': inputs['R_recov'],<br> 'R_composite': R<br> },<br> <br> # Core calculations<br> 's_pot_base': s_pot_base,<br> 's_pot_with_synergy': s_pot,<br> 'synergy_effect': s_pot - s_pot_base,<br> 's_raw': s_raw,<br> 's_raw_adjusted': s_raw_adjusted,<br> 'hysteresis_effect': s_raw - s_raw_adjusted,<br> <br> # Final output<br> 's_kpi_base': s_kpi_base,<br> 'f_vol': f_vol,<br> 'volatility_penalty': 1 - f_vol,<br> 's_kpi_final': s_kpi_final,<br> 'zone': zone,<br> <br> # Uncertainty<br> 'uncertainty': uncertainty_stats,<br> <br> # Diagnostics<br> 'diagnostics': {<br> 'f_lin': f_lin,<br> 'f_soft': f_soft,<br> 'load': D - R,<br> 'exposure': max(P - C, 0),<br> 'volatility_3m': sigma_s if len(self.s_kpi_history) >= 3 else None<br> }<br> }<br> <br> <br> # === DEMO ===<br> if __name__ == "__main__":<br> print("="*70)<br> print("S-G INDEX v2.2 — ПОЛНЫЙ PIPELINE DEMO")<br> print("="*70)<br> <br> calculator = SGIndexV22()<br> <br> # Месячные данные<br> monthly_data = [<br> # Месяц 1: нормальное состояние<br> {<br> 'C': 0.72, 'T_loyalty': 0.68, 'V': 0.65,<br> 'P': 0.35, 'D': 0.30,<br> 'R_prep': 0.82, 'R_resp': 0.75, 'R_recov': 0.78,<br> 'Z_survey': 0.70, 'Z_sentiment': 0.68, 'Z_factcheck': 0.65<br> },<br> <br> # Месяц 2: КРИЗИС!<br> {<br> 'C': 0.70, 'T_loyalty': 0.45, 'V': 0.55,<br> 'P': 0.55, 'D': 0.85,<br> 'R_prep': 0.75, 'R_resp': 0.48, 'R_recov': 0.60,<br> 'Z_survey': 0.75, 'Z_sentiment': 0.80, 'Z_factcheck': 0.78<br> },<br> <br> # Месяц 3: восстановление<br> {<br> 'C': 0.73, 'T_loyalty': 0.52, 'V': 0.60,<br> 'P': 0.42, 'D': 0.50,<br> 'R_prep': 0.80, 'R_resp': 0.68, 'R_recov': 0.70,<br> 'Z_survey': 0.78, 'Z_sentiment': 0.82, 'Z_factcheck': 0.80<br> },<br> ]<br> <br> results = []<br> for month_num, inputs in enumerate(monthly_data, 1):<br> print(f"\n{'='*70}")<br> print(f"МЕСЯЦ {month_num}")<br> print('='*70)<br> <br> result = calculator.calculate_full_index(inputs, n_mc_samples=500)<br> result['month'] = month_num<br> results.append(result)<br> <br> # Output<br> inp = result['inputs']<br> print(f"\n📊 INPUTS:")<br> print(f" Core: C={inp['C']:.2f}, T_loyalty={inp['T_loyalty']:.2f}, Z={inp['Z_skepticism']:.2f}")<br> print(f" → T_composite={inp['T_composite']:.2f}")<br> print(f" V={inp['V']:.2f}")<br> print(f" Load: P={inp['P']:.2f}, D={result['inputs']['D']:.2f}")<br> print(f" Resilience: Prep={inp['R_prep']:.2f}, Resp={inp['R_resp']:.2f}, Recov={inp['R_recov']:.2f}")<br> print(f" → R_composite={inp['R_composite']:.2f}")<br> <br> print(f"\n🔬 CALCULATIONS:")<br> print(f" S_pot (base): {result['s_pot_base']:.4f}")<br> print(f" + Synergy C×T: {result['synergy_effect']:+.4f} → {result['s_pot_with_synergy']:.4f}")<br> print(f" × F_lin: {result['diagnostics']['f_lin']:.4f}")<br> print(f" × F_soft: {result['diagnostics']['f_soft']:.4f}")<br> print(f" = S_raw: {result['s_raw']:.4f}")<br> print(f" - Hysteresis: {result['hysteresis_effect']:.4f} → {result['s_raw_adjusted']:.4f}")<br> print(f" → S_KPI (base): {result['s_kpi_base']:.1f}")<br> <br> diag = result['diagnostics']<br> if diag['volatility_3m'] is not None:<br> print(f" Volatility (3m σ): {diag['volatility_3m']:.2f}")<br> print(f" × F_vol: {result['f_vol']:.4f} (penalty: {result['volatility_penalty']*100:.1f}%)")<br> <br> print(f"\n🎯 FINAL:")<br> print(f" S_KPI = {result['s_kpi_final']:.1f} {result['zone']}")<br> <br> unc = result['uncertainty']<br> print(f"\n📈 UNCERTAINTY (MC n=500):")<br> print(f" Median: {unc['median']:.1f}")<br> print(f" 95% CI: [{unc['ci_95'][0]:.1f}, {unc['ci_95'][1]:.1f}] (width={unc['ci_95_width']:.1f})")<br> print(f" Zone probabilities:")<br> for zone, prob in unc['zone_probabilities'].items():<br> if prob > 0.01:<br> bar = "█" * int(prob * 30)<br> print(f" {zone}: {prob*100:.1f}% {bar}")<br> <br> print("\n" + "="*70)<br> print("✅ v2.2 DEMO COMPLETE")<br> print("="*70)<br> ```<br> <br> ---<br> <br> ## 5. UNIT TESTS<br> <br> ```python<br> import unittest<br> import numpy as np<br> <br> class TestSGIndexV22(unittest.TestCase):<br> """Unit tests для v2.2 компонентов"""<br> <br> def setUp(self):<br> self.calculator = SGIndexWithHysteresis(eta=0.3)<br> self.trust_calc = TrustWithSkepticism()<br> <br> def test_hysteresis_no_effect_on_decline(self):<br> """Гистерезис не должен влиять при падении"""<br> # First calculation<br> result1 = self.calculator.calculate_index(<br> C=0.7, T=0.6, V=0.6, P=0.4, D=0.3, R=0.7<br> )<br> <br> # Second: worse (decline)<br> result2 = self.calculator.calculate_index(<br> C=0.7, T=0.5, V=0.6, P=0.5, D=0.6, R=0.7<br> )<br> <br> # No hysteresis on decline<br> self.assertLess(result2['s_kpi'], result1['s_kpi'])<br> self.assertAlmostEqual(result2['hysteresis_effect'], 0.0, places=4)<br> <br> def test_hysteresis_slows_recovery(self):<br> """Гистерезис замедляет восстановление"""<br> # Decline<br> self.calculator.calculate_index(<br> C=0.7, T=0.4, V=0.5, P=0.6, D=0.8, R=0.6<br> )<br> <br> # Recovery attempt<br> result = self.calculator.calculate_index(<br> C=0.7, T=0.6, V=0.6, P=0.4, D=0.3, R=0.7<br> )<br> <br> # Should have hysteresis effect<br> self.assertGreater(result['hysteresis_effect'], 0)<br> <br> def test_trust_composite_bounded(self):<br> """T_composite должен быть в [0, 1]"""<br> for t in [0.0, 0.5, 1.0]:<br> for z in [0.0, 0.5, 1.0]:<br> composite = self.trust_calc.calculate_t_composite(t, z)<br> self.assertGreaterEqual(composite, 0.0)<br> self.assertLessEqual(composite, 1.0)<br> <br> def test_z_calculation(self):<br> """Z должен быть взвешенной комбинацией 3 источников"""<br> z = self.trust_calc.calculate_z_skepticism(<br> survey_check=1.0,<br> sentiment_skeptical=0.5,<br> factcheck_engagement=0.5<br> )<br> <br> expected = 0.4 * 1.0 + 0.3 * 0.5 + 0.3 * 0.5<br> self.assertAlmostEqual(z, expected, places=4)<br> <br> def test_copula_samples_valid(self):<br> """Copula samples должны быть в valid ranges"""<br> corr_matrix = np.eye(6) # independent для теста<br> simulator = CopulaMonteCarloSimulator(corr_matrix)<br> <br> factors = {'C': 0.7, 'T': 0.6, 'V': 0.6, 'P': 0.4, 'D': 0.3, 'R': 0.7}<br> samples = simulator.sample_with_copula(factors, n_samples=100)<br> <br> # Check ranges<br> self.assertTrue((samples >= 0.01).all().all())<br> self.assertTrue((samples <= 1.0).all().all())<br> <br> # Check means close to inputs<br> for col in samples.columns:<br> mean = samples[col].mean()<br> self.assertAlmostEqual(mean, factors[col], delta=0.1)<br> <br> <br> if __name__ == '__main__':<br> unittest.main()<br> ```<br> <br> ---<br> <br> ## 📦 PACKAGE STRUCTURE<br> <br> ```<br> sg_index_v22/<br> ├── __init__.py<br> ├── core/<br> │ ├── __init__.py<br> │ ├── hysteresis.py # SGIndexWithHysteresis<br> │ ├── trust.py # TrustWithSkepticism<br> │ ├── copula.py # CopulaMonteCarloSimulator<br> │ └── calculator.py # SGIndexV22 (main)<br> ├── utils/<br> │ ├── __init__.py<br> │ ├── visualization.py # Plotting functions<br> │ └── validation.py # Data validation<br> ├── tests/<br> │ ├── __init__.py<br> │ ├── test_hysteresis.py<br> │ ├── test_trust.py<br> │ └── test_copula.py<br> ├── requirements.txt<br> └── README.md<br> ```<br> <br> **requirements.txt:**<br> ```<br> numpy>=1.24.0<br> scipy>=1.10.0<br> pandas>=2.0.0<br> matplotlib>=3.7.0 # for visualization<br> ```<br> <br> ---<br> <br> ## 🚀 NEXT STEPS<br> <br> 1. **Copy код** в production environment<br> 2. **Run unit tests**: `python -m unittest discover tests/`<br> 3. **Calibrate parameters** на historical data<br> 4. **Integrate** в dashboard/reporting<br> 5. **Monitor** performance в production<br> <br> **Questions:** [Ваш email] <br> **Last updated:** January 8, 2026<br>