/** * @copyright Sister Software * @license AGPL-3.0 * @author Teffen Ellis, et al. * * `it-anac`: the OCDS release Autorità Nazionale Anticorruzione publishes for Italy's National * Database of Public Contracts, read for the addresses of the public bodies named on each contract. * * Input is the publisher's own JSON Lines: one OCDS release per line, as * `https://data.open-contracting.org/en/publication/117/download?name=.jsonl.gz` serves it * once decompressed. A release carries a `parties` array, and each party holds a `roles` list, a * `name`, an `identifier` and an `address` whose four fields are `locality`, `postalCode`, * `countryName` and `streetAddress`. The adapter streams the file through `JSONSpliterator`, so a * year's release never sits in memory whole. `opts.inputPath` is one `.jsonl` edition or a directory * holding several, which is what `fetchITANAC` writes. * * ## Which roles the adapter reads, and why those two * * The register's `addressRoles` for `it-procurement-grants-1` names exactly two fields, * `parties[roles=buyer].address` and `parties[roles=payer].address`. * Those two are the only address fields the release carries. Measured over the whole 2025 edition, 9,405 releases and * 29,823 party objects in three roles: a `supplier` party holds no `address` key at all on 11,075 of * 11,075, a `buyer` holds one on 9,381 of 9,381, and a `payer` on 9,359 of 9,367. So every address * in this release belongs to a contracting authority or to the office that pays. * Both of those are Italian public entities, and a contracting counterparty holds none. * * That is what makes this source admissible under the policy that a corpus carries the addresses of * organizations and no address of an identifiable natural person. The two admitted roles are held by * public bodies, and the one role whose parties can be sole traders publishes no address. * {@linkcode readANACParty} still applies a name check and refuses a party whose name takes the * `Surname, Firstname` form, because a later edition could carry one. On the 2025 edition the check * refuses no party: 0 of the 18,740 address-bearing buyer and payer parties take that form, while 56 * hold a comma inside an organization name — `PARCO NAZIONALE DEL CILENTO, VALLO DI DIANO E * ALBURNI` and `SETTORE PATRIMONIO, AMBIENTE E LAVORI PUBBLICI` among them — which is why the check * requires a single word on each side of the comma rather than a comma alone. * * ## The street line * * Italy writes the house number after the street name and joins the two with a space, which is what * `STREET_ORDERS` records as `number-last` and what `COMMA_JOINED_STREET_COUNTRIES` records by * leaving `IT` out. {@linkcode splitTrailingStreetLine} performs the split. Three Italian * conventions are normalized before it runs, each measured over the 2025 edition's 11,930 * non-placeholder street lines: * * 1. **A trailing copy of the locality.** 406 lines end with the value the publisher writes in its * own `locality` field — `CORSO ITALIA, 29 FIRENZE` against `locality` `FIRENZE`. The copy is * removed only where it equals that field, so the publisher's own record is what authorizes the * removal rather than a place-name list. * 2. **A civic marker.** 1,222 lines write `numero civico` as a marker before the number, spaced as * `PIAZZA ROOSEVELT N 1` or glued as `VIA ROMA N.68`. The marker is dropped, so the street is * `PIAZZA ROOSEVELT` rather than `PIAZZA ROOSEVELT N`. * 3. **`senza numero`.** 182 lines end `SNC` or `SN`, which states that the premise has no civic * number, so the tail is dropped and no `house_number` is composed from it. * * A kilometer point is not a house number. `S.S. 7 APPIA KM 671` names a point on a state highway, * so a number preceded by `KM` is left in the street line, which then refuses the row under the rule * below. * * **A street line holding a digit the split could not place refuses the row.** * {@linkcode ANACRefusal.StreetNumberUnplaced} covers 194 of those 11,930 lines, where the publisher * appends a qualifier after the number: `VIA CIRCONVALLAZIONE, 1 PARCO IDROSCALO`, `VIA FELTRE 57 * 32100 BELLUNO (BL)`, `VIA SAN SECONDO 29 BIS`. Emitting them would label a civic number as part of * a street name. A line with no digit at all is emitted as a street with no number, which is the * ordinary `PIAZZA CASTELLO` case and 1,416 of the 11,930. The remaining 10,320 carry a number. * * Two limits of the split are the publisher's own ambiguity rather than this reader's. `PIAZZA * UMBERTO 1` is Umberto I's square and reads identically to civic number 1, and the release gives no * field that separates them. A number written twice, as `CORSO ITALIA 55 55` and `VIALE DEI PIANETI 1 * 1`, leaves the first copy in the street name. Together with Italy's date-named streets — `VIA 4 * NOVEMBRE`, `PIAZZA 1 MAGGIO`, `VIA 2 AGOSTO 1980` — 93 of the 10,320 numbered lines keep a digit * inside the street, and most of those digits belong to the street's name. * * ## What the split was checked against * * The release publishes no preformatted address line, so no field of the publisher's states the * written form this adapter composes. The check available is reversibility against the publisher's * own string: the composed `street` and `house_number`, rejoined, reproduce the published * `streetAddress` on 11,736 of the 11,736 street lines this adapter emits, after the three * normalizations above and folding case, inner whitespace and the comma. Against the published * string as published, 10,027 of 11,736 reproduce it exactly under the same folding, and each of the * 1,709 that do not differs by one of those three normalizations. The split therefore loses no * published token. Which side of the split each token belongs on is the part no field of this * publisher's confirms. * * Every other component is a published value copied unchanged, which the same pass checked: 0 of the * 18,473 rows the 2025 edition admits carry a `locality`, `postcode` or `venue` that differs from the * field it came from. * * ## Placeholders and the postcode * * The publisher writes the literal `N.A.` where it holds no value: 476 postcodes, 6 localities and 6 * street lines on the 2025 edition. {@linkcode isPlaceholderValue} treats it as absent, so no row * carries `N.A.` as a postcode. * * An Italian CAP is five digits. A postcode that is neither a placeholder nor five digits is not a * CAP, and the row is refused rather than stripped of the field: the one such value on the 2025 * edition is `01238-000`, a São Paulo CEP on the payer `ISTITUTO ITALIANO DI CULTURA DI SAN PAOLO`, * whose address is in Brazil rather than Italy. A `countryName` naming any country but Italy refuses * the row for the same reason. Buyers write `ITALY` on all 9,381 rows and payers write no * `countryName` at all. * * ## Identity and license * * The row id is content-addressed over the aligned components. The publisher's party identifier * cannot serve: a buyer's `IT-CF` fiscal code carries more than one address on 17 of 3,119 distinct * codes, and one address recurs across hundreds of releases, so the identifier identifies the * organization rather than the address. * * The OCP Data Registry publishes this release under CC BY 4.0, which the address-source register * elected on 2026-09-30 with `spdx` `CC-BY-4.0`. CC BY requires credit, a link to the license and a * statement that changes were made, so every row records the license and * {@linkcode IT_ANAC_ATTRIBUTION} is the credit the model card carries. * * The adapter honors `opts.limit`, `opts.signal` and `opts.country`. */ import { type CanonicalRow, type CorpusAdapter } from "#types"; /** * Registry id for this adapter. * * Stamped into every row it emits, so a corpus record can be traced back to the dataset it came from. */ export declare const IT_ANAC_ADAPTER_ID = "it-anac"; /** * The one jurisdiction this adapter emits. * * ANAC's release covers Italian public contracting, so the set has one member. */ export declare const IT_ANAC_COUNTRIES: readonly string[]; /** * The license the address-source register elected for this publication. * * The OCP Data Registry's publication page for id 117 states Creative Commons Attribution * 4.0 International, and the register records it under `spdx` as `CC-BY-4.0`. */ export declare const IT_ANAC_LICENSE = "CC-BY-4.0"; /** * The credit CC BY 4.0 obliges, which the model card carries beside the license * and a statement that the data was changed. */ export declare const IT_ANAC_ATTRIBUTION = "Autorit\u00E0 Nazionale Anticorruzione (ANAC), via the OCP Data Registry"; /** * The two party roles the adapter reads. * * The register's `addressRoles` names these two fields alone. * The `supplier` role is the one role whose parties are contracting counterparties * rather than public bodies. * That role publishes no `address` object. */ export declare const IT_ANAC_PARTY_ROLES: readonly string[]; /** * Why a party's address did not become a row. * * The reading returns one of these rather than an empty result, so a caller counting * refusals knows which condition each party met. * `tools/dev-tools` and the adapter's test both count them. */ export declare const ANACRefusal: { /** * The party holds a role other than `buyer` or `payer`, so its address is out * of scope even where it carries one. */ readonly RoleNotAdmitted: "role-not-admitted"; /** * The party carries no `address` object, or every field in it is empty or a placeholder. */ readonly AddressAbsent: "address-absent"; /** * The address states no locality, so no component places it. */ readonly LocalityAbsent: "locality-absent"; /** * The street line holds a digit that the split could not read as a house number. */ readonly StreetNumberUnplaced: "street-number-unplaced"; /** * The postcode is neither absent nor a five-digit Italian CAP. */ readonly PostcodeNotCAP: "postcode-not-cap"; /** * `countryName` names a country other than Italy. */ readonly CountryNotItaly: "country-not-italy"; /** * The party name takes the `Surname, Firstname` form a natural person's name takes. */ readonly NamePersonalForm: "name-personal-form"; /** * The components the party yielded do not render as an Italian address line. */ readonly Unrenderable: "unrenderable"; /** * The rendered line carries one component, so it is a place name rather than an address. * * A party whose name holds no letter, whose postcode is the publisher's placeholder * and whose street line is absent leaves the locality alone. */ readonly ComponentsTooFew: "components-too-few"; }; /** * One of the {@link ANACRefusal} reasons. */ export type ANACRefusal = (typeof ANACRefusal)[keyof typeof ANACRefusal]; /** * What one party yielded: a row, or the reason it yielded none. */ export type ANACPartyReading = { readonly admitted: CanonicalRow; } | { readonly refused: ANACRefusal; }; /** * The OCDS address object, carrying the four fields this release populates. */ export interface ANACAddress { locality?: string | null; postalCode?: string | null; countryName?: string | null; streetAddress?: string | null; } /** * One `parties` member of a release. */ export interface ANACParty { name?: string | null; roles?: readonly string[] | null; address?: ANACAddress | null; } /** * One line of the input: an OCDS release. * * The adapter reads `parties` alone. * A release also carries `tender`, `awards` and `contracts`, and no address * sits on any of them in this publication. */ export interface ANACRelease { ocid?: string; parties?: readonly ANACParty[] | null; } /** * Whether a published value is the publisher's placeholder for an absent one, * or holds no letter and no digit at all. * * A placeholder read at face value would reach a corpus as a postcode spelled `N.A.`. */ export declare function isPlaceholderValue(value: string): boolean; /** * The street line with the three Italian conventions normalized away: a trailing copy * of the locality, a civic marker before the number, and a `senza numero` tail. * * `locality` is the publisher's own value for this party, which is what * authorizes removing the trailing copy. * An empty string leaves the line as it is. */ export declare function normalizeANACStreetLine(line: string, locality: string): string; /** * Read one party's address into a row, or report why it yielded none. * * Exported because the refusal reasons are the measurement this adapter is checked by: a caller reading * a whole edition counts them per reason rather than observing a row count it cannot account for. */ export declare function readANACParty(party: ANACParty): ANACPartyReading; export declare function createITANACAdapter(): CorpusAdapter; /** * The configured adapter instance registered with the corpus builder. */ export declare const itANACAdapter: CorpusAdapter; //# sourceMappingURL=adapter.d.ts.map