paste.txt
Сущности
# 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>