"""Resolução de datas de compra com fallback único e compartilhado. Requisito: quando a data do documento está ilegível/ausente, atribuir o **primeiro dia do mês corrente** (o mês da importação/lançamento). Tanto a extração por IA quanto o cadastro manual passam por aqui, para nunca divergirem. """ from __future__ import annotations import re from datetime import date _ISO_RE = re.compile(r"^(\d{4})-(\d{2})-(\d{2})$") _BR_RE = re.compile(r"^([0-3]?\d)[/.\-]([01]?\d)[/.\-]((?:19|20)?\d{2})$") def first_of_current_month(reference: date | None = None) -> str: ref = reference or date.today() return f"{ref.year:04d}-{ref.month:02d}-01" def _valid_iso(year: int, month: int, day: int) -> str | None: try: return date(year, month, day).isoformat() except ValueError: return None def parse_date(value: str | None) -> str | None: """Tenta interpretar uma data em ISO (YYYY-MM-DD) ou BR (dd/mm/aaaa). Retorna a data normalizada em ISO, ou None se não for uma data válida. """ if not value: return None text = value.strip() m = _ISO_RE.match(text) if m: return _valid_iso(int(m.group(1)), int(m.group(2)), int(m.group(3))) m = _BR_RE.match(text) if m: day, month, year = m.groups() year_i = int(year) if year_i < 100: year_i += 2000 return _valid_iso(year_i, int(month), int(day)) return None def resolve_purchase_date(value: str | None, *, reference: date | None = None) -> str: """Data válida em ISO, ou o 1º dia do mês corrente quando ilegível/ausente.""" parsed = parse_date(value) if parsed: return parsed return first_of_current_month(reference)