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 | |