Spaces:
Sleeping
Sleeping
# file_utils.py | |
# file_utils.py | |
import pandas as pd | |
import chardet | |
import re | |
import os | |
from sacrebleu.metrics import BLEU | |
# Instância global de BLEU com tokenização 'intl', lowercase e smoothing 'exp' | |
_bleu_scorer = BLEU(tokenize='intl', lowercase=True, smooth_method='exp') | |
def smart_read_csv(file_obj): | |
""" | |
Lê um CSV tentando detectar encoding e separador automaticamente. | |
""" | |
if isinstance(file_obj, str) and os.path.exists(file_obj): | |
f = open(file_obj, 'rb') | |
elif hasattr(file_obj, 'name') and isinstance(file_obj.name, str): | |
try: | |
f = open(file_obj.name, 'rb') | |
except Exception: | |
f = file_obj | |
else: | |
f = file_obj | |
raw = f.read() | |
f.seek(0) | |
enc = chardet.detect(raw).get('encoding', 'utf-8') or 'utf-8' | |
for sep in [',', ';', '\t']: | |
try: | |
df = pd.read_csv(f, encoding=enc, sep=sep) | |
if df.shape[1] >= 2: | |
return df | |
except Exception: | |
pass | |
f.seek(0) | |
raise ValueError(f"Não foi possível ler o CSV com encoding {enc} e separadores comuns.") | |
def normalize_sections(txt: str) -> str: | |
""" | |
Normaliza as tags de seção (## S:, ## O:, ## A:, ## P:) conforme seu notebook original. | |
""" | |
txt = str(txt) | |
# Sintomas | |
txt = re.sub(r'(?m)^\s*S\s*C\s*telemedicina', '## S:', txt, flags=re.IGNORECASE) | |
txt = re.sub(r'(?m)^(?:##\s*)?S\s*[:]?$', '## S:', txt, flags=re.IGNORECASE) | |
# “O” e “A” colados | |
txt = re.sub(r'(?m)^\s*O\s+A\s+', '## O:\n## A: ', txt, flags=re.IGNORECASE) | |
# Objetivos, Avaliação, Plano | |
for tag in ['O','A','P']: | |
txt = re.sub(fr'(?m)^(?:##\s*)?{tag}\s*[:]?$', f'## {tag}:', txt, flags=re.IGNORECASE) | |
# Uniformiza “##X:” → “## X:” | |
for tag in ['S','O','A','P']: | |
txt = re.sub(fr'##\s*{tag}\s*:', f'## {tag}:', txt, flags=re.IGNORECASE) | |
return txt | |
def extract_sections(txt: str) -> dict: | |
""" | |
Extrai o conteúdo de cada seção identificada por ## S:, ## O:, ## A:, ## P:. | |
""" | |
txt = normalize_sections(txt).replace('\n', ' ') | |
txt = re.sub(r'\s+', ' ', txt).strip() | |
sections = {} | |
for tag in ['S','O','A','P']: | |
pat = fr'## {tag}:(.*?)(?=## [SOAP]:|$)' | |
m = re.search(pat, txt, flags=re.IGNORECASE) | |
sections[tag] = m.group(1).strip() if m else '' | |
return sections | |
def normalize_and_flatten(txt: str) -> str: | |
""" | |
Prepara texto completo para cálculo global (flatten + lowercase). | |
""" | |
flat = normalize_sections(txt).replace('\n', ' ') | |
flat = re.sub(r'\s+', ' ', flat).strip() | |
return flat.lower() | |
def has_sections(txt: str) -> bool: | |
""" | |
Retorna True se o texto contém pelo menos uma das tags ## S:, ## O:, ## A: ou ## P: | |
""" | |
txt = normalize_sections(txt) | |
return any(f"## {tag}:" in txt for tag in ['S', 'O', 'A', 'P']) | |
def section_bleu(gen_txt: str, ref_txt: str) -> float: | |
""" | |
Calcula BLEU para um par de strings (seção), retornando score de 0 a 100. | |
""" | |
if not gen_txt.strip() and not ref_txt.strip(): | |
return 100.0 | |
if (not gen_txt.strip()) ^ (not ref_txt.strip()): | |
return 0.0 | |
return _bleu_scorer.sentence_score(gen_txt, [ref_txt]).score | |
def full_bleu(gen_raw: str, ref_raw: str) -> float: | |
""" | |
Calcula BLEU global para strings completas, retornando score de 0 a 100. | |
""" | |
gen = normalize_and_flatten(gen_raw) | |
ref = normalize_and_flatten(ref_raw) | |
if not gen and not ref: | |
return 100.0 | |
if (not gen) ^ (not ref): | |
return 0.0 | |
return _bleu_scorer.sentence_score(gen, [ref]).score | |