# Auditoria 02 — Datação (datas de protecção)

| | |
|---|---|
| **Painel** | Auditoria de fidelidade MonumentosES — agente **2/8** |
| **Aspecto** | Datas de protecção (`raw/prot_dates.json`, gerado por `tools/build_prot_dates.py` a partir dos textos `pr` = `pd · disp · pb`) |
| **Data** | 2026-09-04 |
| **Veredicto** | **APROVADO com ressalvas.** Re-parse independente: concordância 99,97% (9575/9578). Zero datas implausíveis. Amostragem 15/15 confirmada na fonte; 3 verificações HTTP bem-sucedidas (2 [C] plenas, 1 [C] por metadados BOE). Encontrados: 2 datas com ano errado **na fonte** (ext, confirmado contra BOE), 1 discrepância fonte↔BOE (ast), 1635 eventos só-ano (17,1%) que concentram o gráfico por década, e o Passo 2 (resíduo → lotes de agentes) ainda não executado. |

---

## Âmbito

Objecto da auditoria [C — ficheiros presentes no repo]:

- `raw/prot_dates.json` — 9578 datas de protecção `{i: {date, prov, raw}}`, geradas por `tools/build_prot_dates.py` (Fase C.3);
- string cru de protecção: `www/explorer/bic_data.json`, campo `pr` (10460 registos com `pr` de 24337);
- textos originais por campo (`pd`/`disp`/`pb`/`pu`): `raw/interm/<src>.json` (15 fontes);
- resíduo não-parseável: `raw/prot_residuo.json` (365 entradas);
- recomendação de resolução via API IPAC (invarquit, Generalitat) — `tools/fuentes.csv`, `docs/fuentes/cataluna.md`.

Fora do âmbito: qualidade jurídica das datas (qual acto protege efectivamente), datas de incoação, datas de fim (vigência).

## Método

1. **Re-parse independente** [C]: escrevi um parser de raiz (regexes próprias, mapa de meses próprio, validações próprias — código completo no fim) que implementa a mesma convenção documentada no docstring do `build_prot_dates.py`: *data do evento = data de publicação no boletim quando existir; quando só há disposição, ano da disposição [I]*. Ordem de preferência por tipo: dmy > iso > texto > ano-disposição > boe-id > ast-decreto. Comparei (date + estratégia) com `raw/prot_dates.json`, registo a registo.
2. **Plausibilidade** [C]: histograma por década; pesquisa de datas < 1800 ou > 2026 (o `tools/audit_es.py` A9 verifica o mesmo intervalo programaticamente); inspecção dos extremos e dos vales.
3. **Amostragem dirigida** [C]: 15 registos, `random.Random(7)`, estratificado por estratégia (5 dmy + 5 iso + 3 texto + 2 livres) sobre o pool `prov C-regex-*` com dia (7864 elegíveis). Para cada um, voltei ao `raw/interm/<src>.json` (campos `pd`/`disp`/`pb` originais) e confirmei que a data extraída é derivável do texto fonte. Para 3 com URL de boletim (`pu`), verifiquei por HTTP.
4. **Cobertura e viés** [C]: % de registos com data por fonte; proveniência da data oficial por campo (parse campo-a-campo com o meu parser); diferença declaração (`pd`) ↔ publicação (`pb`) nos pares onde ambas existem; contagem de eventos year-only.
5. **Resíduo** [C]: classificação dos 365 por padrão; verificação da recomendação IPAC contra a documentação interna das fontes.

## Achados

### 1. Re-parse independente — concordância 99,97% [C]

9578 datas oficiais; o meu parser produziu exactamente as mesmas 9578 (zero casos "só o meu achou", zero "só a oficial achou"). Concordância (date **e** estratégia): **9575/9578 = 99,97%**.

| Estratégia (prov) | n oficial | acordo | taxa |
|---|---:|---:|---:|
| `C-regex-dmy` | 3235 | 3235 | 100% |
| `C-regex-iso` | 4376 | 4373 | 99,93% |
| `C-regex-texto` | 253 | 253 | 100% |
| `C-boe-id` | 1539 | 1539 | 100% |
| `I-ano-disposicion` | 96 | 96 | 100% |
| `I-ast-decreto` | 79 | 79 | 100% |

As **3 divergências** (0,03%) são o mesmo caso: `arg:1781`, `arg:1783`, `arg:1784` com raw `'2017-09-13 · BOA19/10/2017'` (espaço colado em "BOA19/10/2017"). O `\b` inicial do regex oficial não bate a seguir a "A", pelo que a oficial devolve a data ISO `2017-09-13` (`C-regex-iso`, a data `pd`); o meu parser (fronteira definida como "não-dígito") devolve `2017-10-19` (`C-regex-dmy`, a publicação no BOA). **Pela convenção do próprio projecto (publicação primeiro), o meu parse é o mais conforme** — mas o impacto é de 3 fichas e ~1 mês de diferença entre as duas datas. Sem erro de extracção propriamente dito; é uma fragilidade de normalização.

### 2. Plausibilidade — zero anomalias [C]

- **Datas < 1800 ou > 2026: 0** [C — confirmado por varrimento independente; o mesmo check existe em `tools/audit_es.py` A9 (1800–2026)].
- Intervalo real: 1844–2026 (com dia); 1949–2026 (só ano).

```
Déca   n     (barra ≈ n/2067*55)
1840   2    #
1860   1    #
1870   9    #
1880  21    #
1890  25    #
1900   9    #
1910  46    #
1920 120    ###
1930 515    ##############
1940 2067   #######################################################
1950  50    #
1960 377    ##########
1970 527    ##############
1980 1793   ################################################
1990 1244   #################################
2000 1707   #############################################
2010  809   ######################
2020  256   #######
```

- **Pico 1940-49 (2067)** é explicável e não é erro: 1492 são de Catalunha, dos quais **1478 valem `C-boe-id` = 1949** do identificador `BOE-A-1949-4615. Protecció dels castells espanyols.` [C — texto no `raw`] (um único decreto-geral aplicado a milhares de bens; o decreto dos castelos de 22/04/1949 [I — identificação histórica, não verificada na fonte]) + cyl 456 (declarações do mesmo ano-1949 na base da Junta de Castilla y León [C — valores `pd` na fonte]). **Vale 1950-59 (50)** é a consequência: entre o decreto-geral de 1949 e a vaga autonómica pós-Lei 16/1985 quase não houve declarações [I — leitura histórica].
- **1980-89 (1793) / 2000-09 (1707)**: vaga autonómica e actualizações — coerente com os `pd` de clm/mad/gal/cant.
- Sem aglomerados impossíveis (p.ex. datas todas no mesmo dia sem causa: o único aglomerado grande e de dia fixo é 1949 em cyl/cat, com causa documentada).

### 3. Amostragem — 15/15 confirmados na fonte; 3 verificações HTTP [C]

Desenho: `random.Random(7)` estratificado (5 dmy, 5 iso, 3 texto, 2 livres) sobre 7864 registos `C-regex-*` com dia. Em todos os 15, a data em `prot_dates.json` é derivável literalmente do campo fonte [C]. Tabela condensada:

| # | Registo | prov | date | campo fonte (raw/interm) | Confirmação |
|---|---|---|---|---|---|
| 1 | `clm:148` | C-regex-dmy | 1974-07-30 | `pd='30/07/1974'` | ✓ |
| 2 | `arg:372` | C-regex-dmy | 2006-05-22 | `pd='2006-04-17'` · `pb='BOA 22/05/2006'` | ✓ (publicação preferida, conforme convenção) |
| 3 | `clm:426` | C-regex-dmy | 1992-07-08 | `pd='08/07/1992'` | ✓ |
| 4 | `gal:BIC.000.529` | C-regex-dmy | 1994-10-17 | `pd='17/10/1994'` · `disp='Resolución DGBAA'` · `pb='-'` | ✓ (`pb='-'` correctamente ignorado) |
| 5 | `arg:1325` | C-regex-dmy | 2006-05-22 | idem #2 | ✓ |
| 6 | `cant:{50D38EB1-…}` | C-regex-iso | 2009-01-08 | `pd='2009-01-08'` (`PUB_FECHA`) · `pu` BOC | ✓ (pu é PDF — não fetchável, ver abaixo) |
| 7 | `cant:{6AD0C22C-…}` | C-regex-iso | 1893-04-14 | `pd='1893-04-14'` · `pb='La Gaceta'` · `pu=' '` | ✓ (Gaceta 1893 coerente com a data) |
| 8 | `cyl:2156` | C-regex-iso | 1949-04-21 | `pd='1949-04-21'` (`f_bien_protec`) | ✓ fonte (nota: o decreto-quadro de 1949 é de 22/04 [I]; o valor 21/04 é o da fonte cyl) |
| 9 | `cant:{42330FE4-…}` | C-regex-iso | 1931-06-04 | `pd='1931-06-04'` · `pb='La Gaceta'` | ✓ coerente com Gaceta nº 155 de 04/06/1931 [C, ver #12] |
| 10 | `cyl:865987` | C-regex-iso | 1984-08-01 | `pd='1984-08-01'` | ✓ |
| 11 | `ten:0` | C-regex-texto | 2006-12-11 | `pb='BOC nº 238 de 11 de diciembre de 2006'` | ✓ **HTTP [C]** — BOC nº 238, "Lunes 11 de Diciembre de 2006" |
| 12 | `ast:120202002000` | C-regex-texto | 1931-06-03 | `disp='Decreto 3 de Junio de 1931'` | ✓ **HTTP [C]** — BOE-A-1931-4481: «Gaceta de Madrid núm. 155, de 4 de junio de 1931, páginas 1181 a 1185» = exactamente o path do `pu` (`BOE/1931/155/A01181-01185.pdf`); o PDF em si não fetchable (application/pdf) |
| 13 | `ast:380304001000` | C-regex-texto | 1965-03-16 | `disp='Orden de 16 de Marzo de 1965'` | ✓ fonte; **mas** HTTP [C]: o BOE de 22/05/1965 (Núm. 122) publica «Orden de **15** de marzo de 1965» (BOE-A-1965-9879, Torres de Proaza y de Peñerudes) — desvio de fonte, ver Desvios |
| 14 | `clm:424` | C-regex-dmy | 2002-01-25 | `pd='25/01/2002'` | ✓ |
| 15 | `clm:303` | C-regex-dmy | 1992-03-04 | `pd='04/03/1992'` | ✓ |

Verificações HTTP (3/3 com resultado; 0 marcadas [I]):
1. `ten:0` → `http://www.gobiernodecanarias.org/boc/2006/238/006.html` — **bate** [C]: BOC nº 238, 11/12/2006, Declaração BIC Zona Arqueológica "La Rasca" (Decreto 175/2006, de 28 de novembro — a disposição é 13 dias antes da publicação).
2. `ast:120202002000` → PDF do BOE não fetchável; verificação feita pela ficha BOE-A-1931-4481 [C]: Gaceta nº 155, 04/06/1931, págs. 1181-1185 — consistente com o `pu` do registo e com a data extraída (data da disposição, 03/06/1931).
3. `ast:380304001000` → `https://www.boe.es/boe/dias/1965/05/22/` [C]: índice do BOE Núm. 122 (sábado 22/05/1965) contém a «Orden de 15 de marzo de 1965» que declara as «Torres de Proaza y de Peñerudes» — **a data na fonte ast (16/03) difere em 1 dia da data no BOE (15/03)**.

### 4. Cobertura e viés para o gráfico "protecções por década" [C]

Cobertura por fonte (24337 registos, 10460 com `pr`, 9578 com data = **39,4%** do total; 82,9% dos datados têm dia):

| fonte | recs | c/ `pr` | c/ data | % recs | % `pr` | só ano |
|---|---:|---:|---:|---:|---:|---:|
| eus | 10295 | 0 | 0 | 0% | — | 0 |
| cyl | 2479 | 2477 | 2477 | 99,9% | 100% | 0 |
| cant | 2400 | 2400 | 1888 | 78,7% | 78,7% | 0 |
| cat | 2161 | 2161 | 1798 | 83,2% | 83,2% | 1540 |
| nav | 1426 | 0 | 0 | 0% | — | 0 |
| gva | 1185 | 0 | 0 | 0% | — | 0 |
| arg | 1021 | 1021 | 1021 | 100% | 100% | 0 |
| eiv | 728 | 0 | 0 | 0% | — | 0 |
| gal | 723 | 723 | 723 | 100% | 100% | 0 |
| clm | 631 | 631 | 631 | 100% | 100% | 0 |
| mad | 443 | 309 | 309 | 69,8% | 100% | 0 |
| ast | 331 | 226 | 224 | 67,7% | 99,1% | 91 |
| ext | 250 | 248 | 248 | 99,2% | 100% | 4 |
| ten | 199 | 199 | 199 | 100% | 100% | 0 |
| rio | 65 | 65 | 60 | 92,3% | 92,3% | 0 |

- **4 fontes sem nenhum texto de protecção** (eus 10295, nav 1426, gva 1185, eiv 728 = 13634 registos, 56,0% do total) [C]. As datas do site representam apenas as 11 fontes com `pr`.
- **Year-only: 1635 eventos = 17,1% dos datados** [C]: `C-boe-id` 1539 (cat: ano do identificador BOE — 1949 ×1478, 1963 ×61 [C]), `I-ano-disposicion` 96 (ast 91 + ext 4 + cat 1), `I-ast-decreto` 79 **têm** dia mas o século é inferido do sufixo `/91` do decreto (limiar yy≥30→1900) [C — regra no código, plausível mas [I] por natureza].
- **Fontes onde a data vem só do ANO do decreto** (`I-*`): Astúrias (disp tipo 'Decreto 145/91, de 31 de Octubre' → ano do sufixo; 91 fichas com ano apenas), Extremadura (4), Catalunha (1) [C].
- **Disposição ≠ publicação, quantificado** [C]: em 1046 fichas com par `pd`→`pb` datáveis (arg 1018 + ext 28), a diferença declaração→publicação tem **mediana 35 dias** (média 28,3; mín −1634, máx 93). Em 1198 fichas `pd` e `pb` têm datas distintas; em apenas **15** a data oficial veio de `pd` ignorando uma data de publicação diferente em `pb` (efeito da prioridade dmy>iso, por desenho). Proveniência real da data oficial por campo: `pd` 6106 (+2 com igual em ambos), `pb` 3246, `disp` 224 — por fonte: arg→`pb` 1018, cant→`pd` 1888 (`PUB_FECHA` = data de publicação BOC [I — interpretação do nome do campo]), cat→`pb` 1798, clm→`pd` 631, cyl→`pd` 2477 (`f_bien_protec` = data de aquisição da categoria [I — idem]), ext→`pb` 231/`pd` 17, gal→`pd` 723, mad→`pd` 309, rio→`pd` 60, ten→`pb` 199, ast→`disp` 224.
- **Risco para o gráfico**: a década de 1940-49 é dominada por 1478 datas year-only de UM decreto-geral estatal (cat) — o gráfico mede, nessa década, "aplicação do Decreto de 1949", não actos de protecção individuais; e mistura semanticamente datas de declaração (cyl/clm/gal/mad/rio), de publicação (arg/cat/ten/cant/ext) e de disposição (ast). Nada disto está assinalado no dataset de saída.

### 5. Resíduo — 365 entradas; 358 DOGC confirmado [C]

Classificação dos 365 [C — varrimento directo]:

| padrão | n | observação |
|---|---:|---|
| `DOGC nº …` (só número, sem data) | **358** | todos `cat:*` — campo `LEGISLACIO` do WFS gencat, que não traz data [C] |
| `DOCG nº …` | 2 | `cat` — siglas transpostas na fonte (DOGC invertido) |
| `DOGG nº …` | 1 | `cat` — variante da fonte |
| `Orden 28 Septiembre 1973` | 1 | `ast` — **recuperável**: data completa no texto, só falta variante sem "de" no regex-texto |
| `Decreto 33/92, de 3 de Abirl` | 1 | `ast` — **recuperável**: typo "Abirl" (abril) na fonte |
| `Acord GOV/266/2025` | 1 | `cat` — ano 2025 disponível como `I-ano-disposicion` se "acord" entrar nas keywords |
| `BOE nº 261 de 31/101974` | 1 | `cat` — **recuperável**: data colada 31/10/1974 |

- Confirmação da classificação **358 DOGC nº**: ✓ exacta [C]. (Nota: 35 dos 358 têm "número de DOGC" que *parece* ano, p.ex. `DOGC nº 1708`; o branch `I-ano-disposicion` não os apanhou porque "DOGC" não contém as keywords `decreto|resoluci|ley|ordena|acuerdo|declaraci` — resultado correcto por sorte estrutural; se um raw misturasse "DOGC nº 1708 … Decreto …", o branch tomariam `anos[-1]` e podia devolver o nº do boletim como ano.)
- **Recomendação IPAC confirmada como adequada** [C]: para os 358 (+3 variantes DOGC), a fonte da data é a API IPAC/invarquit da Generalitat — `POST /api/Search/advanced-search-range`, `/api/Card/{codarq}`, `/api/Inventary/{codarq}/proteccions`, que "devolve disposicio+data (actos)" (`tools/fuentes.csv`, linha da Cataluña API IPAC; `docs/fuentes/cataluna.md`, DATASET 2); o identificador `ipac` (CODI_INVENTARI) já está guardado nos items de `raw/interm/cataluna.json` [C].
- **O Passo 2 previsto no docstring ("resíduo vai em lotes — tools/prot_batches/") nunca foi executado**: `tools/prot_batches/` não existe [C].

## Desvios

1. **[FONTE, confirmado contra BOE] 2 datas com ano errado**: `ext:n120` e `ext:RI-51-0000943-00000` têm raw `1931-06-03 · LA GACETA: 04-06-1930` (FECHA DIARIO = 04-06-**1930**) e ficaram datados **1930-06-04**. A Gaceta nº 155 do Decreto de 3/6/1931 é de **04-06-1931** [C — BOE-A-1931-4481]. O pipeline foi fiel à fonte; a fonte está errada (typo no ano, −364 dias). Impacto: 2 fichas na década errada (1920→1930 não; ficam em 1930 em vez de 1931).
2. **[FONTE] 1 discrepância fonte ↔ BOE de 1 dia**: `ast:380304001000` fonte diz "Orden de **16** de Marzo de 1965"; o BOE Núm. 122 (22/05/1965) publica «Orden de **15** de marzo de 1965» (BOE-A-1965-9879) [C]. Extracção fiel; a data na fonte ast é suspeita. (Possível segunda Orden de 16/03 não localizada [I].)
3. **[PIPELINE, 3 fichas] Datas de boletim coladas não reconhecidas**: `BOA19/10/2017` sem espaço → o parser oficial perde a publicação e fica com a data `pd` (ver Achados 1).
4. **[PIPELINE, por desenho] Prioridade dmy>iso pode escolher declaração em vez de publicação**: 15 fichas com data oficial de `pd` embora `pb` tenha data diferente [C]. Convenção declarada é "publicação quando existir"; a prioridade por *formato* é só uma aproximação dessa convenção.
5. **[PIPELINE, latente] `protection_str` trunca a `parts[:2]`**: com as 3 partes presentes (`pd·disp·pb`) a publicação cairia fora de `pr` — hoje sem impacto real porque na única fonte de 3 campos (gal) os 633 `pb` são todos o placeholder `'-'` [C]; mas o comentário em `build_prot_dates.py` ("pr já compõe pd·disp·pb") é impreciso.
6. **[PIPELINE] 517 `pr` curtos descartados fora do resíduo**: 512 `pr=' '` (cant) + 5 `pr='BOE'` (rio, sem data) são silenciosamente omitidos pelo filtro `len(pr)>3` e não contam no resíduo de 365 [C]. A estatística "resíduo p/ agentes" subestima assim a cauda em 517.
7. **[PIPELINE, 23 fichas] Strings com ≥2 datas dmy**: p.ex. `arg:627` `2006-04-17 · BOA 26/10/2001 y BOA 22/05/2006` → toma a 1ª (2001-10-26, protecção mais antiga — razoável, mas regra não documentada); 22 cat do tipo `GACETA DE MADRID nº 155 de 04/06/1931 - BOE nº 131 de 01/06/1963` → toma a Gaceta (1ª) [C].
8. **[PIPELINE] Docstring promete `"kind": "publicación|disposición|incoación"` e a saída não tem esse campo** (só `date/prov/raw`) [C] — a semântica mista da secção 4 fica invisível para o consumidor do JSON.

## Recomendações

1. **Normalizar colagens antes do parse** (`re.sub(r'([A-Z]{2,})(?=\d{1,2}[/-])', r'\1 ', raw)`): recupera as 3 fichas BOA (arg) e a `31/101974` (cat). Custo mínimo.
2. **Validação cruzada declaração↔publicação**: sinalizar pares com |Δ| > 365 dias — teria apanhado os 2 casos ext (−364/−331) e o duplo evento arg:627 (−1634). Publicar a lista como `raw/prot_flags.json`.
3. **Executar o Passo 2**: criar `tools/prot_batches/` para o resíduo — 6 das 7 não-DOGC são recuperáveis por regex/typo (Achados 5); os 358+3 DOGC via API IPAC (`/api/Inventary/{codarq}/proteccions`, com o `ipac` já em `raw/interm/cataluna.json`), mantendo `[I]` até confirmação na ficha.
4. **Marcar a semântica no JSON de saída** (cumprir o docstring): `kind` por fonte (arg/cat/ten/cant=publicação; cyl/clm/gal/mad/rio=declaração; ast=disposição) e um booleano `year_only` para os 1635 — condição para o gráfico por década ser honesto.
5. **No gráfico "protecções por década"**: distinguir (ou poder excluir) os 1478 da década 1940-49 que derivam do decreto-geral BOE-A-1949-4615; e legendá-los como "aplicação de decreto-geral", não como declarações individuais.
6. **Corrigir na fonte os 2 anos ext (1930→1931)** — ou sobrepor uma lista de excepções justificada (com a referência BOE-A-1931-4481) no ingest_extremadura.py; idem para o typo "Abirl" (ast) se a API ast não for reconsultada.
7. Corrigir o comentário de `build_prot_dates.py` sobre a composição de `pr` (`parts[:2]`) — hoje inócuo, perigoso se a gal passar a ter `pb` real.

## Fontes

**Internas** [C — lidas durante a auditoria]:
- `tools/build_prot_dates.py` (gerador; convenção, regexes, docstring Passo 2)
- `tools/build_site_view.py` (função `protection_str`, `parts[:2]`)
- `tools/audit_es.py` (check A9, intervalo 1800–2026)
- `tools/ingest_aragon.py`, `ingest_cantabria.py` (`PUB_FECHA`/`PUBLICA`/`PUB_WEB`), `ingest_extremadura.py` (`FECHA DECLARACIÓN`/`FECHA DIARIO`), `ingest_castilla-y-leon.py` (`f_bien_protec`), `ingest_cataluna.py` (`LEGISLACIO`, `CODI_INVENTARI`=ipac)
- `tools/fuentes.csv` (API IPAC: `https://invarquit.cultura.gencat.cat/` — `/api/Inventary/{codarq}/proteccions` devolve disposicio+data)
- `docs/fuentes/cataluna.md` (DATASET 2 — API IPAC), `docs/relatorio-proveniencia.md`
- Dados: `raw/prot_dates.json`, `raw/prot_residuo.json`, `www/explorer/bic_data.json`, `raw/interm/{aragon,asturias,cantabria,cataluna,castilla-la-mancha,castilla-y-leon,extremadura,galicia,la-rioja,madrid,canarias-tenerife}.json`

**Externas** (verificações HTTP desta auditoria):
- BOC nº 238, "Lunes 11 de Diciembre de 2006" (Rasca) — <http://www.gobiernodecanarias.org/boc/2006/238/006.html> [C]
- BOE-A-1931-4481, Decreto de 3/6/1931, «Gaceta de Madrid núm. 155, de 4 de junio de 1931, páginas 1181 a 1185» — <https://www.boe.es/buscar/doc.php?id=BOE-A-1931-4481> [C]
- BOE Núm. 122, sábado 22/05/1965 (Orden de 15/03/1965, BOE-A-1965-9879, Torres de Proaza y de Peñerudes) — <https://www.boe.es/boe/dias/1965/05/22/> [C]
- Identificação BOE-A-1949-4615 = Decreto dos castelos (22/04/1949): [I] — inferida do texto do raw + conhecimento histórico, não verificada por HTTP nesta auditoria.

## Comandos

Ambiente: Git Bash + Python 3.12, na raiz do projecto. Parser independente reproduzido integralmente no fim (guardado em `$TMPDIR/audit02/reparse.py` durante a auditoria).

```bash
# 1) Re-parse independente + comparação por estratégia (script no fim)
python "$TMPDIR/audit02/reparse.py"          # -> concordância 9575/9578; 3 divergências; cmp.json

# 2) Plausibilidade + histograma + resíduo + cobertura (heredoc python, resumo dos comandos)
python - <<'EOF'
import json
from collections import Counter
d = json.load(open('raw/prot_dates.json', encoding='utf-8'))
bad = [(i,v) for i,v in d.items() if not (1800 <= int(v['date'][:4]) <= 2026)]   # -> []
dec = Counter(int(v['date'][:4])//10*10 for v in d.values())                     # histograma
yr  = Counter(v['prov'] for v in d.values() if len(v['date'])==4)                # year-only
r = json.load(open('raw/prot_residuo.json', encoding='utf-8'))
res = Counter(str(x['raw']).strip().upper().split()[0] for x in r)               # DOGC: 358
EOF

# 3) Amostragem (seed 7, estratificada) + confronto com raw/interm
python - <<'EOF'
import json, random
dates = json.load(open('raw/prot_dates.json', encoding='utf-8'))
pools = {p: [i for i,v in sorted(dates.items())
             if v['prov']==p and len(v['date'])==10]
         for p in ('C-regex-dmy','C-regex-iso','C-regex-texto')}
rng = random.Random(7)
sel = rng.sample(pools['C-regex-dmy'],5) + rng.sample(pools['C-regex-iso'],5) \
    + rng.sample(pools['C-regex-texto'],3) + rng.sample(pools['C-regex-dmy'],2)
# para cada i: abrir raw/interm/<src>.json, comparar date com pd/disp/pb
EOF

# 4) Verificações HTTP (WebFetch): boc 2006/238/006.html; boe.es/boe/dias/1965/05/22/;
#    boe.es/buscar/doc.php?id=BOE-A-1931-4481   (PDF boc/boe: unsupported application/pdf)
```

<details><summary>Parser independente completo (audit02/reparse.py)</summary>

```python
import json, re
from collections import Counter
from pathlib import Path
ROOT = Path(r"C:/Users/Atelier X/Nextcloud/Documents/00_Website/MonumentosES")
MM = {}
for k, v in [("enero",1),("febrero",2),("marzo",3),("abril",4),("mayo",5),("junio",6),
             ("julio",7),("agosto",8),("septiembre",9),("setiembre",9),("octubre",10),
             ("noviembre",11),("diciembre",12),("xaneiro",1),("febreiro",2),("maio",5),
             ("xuño",6),("xullo",7),("setembro",9),("outubro",10),("novembro",11),("decembro",12)]:
    MM[k] = v
RX1 = re.compile(r"(?:\A|[^0-9])(\d{1,2})[/-](\d{1,2})[/-](\d{4})(?![0-9])")
RX2 = re.compile(r"(?:\A|[^0-9])(\d{4})-(\d{1,2})-(\d{1,2})(?![0-9])")
RX3 = re.compile(r"(?:\A|[^0-9])(\d{1,2})\s+de\s+([A-Za-zÁÉÍÓÚÜÑáéíóúüñ]+?),?\s+(?:de\s+)?(\d{4})(?![0-9])", re.I)
RX4 = re.compile(r"(?:\A|[^0-9])(1[5-9][0-9]{2}|20[0-2][0-9])(?![0-9])")
RX5 = re.compile(r"BOE-A-(1[5-9][0-9]{2}|20[0-2][0-9])")
RX6 = re.compile(r"/([0-9]{2})\s*,\s*(?:de\s+)?(\d{1,2})\s+(?:de\s+)?([A-Za-zÁÉÍÓÚÜÑáéíóúüñ]+)(?![a-z])", re.I)
KW  = re.compile(r"decreto|resoluci|ley|ordena|acuerdo|declaraci", re.I)
def norm(mes):
    m = mes.lower()
    for a,b in (("á","a"),("é","e"),("í","i"),("ó","o"),("ú","u"),("ü","u")): m = m.replace(a,b)
    return MM.get(m)
def my_parse(raw):
    s = str(raw)
    m = RX1.search(s)
    if m:
        d, mo, y = int(m[1]), int(m[2]), int(m[3])
        if 1 <= mo <= 12 and 1 <= d <= 31 and 1500 < y < 2030:
            return f"{y:04d}-{mo:02d}-{d:02d}", "C-regex-dmy"
    m = RX2.search(s)
    if m:
        y, mo, d = int(m[1]), int(m[2]), int(m[3])
        if 1 <= mo <= 12 and 1 <= d <= 31 and 1500 < y < 2030:
            return f"{y:04d}-{mo:02d}-{d:02d}", "C-regex-iso"
    m = RX3.search(s)
    if m:
        mo, d, y = norm(m[2]), int(m[1]), int(m[3])
        if mo and 1 <= d <= 31 and 1500 < y < 2030:
            return f"{y:04d}-{mo:02d}-{d:02d}", "C-regex-texto"
    if KW.search(s):
        anos = RX4.findall(s)
        if anos: return anos[-1], "I-ano-disposicion"
    m = RX5.search(s)
    if m: return m[1], "C-boe-id"
    m = RX6.search(s)
    if m:
        mo = norm(m[3]); yy = int(m[1]); y = 1900+yy if yy >= 30 else 2000+yy; d = int(m[2])
        if mo and 1 <= d <= 31:
            return f"{y:04d}-{mo:02d}-{d:02d}", "I-ast-decreto"
    return None
def main():
    site = json.loads((ROOT/"www"/"explorer"/"bic_data.json").read_text(encoding="utf-8"))
    oficial = json.loads((ROOT/"raw"/"prot_dates.json").read_text(encoding="utf-8"))
    ok = dis = 0; per = Counter(); perok = Counter()
    for r in site["records"]:
        got = my_parse(r["pr"]) if r.get("pr") else None
        off = oficial.get(r["i"])
        if got is None and off is None: continue
        assert got is not None and off is not None, r["i"]
        per[off["prov"]] += 1
        if got == (off["date"], off["prov"]): ok += 1; perok[off["prov"]] += 1
        else:
            dis += 1
            print("DIVERGE", r["i"], repr(r["pr"]), off, got)
    print("acordo:", ok, "divergem:", dis)
    for k in sorted(per): print(f"  {k:20s} {perok[k]}/{per[k]} = {perok[k]/per[k]*100:.2f}%")
if __name__ == "__main__": main()
```

</details>

*Auditor 2/8 — datação. Todos os números deste relatório foram re-derivados em 2026-09-04 dos ficheiros citados; marcações [C] = confirmado no documento/ficheiro fonte indicado, [I] = inferido/secundário.*
