Spaces:
Sleeping
Sleeping
Gil Almeida
fix: resolve TypeError in normalized_uc, add Intent Router for inventory queries, and fix payload integrity
5bfaada | #!/usr/bin/env python3 | |
| import json | |
| import csv | |
| import re | |
| import argparse | |
| from pathlib import Path | |
| import unicodedata | |
| KNOWN_RANGES = { | |
| "tacs": { | |
| "default": (4, 133), | |
| "variáveis e scope": (7, 9), | |
| "assincronia: promises": (119, 121), | |
| }, | |
| "semio": { | |
| "default": (1, 60), | |
| "resumo sc": (17, 28), | |
| "semiótica resumos": (1, 30), | |
| } | |
| } | |
| def clean_text(text): | |
| text = (text or "").lower() | |
| text = unicodedata.normalize("NFKD", text).encode("ascii", "ignore").decode("ascii") | |
| return re.sub(r'\s+', ' ', re.sub(r'[^a-z0-9_]+', ' ', text)).strip() | |
| def get_words(text): | |
| return set(clean_text(text).split()) | |
| def calculate_jaccard(text1, text2): | |
| w1 = get_words(text1) | |
| w2 = get_words(text2) | |
| if not w1 or not w2: | |
| return 0.0 | |
| return len(w1.intersection(w2)) / len(w1.union(w2)) | |
| def parse_expected_sources(src_list): | |
| # Returns list of (file, page) | |
| parsed = [] | |
| for src in src_list: | |
| # e.g., "Variáveis e Scope, pp. 6-8" or "Resumo SC, p. 18" | |
| parts = src.split(",") | |
| file_name = parts[0].strip() if parts else "Desconhecido" | |
| page = parts[1].strip() if len(parts) > 1 else "N/A" | |
| parsed.append((file_name, page)) | |
| return parsed | |
| def audit_dataset(dataset_path: Path, old_dataset_path: Path | None, subject: str, out_csv: Path) -> None: | |
| print(f"\n================ AUDITING DATASET: {dataset_path.name} ================") | |
| with open(dataset_path, "r", encoding="utf-8") as f: | |
| data = json.load(f) | |
| old_data = [] | |
| if old_dataset_path and old_dataset_path.exists(): | |
| with open(old_dataset_path, "r", encoding="utf-8") as f: | |
| old_data = json.load(f) | |
| # 1. Structural Integrity Check | |
| structural_ok = True | |
| reasons = [] | |
| if len(data) != 50: | |
| structural_ok = False | |
| reasons.append(f"Número de itens incorreto: {len(data)} (esperado 50)") | |
| ids = [item.get("id") for item in data] | |
| if sorted(ids) != list(range(1, 51)): | |
| structural_ok = False | |
| reasons.append(f"IDs não são consecutivos de 1 a 50: {sorted(ids)[:5]}...") | |
| categories = [item.get("categoria") for item in data] | |
| from collections import Counter | |
| counts = Counter(categories) | |
| expected_counts = { | |
| "definicao": 10, | |
| "conceptual": 10, | |
| "procedimento": 10, | |
| "debug": 8, | |
| "aplicacao": 5, | |
| "fora_scope": 5, | |
| "ambigua": 2 | |
| } | |
| for cat, expected in expected_counts.items(): | |
| if counts[cat] != expected: | |
| structural_ok = False | |
| reasons.append(f"Categoria '{cat}' com contagem {counts[cat]} (esperado {expected})") | |
| # 2. Reference Validity Check | |
| reference_ok = True | |
| # 3. Internal Consistency Check (must_include in expected_context) | |
| consistency_ok = True | |
| # Generate CSV Rows | |
| csv_rows = [] | |
| for item in data: | |
| item_id = item.get("id") | |
| cat = item.get("categoria") | |
| pergunta = item.get("pergunta") | |
| gt = item.get("contexto_esperado") | |
| must_inc = item.get("must_include", []) | |
| must_not = item.get("must_not_include", []) | |
| sources = item.get("expected_sources", []) | |
| notes = item.get("evaluation_notes", "") | |
| parsed_src = parse_expected_sources(sources) | |
| file_names = " | ".join(p[0] for p in parsed_src) if parsed_src else "N/A" | |
| pages = " | ".join(p[1] for p in parsed_src) if parsed_src else "N/A" | |
| # Validar páginas | |
| subject_ranges = KNOWN_RANGES.get(subject, {}) | |
| for src in sources: | |
| matched_key = "default" | |
| for key in subject_ranges: | |
| if key != "default" and key in src.lower(): | |
| matched_key = key | |
| break | |
| min_p, max_p = subject_ranges[matched_key] | |
| page_matches = re.findall(r'\b(?:p\.?|pp\.?|pag\.?|p[aá]gina|powerpoint|slide)\s*[:.]?\s*(\d+)', src, re.IGNORECASE) | |
| for p_str in page_matches: | |
| p_num = int(p_str) | |
| if p_num < min_p or p_num > max_p: | |
| reference_ok = False | |
| reasons.append(f"ID {item_id}: página {p_num} em '{src}' fora do intervalo ({min_p}-{max_p})") | |
| # Validar consistência interna | |
| for must in must_inc: | |
| must_clean = clean_text(must) | |
| must_words = [w for w in must_clean.split() if len(w) >= 4] | |
| if must_words: | |
| matches = sum(1 for w in must_words if w in clean_text(gt)) | |
| if (matches / len(must_words)) < 0.4: | |
| consistency_ok = False | |
| reasons.append(f"ID {item_id}: must_include '{must}' tem fraca consistência com o ground truth") | |
| csv_rows.append({ | |
| "ID": item_id, | |
| "categoria": cat, | |
| "pergunta": pergunta, | |
| "resposta_de_referencia": gt, | |
| "ficheiro": file_names, | |
| "pagina": pages, | |
| "excerto_da_fonte": "[Preencher na auditoria manual]", | |
| "must_include": "; ".join(must_inc), | |
| "notas": notes, | |
| "manual_audit_status": "pending" | |
| }) | |
| # Write CSV | |
| fields = ["ID", "categoria", "pergunta", "resposta_de_referencia", "ficheiro", "pagina", "excerto_da_fonte", "must_include", "notas", "manual_audit_status"] | |
| with open(out_csv, "w", encoding="utf-8", newline="") as f: | |
| writer = csv.DictWriter(f, fieldnames=fields) | |
| writer.writeheader() | |
| writer.writerows(csv_rows) | |
| print(f"Audit CSV written to: {out_csv.name}") | |
| # 4. Lexical Similarity Report against Previous Dataset | |
| similarities = [] | |
| if old_data: | |
| for item in data: | |
| q_new = item.get("pergunta") | |
| id_new = item.get("id") | |
| for old_item in old_data: | |
| q_old = old_item.get("pergunta") | |
| id_old = old_item.get("id") | |
| jac = calculate_jaccard(q_new, q_old) | |
| similarities.append((id_new, id_old, jac, q_new, q_old)) | |
| # Sort and get top 5 highest similarities | |
| similarities.sort(key=lambda x: x[2], reverse=True) | |
| print("\nTop 5 Pares com Maior Semelhança com Dataset Anterior:") | |
| for idx, (id_n, id_o, jac, q_n, q_o) in enumerate(similarities[:5]): | |
| print(f" {idx+1}. Jaccard Similarity = {jac:.3f}") | |
| print(f" Novo ID {id_n}: {q_n}") | |
| print(f" Antigo ID {id_o}: {q_o}") | |
| # Audit summary | |
| print("\n---------------- AUDIT SUMMARY ----------------") | |
| print(f"1. Integridade Estrutural: {'✅ APROVADO' if structural_ok else '❌ FALHOU'}") | |
| print(f"2. Validade das Referências: {'✅ APROVADO' if reference_ok else '❌ FALHOU'}") | |
| print(f"3. Consistência Interna: {'✅ APROVADO' if consistency_ok else '⚠️ AVISOS EMITIDOS'}") | |
| print(f"4. Factualidade Científica: 🔎 PENDENTE DE AUDITORIA HUMANA (manual_audit_status=pending)") | |
| print("-----------------------------------------------") | |
| if reasons: | |
| print("Detalhes encontrados:") | |
| for r in reasons[:10]: | |
| print(f" - {r}") | |
| if len(reasons) > 10: | |
| print(f" ... e mais {len(reasons)-10} ocorrências.") | |
| if __name__ == "__main__": | |
| audit_dataset( | |
| Path("dataset_tacs_mis4tel_v5_heldout_50.json"), | |
| Path("dataset_tacs_mis4tel_v4_50.json"), | |
| "tacs", | |
| Path("dataset_tacs_mis4tel_v5_heldout_audit.csv") | |
| ) | |
| audit_dataset( | |
| Path("dataset_semio_mis4tel_v2_heldout_50.json"), | |
| Path("dataset_semio_mis4tel_v1.json"), | |
| "semio", | |
| Path("dataset_semio_mis4tel_v2_heldout_audit.csv") | |
| ) | |