/** * @copyright Sister Software * @license AGPL-3.0 * @author Teffen Ellis, et al. * * Harvest the INSPIRE Addresses archives the Gobierno de Navarra publishes into the directory * `#es/adapters/navarra/adapter` reads. * * The service is one ATOM document rather than two levels. The service document at * `https://filescartografia.navarra.es/2_CARTOGRAFIA_TEMATICA/2_7_CATASTRO/2_7_3_INSPIRE_ATOM/2_7_3_3_AD/Addresses_ServiceATOM_Navarra.xml` * lists one entry per partition, and each entry links that partition's zipped GML directly. * Measured 2026-10-03: the document answered http 200 with 671,131 bytes holding 272 entries, * sha256 `6971dd61d87e2fc047e953e956b09c6cc88aaa92e16ffb3bbc4750a1edb1ab60`. * * Four properties of this service decide the harvest's shape. * * 1. **The archive link is the `enclosure` link, and the `alternate` link is not it.** Each entry * carries six links, three of which address the same archive under `enclosure`, `section` and * `alternate`. The entry's *first* `alternate` link is the relative href * `Addresses_DatasetATOM_Navarra.xml`, which is the dataset feed, so a reader taking the first * `alternate` link downloads an ATOM document in place of the archive. This harvest takes * `enclosure` and refuses an entry that carries none. * 2. **An entry leaves its own area unstated.** Every one of the 272 entries is titled * `Address Navarra` and every `` is empty, so the archive's own file name, * `AD_Navarra_.gml.zip`, is the only statement of which partition an entry offers. The * numbers are not contiguous: the 272 published partitions run from 1 to 908. A partition * therefore cannot be selected by place from the feed, and this module claims no mapping from a * partition number to a municipality. * 3. **The `length` attribute is one number repeated.** All 272 entries claim 34,987 bytes, while * partition 1 delivers 7,476. Every byte count this module records is counted off the delivered * body. * 4. **Every entry states one ``, which is also the feed's own.** All 272 read * `2026-04-14T12:06:58Z`, so the value dates the publication rather than the file. It is still * the freshness signal a re-run compares, because a new edition moves it. A skip requires the * feed to state a value: where it states none, both sides read an empty string and an equality * test would keep an archive of unknown age for as long as the feed stayed silent. * * A full harvest is 273 requests and roughly 10 MB: a systematic sample of 10 of the 272 archives * on 2026-10-03 ran from 7,476 to 112,830 bytes. The harvest is bound by round trips rather than by * bandwidth and is dispatched serially through one `APIClient` at * {@linkcode ES_NAVARRA_REQUEST_INTERVAL_MS}. * * The archives stay compressed. `#es/adapters/navarra/adapter` reads the GML member through * `inspireGMLChunks`, which inflates from the archive, and partition 1's member is 307,117 bytes * against its archive's 7,476. */ import { APIClient } from "@mailwoman/core/api"; import { type PathBuilderLike } from "path-ts"; import type { AtomFeed } from "#tools/fetch/atom"; import type { BaseFetchOptions, FetchSummary, SourceCollectionManifest, SourceManifest } from "#tools/fetch/download"; /** * The ATOM service document, which lists one zipped GML partition per entry. */ export declare const ES_NAVARRA_SERVICE_FEED_URL = "https://filescartografia.navarra.es/2_CARTOGRAFIA_TEMATICA/2_7_CATASTRO/2_7_3_INSPIRE_ATOM/2_7_3_3_AD/Addresses_ServiceATOM_Navarra.xml"; /** * The attribution the publisher's license requires. * * Every entry's `` elects `Creative Commons Attribution 4.0 International (CC BY 4.0)`, * whose attribution clause requires naming the creator, so a model card carrying * this source credits the publisher. */ export declare const ES_NAVARRA_ATTRIBUTION = "Gobierno de Navarra"; /** * The minimum spacing between two requests to `filescartografia.navarra.es`, in milliseconds. * * Four requests per second against one government host. * The publisher states no rate limit, and a full harvest is 273 requests. */ export declare const ES_NAVARRA_REQUEST_INTERVAL_MS = 250; /** * One partition, as the service document states it. */ export interface ESNavarraPartition { /** * The partition number the archive's file name states. * * Kept as the digits the publisher wrote rather than as a number, because it * is part of a file name rather than a quantity. */ partition: string; /** * The entry's title, which reads `Address Navarra` on every entry. */ title: string; /** * The entry's ``, which dates the publication. */ updated: string; archiveURL: string; /** * The file name the archive is written under, which is the name the publisher gave it * and the name the adapter globs. */ filename: string; } /** * The partitions the service document lists. * * @throws When the document holds no entry, when an entry carries no `enclosure` link, when a * linked archive is not named `AD_Navarra_.gml.zip`, or when two entries name one archive. * Each of those would otherwise write zero archives while reporting a completed run. */ export declare function readESNavarraServiceFeed(feed: AtomFeed): readonly ESNavarraPartition[]; /** * One archive, as the harvest's manifest records it. */ export interface ESNavarraArchiveEntry extends SourceManifest { partition: string; /** * The `` the service document stated when this archive was fetched. * * A later run that reads the same value leaves the file alone. * The publisher moves it when it publishes a new edition. */ feed_updated: string; /** * The `Last-Modified` the host served the archive under, or `null` where it served none. */ last_modified: string | null; } /** * The harvest's `MANIFEST.json`. */ export interface ESNavarraHarvestManifest extends SourceCollectionManifest { /** * How many partitions the service document listed when this manifest was written. * * The denominator for `files`, read from the feed on each run rather than fixed here. */ partitions_listed: number; files: ESNavarraArchiveEntry[]; } export interface HarvestESNavarraOptions { /** * Where the archives and the manifest are written, which is the adapter's `inputPath`. */ outputDir: PathBuilderLike; /** * Harvest only these partition numbers, for a probe or for a repair of named partitions. * * A number the service document does not list is reported as a failure rather than ignored. */ partitions?: readonly string[]; /** * Stop after this many partitions, counting the ones already on disk. */ limit?: number; /** * Re-read the sha256 of every archive already on disk instead of comparing its byte count. * * The default compares the recorded byte count against the file's size, which is one `stat`. */ verifyDigests?: boolean; signal?: AbortSignal; report?: (line: string) => void; } /** * Harvest the partitions the service document lists into `options.outputDir`. * * The client is injected so the harvest is testable without the network and so a caller decides * the pacing. {@linkcode fetchESNavarra} is the registry entry point and supplies both. * * @returns What was fetched, what the feed states is already recorded, and which partitions failed. * @throws When the service document lists no partition, which would otherwise answer * `{fetched: 0, skipped: 0, failed: 0}` and read to a caller as a completed fetch of an empty publisher. */ export declare function harvestESNavarra(client: Pick, options: HarvestESNavarraOptions): Promise; /** * The path `#es/adapters/navarra/adapter` reads, given the root a fetch wrote under. * * The adapter takes one archive or a directory holding them, and a harvest writes a directory. */ export declare function esNavarraInputPath(outRoot: BaseFetchOptions["outRoot"]): PathBuilderLike; export interface FetchESNavarraOptions extends BaseFetchOptions, Pick { /** * The minimum spacing between two requests, in milliseconds. * * Defaults to {@linkcode ES_NAVARRA_REQUEST_INTERVAL_MS}. */ minRequestIntervalMs?: number; } /** * Harvest the Gobierno de Navarra's INSPIRE Addresses partitions into `/es-navarra/`. * * Re-runnable: a partition whose recorded publication date is still the one the feed states, * and whose archive is still on disk at the recorded length, costs no request. */ export declare function fetchESNavarra(options: FetchESNavarraOptions, report?: (line: string) => void): Promise; //# sourceMappingURL=navarra.d.ts.map