Skip to content

Commit 1e00256

Browse files
author
marce
committed
SPEC-030: Evolutionary Trajectories Scanner — 5 modulos (Noological + Teleological + CrossVal + Polymathic + Trajectory) — 16/16 CTs (100% pass)
1 parent 00d309e commit 1e00256

4 files changed

Lines changed: 1319 additions & 0 deletions

File tree

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
CrossValidationEngine v1.0 — Validação Cruzada Evolutiva (Módulo 3)
5+
6+
Identifica dependências ocultas entre capacidades e modela efeitos cascata.
7+
8+
Regras de inferência:
9+
R1 (Prerequisite): Se A requer B e B está ausente → A é inviável
10+
R2 (Cascade): Se A habilita B,C,D e A está ausente → B,C,D em risco
11+
R3 (Co-occurrence): A e B aparecem juntos em >80% dos sistemas → alta afinidade
12+
R4 (Bottleneck): Se A é prerequisite de >3 capacidades → bottleneck crítico
13+
"""
14+
15+
from __future__ import annotations
16+
17+
from dataclasses import dataclass, field
18+
from typing import Any
19+
20+
21+
@dataclass
22+
class CapabilityNode:
23+
"""Nó no grafo de dependências entre capacidades."""
24+
name: str
25+
domain: str
26+
category: str
27+
provides: list[str] = field(default_factory=list) # capacidades que habilita
28+
requires: list[str] = field(default_factory=list) # capacidades das quais depende
29+
influence_score: float = 0.0
30+
cascade_impact: float = 0.0
31+
32+
33+
@dataclass
34+
class DependencyEdge:
35+
"""Aresta direcionada no grafo de dependências."""
36+
source: str # "raciocinio.probabilistico"
37+
target: str # "metodos.quantitativo_experimental"
38+
weight: float # força da dependência (0-1)
39+
relation: str # "requires" | "enables" | "co_occurs"
40+
41+
42+
# ═══════════════════════════════════════════════════════════════════════════
43+
# REGRAS DE DEPENDÊNCIA ENTRE CAPACIDADES
44+
# ═══════════════════════════════════════════════════════════════════════════
45+
46+
# (source_domain.category, target_domain.category, weight, relation)
47+
DEPENDENCY_RULES: list[tuple[str, str, float, str]] = [
48+
# ─── Prerequisites (requires) ────────────────────────────────────────
49+
("metodos.Quantitativo experimental", "raciocinio.Probabilístico", 0.8, "requires"),
50+
("metodos.Quantitativo experimental", "raciocinio.Dedutivo", 0.7, "requires"),
51+
("metodos.Quantitativo correlacional", "raciocinio.Probabilístico", 0.6, "requires"),
52+
("metodos.Meta-análise", "raciocinio.Probabilístico", 0.9, "requires"),
53+
("metodos.Meta-análise", "dados.Metadados (revisões)", 0.8, "requires"),
54+
("metodos.Revisão sistemática", "dados.Metadados (revisões)", 0.7, "requires"),
55+
("dados.Dados longitudinais", "temporalidade.Longitudinal (longo prazo)", 0.9, "requires"),
56+
("dados.Dados epidemiológicos", "populacao.Contexto clínico", 0.7, "requires"),
57+
("dados.Dados comparativos (cross-cultural)", "populacao.Cross-cultural", 0.8, "requires"),
58+
("raciocinio.Contrafactual", "raciocinio.Probabilístico", 0.5, "requires"),
59+
("raciocinio.Probabilístico", "raciocinio.Dedutivo", 0.5, "requires"),
60+
61+
# ─── Enablers (habilita) ────────────────────────────────────────────
62+
("raciocinio.Probabilístico", "metodos.Meta-análise", 0.9, "enables"),
63+
("raciocinio.Probabilístico", "raciocinio.Bayesiano", 0.8, "enables"),
64+
("raciocinio.Probabilístico", "raciocinio.Contrafactual", 0.6, "enables"),
65+
("raciocinio.Sistêmico", "niveis_analise.Sistêmico/político", 0.7, "enables"),
66+
("raciocinio.Sistêmico", "dominios.Neurociências", 0.5, "enables"),
67+
("temporalidade.Longitudinal (longo prazo)", "dados.Dados longitudinais", 0.9, "enables"),
68+
("temporalidade.Longitudinal (longo prazo)", "raciocinio.Probabilístico", 0.6, "enables"),
69+
("paradigmas.Complexo/Sistêmico", "raciocinio.Sistêmico", 0.8, "enables"),
70+
("paradigmas.Complexo/Sistêmico", "dominios.Sociologia", 0.5, "enables"),
71+
("paradigmas.Pragmatista", "metodos.Misto sequencial", 0.7, "enables"),
72+
("paradigmas.Pragmatista", "metodos.Misto convergente", 0.7, "enables"),
73+
("teoria_jogos.Equilíbrio de Nash", "raciocinio.Probabilístico", 0.6, "enables"),
74+
("teoria_jogos.Equilíbrio de Nash", "raciocinio.Contrafactual", 0.5, "enables"),
75+
("teoria_jogos.Cooperativo", "niveis_analise.Sistêmico/político", 0.5, "enables"),
76+
77+
# ─── Co-occurrence (alta afinidade) ─────────────────────────────────
78+
("paradigmas.Fenomenológico", "metodos.Qualitativo fenomenológico", 0.95, "co_occurs"),
79+
("paradigmas.Fenomenológico", "dados.Dados qualitativos (entrevistas)", 0.90, "co_occurs"),
80+
("paradigmas.Positivista", "metodos.Quantitativo experimental", 0.90, "co_occurs"),
81+
("paradigmas.Positivista", "raciocinio.Dedutivo", 0.80, "co_occurs"),
82+
("paradigmas.Construtivista", "metodos.Qualitativo grounded theory", 0.85, "co_occurs"),
83+
("raciocinio.Indutivo", "metodos.Qualitativo grounded theory", 0.80, "co_occurs"),
84+
("raciocinio.Abdutivo", "paradigmas.Pragmatista", 0.75, "co_occurs"),
85+
("teoria_jogos.Bayesiano", "raciocinio.Probabilístico", 0.85, "co_occurs"),
86+
("teoria_jogos.Evolutivo", "paradigmas.Complexo/Sistêmico", 0.75, "co_occurs"),
87+
("dominios.Neurociências", "dados.Dados neurobiológicos", 0.90, "co_occurs"),
88+
("dominios.Antropologia", "metodos.Qualitativo fenomenológico", 0.80, "co_occurs"),
89+
("dominios.Economia comportamental", "teoria_jogos.Bayesiano", 0.75, "co_occurs"),
90+
]
91+
92+
93+
# ═══════════════════════════════════════════════════════════════════════════
94+
# ENGINE
95+
# ═══════════════════════════════════════════════════════════════════════════
96+
97+
class CrossValidationEngine:
98+
"""Motor de validação cruzada entre capacidades do ecossistema.
99+
100+
Constrói um grafo de dependências a partir de regras pré-definidas
101+
e do scan noológico, identificando bottlenecks, efeitos cascata
102+
e afinidades estruturais.
103+
"""
104+
105+
def __init__(self):
106+
self.nodes: dict[str, CapabilityNode] = {}
107+
self.edges: list[DependencyEdge] = []
108+
self._node_key = lambda d, c: f"{d}.{c}"
109+
110+
# ─── GRAPH CONSTRUCTION ──────────────────────────────────────────────
111+
112+
def build_graph(self, noological_scan: dict[str, Any]) -> dict[str, CapabilityNode]:
113+
"""Constrói grafo de dependências a partir do scan noológico.
114+
115+
Cria nós para todas as 92 categorias e arestas a partir
116+
das DEPENDENCY_RULES.
117+
"""
118+
self.nodes = {}
119+
self.edges = []
120+
121+
dims = noological_scan.get("dimensions", {})
122+
for dim_key, dim_data in dims.items():
123+
all_cats = dim_data.get("covered", []) + dim_data.get("absent", [])
124+
for cat in all_cats:
125+
node_key = self._node_key(dim_key, cat)
126+
self.nodes[node_key] = CapabilityNode(
127+
name=cat,
128+
domain=dim_key,
129+
category=cat,
130+
)
131+
132+
# Aplicar regras de dependência
133+
for src_key, tgt_key, weight, relation in DEPENDENCY_RULES:
134+
if src_key in self.nodes and tgt_key in self.nodes:
135+
self.edges.append(DependencyEdge(
136+
source=src_key, target=tgt_key,
137+
weight=weight, relation=relation,
138+
))
139+
src_node = self.nodes[src_key]
140+
tgt_node = self.nodes[tgt_key]
141+
if relation == "requires":
142+
src_node.requires.append(tgt_key)
143+
elif relation == "enables":
144+
src_node.provides.append(tgt_key)
145+
# co_occurs não altera provides/requires
146+
147+
return self.nodes
148+
149+
# ─── BOTTLENECK DETECTION ────────────────────────────────────────────
150+
151+
def find_bottlenecks(self, min_dependents: int = 3) -> list[CapabilityNode]:
152+
"""Identifica bottlenecks: nós que habilitam (provides/requires) muitas outras capacidades.
153+
154+
Um nó é bottleneck se:
155+
- Habilita (provides) >= min_dependents outros nós, OU
156+
- É prerequisite (outros requires apontam para ele) >= min_dependents
157+
"""
158+
# Contar quantos nós cada nó habilita (provides)
159+
provides_count: dict[str, int] = {}
160+
# Contar quantos nós dependem de cada nó (reverse requires)
161+
depended_by_count: dict[str, int] = {}
162+
163+
for node_key, node in self.nodes.items():
164+
provides_count[node_key] = len(node.provides)
165+
depended_by_count[node_key] = 0
166+
167+
for edge in self.edges:
168+
if edge.relation == "requires":
169+
depended_by_count[edge.target] = depended_by_count.get(edge.target, 0) + 1
170+
171+
bottlenecks = []
172+
for node_key, node in self.nodes.items():
173+
total_influence = provides_count.get(node_key, 0) + depended_by_count.get(node_key, 0)
174+
if total_influence >= min_dependents:
175+
node.influence_score = min(1.0, total_influence / 10.0)
176+
bottlenecks.append(node)
177+
178+
# Ordenar por influence_score decrescente
179+
bottlenecks.sort(key=lambda n: n.influence_score, reverse=True)
180+
return bottlenecks
181+
182+
# ─── CASCADE IMPACT ──────────────────────────────────────────────────
183+
184+
def cascade_impact(self, noological_scan: dict[str, Any]) -> dict[str, float]:
185+
"""Calcula impacto em cascata: se categoria X está ausente, quantas outras são afetadas.
186+
187+
Retorna dict: {node_key: cascade_score} onde cascade_score é o número
188+
de capacidades que dependem direta ou indiretamente desta, ponderado
189+
pelos pesos das arestas.
190+
"""
191+
dims = noological_scan.get("dimensions", {})
192+
cascade: dict[str, float] = {}
193+
194+
for node_key, node in self.nodes.items():
195+
# Verificar se esta categoria está ausente no scan
196+
dim_data = dims.get(node.domain, {})
197+
absent_cats = dim_data.get("absent", [])
198+
if node.category not in absent_cats:
199+
continue # só calcular impacto para categorias ausentes
200+
201+
# Impacto direto: soma dos pesos das arestas "enables"
202+
direct = sum(
203+
e.weight for e in self.edges
204+
if e.source == node_key and e.relation == "enables"
205+
)
206+
# Impacto reverso: quantos nós "require" este
207+
reverse = sum(
208+
e.weight for e in self.edges
209+
if e.target == node_key and e.relation == "requires"
210+
)
211+
cascade[node_key] = round(direct + reverse, 2)
212+
213+
return cascade
214+
215+
# ─── CO-OCCURRENCE ───────────────────────────────────────────────────
216+
217+
def co_occurrence_matrix(self) -> dict[tuple[str, str], float]:
218+
"""Calcula matriz de co-ocorrência entre pares de categorias.
219+
220+
Retorna: {(cat_a, cat_b): affinity_score}
221+
"""
222+
matrix: dict[tuple[str, str], float] = {}
223+
for edge in self.edges:
224+
if edge.relation == "co_occurs":
225+
items = sorted([edge.source, edge.target])
226+
key: tuple[str, str] = (items[0], items[1])
227+
matrix[key] = max(matrix.get(key, 0), edge.weight)
228+
return matrix
229+
230+
# ─── DETECT CYCLES ───────────────────────────────────────────────────
231+
232+
def detect_cycles(self) -> list[list[str]]:
233+
"""Detecta ciclos no grafo de dependências (A→B→A).
234+
235+
Retorna lista de ciclos encontrados.
236+
"""
237+
cycles = []
238+
for edge_a in self.edges:
239+
if edge_a.relation not in ("requires", "enables"):
240+
continue
241+
for edge_b in self.edges:
242+
if edge_b.relation not in ("requires", "enables"):
243+
continue
244+
if edge_a.source == edge_b.target and edge_a.target == edge_b.source:
245+
cycle = [edge_a.source, edge_a.target]
246+
if cycle not in cycles and list(reversed(cycle)) not in cycles:
247+
cycles.append(cycle)
248+
return cycles

0 commit comments

Comments
 (0)