"""Modo de ensaio determinístico: simula 15 sistemas sem chamar API real.""" from __future__ import annotations import hashlib import re from providers import detectar_recusa _LETRAS = "ABCD" _PREENCHER = { "pt": "Análise: considerando o enunciado e o contexto técnico apresentado, a resposta consistente é a que segue.", "en": "Analysis: given the statement and the technical context, the consistent answer is the following.", "zh": "分析:结合题干与技术背景,一致的答案如下。", } def _u64(*partes: object) -> int: return int(hashlib.sha256("|".join(str(p) for p in partes).encode()).hexdigest()[:16], 16) class MockProvider: """Gera resposta determinística a partir de um perfil de qualidade.""" def __init__(self, cfg: dict): self.nome = cfg["nome"] self.model = cfg.get("model", "mock") self.q = float(cfg.get("mock", {}).get("q", 0.8)) self.lang = cfg.get("mock", {}).get("lang", {}) self.perfil = cfg.get("perfil", "") # -- utilidades ------------------------------------------------------- def _eficacia(self, lang: str) -> float: fator = float(self.lang.get(lang, 0.8)) if self.lang else 0.9 return max(0.05, min(0.99, self.q * fator)) def _unico(self, *partes: object) -> float: return _u64(self.nome, *partes) / float(1 << 64) # -- API compatível --------------------------------------------------- def completa(self, system: str, user: str, lang: str, item: dict | None = None) -> dict: if item is None: return { "text": _PREENCHER.get(lang, _PREENCHER["en"]), "latency_ms": 120.0, "prompt_tokens": len(user.split()), "completion_tokens": 40, "refused": False, "raw_model": self.model, } texto = self._responder(item, lang) return { "text": texto, "latency_ms": round(150 + 900 * (1 - self._eficacia(lang)), 1), "prompt_tokens": len(str(item.get("q", "")).split()) + 40, "completion_tokens": len(texto.split()), "refused": detectar_recusa(texto), "raw_model": self.model, } # -- geração ---------------------------------------------------------- def _responder(self, item: dict, lang: str) -> str: eff = self._eficacia(lang) tipo = item["type"] sorte = self._unico(item["id"], lang, tipo) if tipo == "mcq": return self._mcq(item, eff, sorte) if tipo == "calc": return self._calc(item, eff, sorte) return self._aberto(item, lang, eff, sorte) def _mcq(self, item: dict, eff: float, sorte: float) -> str: correta = item["a"] if sorte < eff: escolha = correta else: alternativas = [i for i in range(len(item["opts"])) if i != correta] escolha = alternativas[int(sorte * 1000) % len(alternativas)] letra = _LETRAS[escolha] texto = item["opts"][escolha] return f"{letra}) {texto}" def _calc(self, item: dict, eff: float, sorte: float) -> str: alvo = float(item["val"]) if sorte < eff: valor = alvo # acerto else: amplitude = 0.40 * (1 - eff) + 0.06 ruido = (sorte * 2 - 1) * amplitude valor = alvo * (1 + ruido) if abs(alvo) > 1e-9 else ruido * 1000 decimais = 4 if abs(alvo) < 10 else (2 if abs(alvo) < 1000 else 1) return f"{valor:.{decimais}f} {item.get('unit', '')}".strip() def _aberto(self, item: dict, lang: str, eff: float, sorte: float) -> str: rubrica = item.get("rubric", []) if not rubrica: return _PREENCHER.get(lang, _PREENCHER["en"]) # sistemas mais fracos cobrem menos critérios e inventam conteúdo cobertos = min(len(rubrica), int(eff * len(rubrica) + 0.35)) if sorte < 0.03: # recusa política ocasional return "Desculpe, não posso responder a esta solicitação." if lang == "pt" else ( "抱歉,我无法回答该请求。" if lang == "zh" else "Sorry, I cannot answer this request." ) partes = [_PREENCHER.get(lang, _PREENCHER["en"])] for i, criterio in enumerate(rubrica): if i < cobertos: partes.append(f"- {criterio}") else: partes.append( { "pt": "- Aspecto secundário não desenvolvido.", "en": "- Secondary aspect not developed.", "zh": "- 次要方面未展开。", }[lang] ) if eff < 0.7: partes.append( { "pt": "Observação adicional: o tema é complexo e depende de mais dados.", "en": "Additional note: the topic is complex and depends on more data.", "zh": "补充说明:该主题较复杂,需更多数据。", }[lang] ) return "\n".join(partes) class MockJudge: """Juiz sintético: pontua por cobertura de palavras-chave da rubrica.""" def __init__(self, cfg: dict): self.nome = cfg.get("id", "juiz") self.perfil = cfg.get("perfil", "balanced") self.q = float(cfg.get("mock", {}).get("q", 0.95)) vies = {"strict": -1.0, "balanced": 0.0, "lenient": 1.2}.get(self.perfil, 0.0) self.vies = vies def pontuar(self, item: dict, resposta: str, lang: str) -> dict: rubrica = item.get("rubric", []) notas = [] for i, criterio in enumerate(rubrica): chaves = _chaves(criterio) acertos = sum(1 for c in chaves if c in resposta.lower()) proporcao = acertos / max(1, len(chaves)) nota = proporcao * 5 + self.vies + (self.q - 0.9) * 2 nota = max(0.0, min(5.0, nota)) notas.append(round(nota, 2)) return {"notas": notas, "juiz": f"{self.nome}/{self.perfil}"} def _chaves(texto: str) -> list[str]: t = re.sub(r"[^\w\s-]", " ", texto.lower()) palavras = [p for p in t.split() if len(p) > 3] return palavras[:6] or [texto.lower()[:12]]