# GENERATED by governance/data_contract.py — do not edit.
#
# Regenerate:  python -m governance.data_contract --write
# Check:       python -m governance.data_contract --check
#
# Everything below is read from a register that already gates the build: the licence register
# (docs/LICENSE_REGISTER.md), the catalogue (catalog/products.py), the published dictionary
# (pipelines/publish/release.py), the freshness SLAs (quality/freshness_sla.yml) and the release
# floors (quality/floors.yml). Editing this file by hand makes it disagree with the pipeline it
# describes, which is the one thing a data contract must never do.
dataContractSpecification: 1.1.0
id: certvas-af-tender
info:
  title: African Public Procurement
  version: '2026.09'
  description: Zambian, South African and Nigerian federal public tenders normalised onto OCDS, licence-clean.
  owner: Certvas
  contact:
    name: Certvas
    email: mandase@certvas.com
    url: https://certvas.com/skus/af-tender.html
terms:
  usage: "Licensed per the Certvas Data Licence Agreement. Every row carries `license_class`, the register\
    \ code governing permitted use of the SOURCE it was extracted from — redistribution rights differ\
    \ per source and the register is authoritative:\n  - S001 GLEIF LEI Golden Copy (L1+L2) — CLEARED;\
    \ basis: CC0\n  - S007 South Africa OCPO Transparency Portal (eTenders) — CLEARED; basis: Explicit\
    \ open license incl. commercial use\n  - S012 Manually-curated public issuer identities (non-LEI)\
    \ — CLEARED; basis: Public fact (name + jurisdiction + exchange ticker from public exchange listings)\n\
    \  - S034 Nigeria NOCOPO federal OCDS (BPP) — CLEARED; basis: ODC-PDDL — publisher's own licence page\
    \ names PDDL with Share/Create/Adapt grants (VERIFIED 2026-08-08, evidence docs/licenses/S034/); site\
    \ Terms-page boilerplate conflict recorded in evidence — /license is the data-directed instrument\n\
    \  - S043 Zambia ZPPA e-GP OCDS — CLEARED; basis: ODC-PDDL — publisher's OWN licence page (zppa.org.zm/ocds-license)\
    \ names the ODC Public Domain Dedication and Licence with Share/Adapt grants — strongest possible\
    \ grant, commercial reuse unrestricted (VERIFIED 2026-08-09, evidence docs/licenses/S043/). OCP registry\
    \ lists CC BY 4.0 (discrepancy recorded; both permit commercial reuse — the publisher's data-directed\
    \ /ocds-license controls). Same instrument as Nigeria S034."
  limitations: "Derived from primary filings and official publications, never from exchange price feeds;\
    \ Certvas holds no market-data licence and publishes no prices. Source conditions that travel with\
    \ the data:\n  - S001: None\n  - S007: Attribution; OCDS format\n  - S012: Identity facts only — no\
    \ natural persons; each seed row cites its public listing; used only to give a non-LEI issuer a `dataco_id`\
    \ so its S009 filings can be extracted\n  - S034: Attribution to BPP good practice; date-sanity +\
    \ ocid-dedupe guardrails REQUIRED before first run (award dates 1949–2919, 93 bad ocid prefixes, batch\
    \ release-dates); federal completeness caveat in SKU docs; PDF /license at first ingest; changedetection\
    \ watch on /license; no natural persons\n  - S043: Attribution to ZPPA good practice (CC-BY 4.0 conservative\
    \ fallback → encode attribution regardless); OCDS date-sanity + ocid-dedupe guardrails per S034 before\
    \ first run; no natural persons"
  billing: Certvas — African Public Procurement Sample (free) — FREE; Certvas — African Public Procurement
    (AF-TENDER) — $129
  noticePeriod: P30D
models:
  gold_tender:
    description: African Public Procurement — gold_tender.
    type: table
    fields:
      ocid:
        type: string
        description: Open Contracting ID — globally unique process identifier.
      country:
        type: string
        description: ISO country code of the procuring jurisdiction.
      tender_id:
        type: string
      title:
        type: string
        description: Tender title as published.
      status:
        type: string
        description: Tender status from the OCDS release.
      category:
        type: string
        description: Procurement category (goods / works / services).
      sector:
        type: string
      sector_source:
        type: string
      value_amount:
        type: double
        description: 'Tender value exactly as published — NOT normalised. SA eTenders (S007) sends 0 on
          every release rather than omitting the field, so a 0 here means ''not disclosed'', not ''no
          value''. The roll-up tables apply that reading: they sum only positive amounts and report NULL
          where a group discloses none.'
      value_currency:
        type: string
        description: ISO currency of the value, as published; may be stated even where no amount is. In
          the roll-up tables this column reports 'MIXED' when a group spans more than one currency, and
          the group's total is NULL — values are never summed across currencies and no FX conversion is
          applied.
      release_date:
        type: timestamp_ntz
        description: OCDS release date.
      tender_start:
        type: timestamp_ntz
      tender_end:
        type: timestamp_ntz
      buyer_name:
        type: string
        description: Procuring entity (organization only).
      buyer_id:
        type: string
      observed_at:
        type: timestamp_tz
      source_ref:
        type: string
        description: Reference back to the OCDS release package.
      license_class:
        type: string
        description: License Register code (OCDS feed is openly licensed).
    quality: &id001
    - type: custom
      engine: certvas
      description: 'arithmetic cross-validation pass rate: min 0.8. Enforced by exports/release_gate.py
        — below its floor a release auto-HOLDs and does not publish.'
      specification: arithmetic_pass_rate min 0.8
    - type: custom
      engine: certvas
      description: 'dual-model agreement rate: min 0.95. Enforced by exports/release_gate.py — below its
        floor a release auto-HOLDs and does not publish.'
      specification: dual_model_agreement_rate min 0.95
    - type: custom
      engine: certvas
      description: 'minimum extraction confidence: min 0.85. Enforced by exports/release_gate.py — below
        its floor a release auto-HOLDs and does not publish.'
      specification: extraction_confidence_min min 0.85
    - type: custom
      engine: certvas
      description: 'Soda check failures: max 0. Enforced by exports/release_gate.py — below its floor
        a release auto-HOLDs and does not publish.'
      specification: soda_failures max 0
    - type: custom
      engine: certvas
      description: 'Pandera schema failures: max 0. Enforced by exports/release_gate.py — below its floor
        a release auto-HOLDs and does not publish.'
      specification: pandera_failures max 0
    - type: custom
      engine: certvas
      description: 'Arelle XBRL validation errors: max 0. Enforced by exports/release_gate.py — below
        its floor a release auto-HOLDs and does not publish.'
      specification: arelle_errors max 0
  gold_tender_award:
    description: African Public Procurement — gold_tender_award.
    type: table
    fields:
      ocid:
        type: string
        description: Open Contracting ID — globally unique process identifier.
      award_id:
        type: string
      title:
        type: string
        description: Tender title as published.
      status:
        type: string
        description: Tender status from the OCDS release.
      award_date:
        type: timestamp_ntz
      value_amount:
        type: double
        description: 'Tender value exactly as published — NOT normalised. SA eTenders (S007) sends 0 on
          every release rather than omitting the field, so a 0 here means ''not disclosed'', not ''no
          value''. The roll-up tables apply that reading: they sum only positive amounts and report NULL
          where a group discloses none.'
      value_currency:
        type: string
        description: ISO currency of the value, as published; may be stated even where no amount is. In
          the roll-up tables this column reports 'MIXED' when a group spans more than one currency, and
          the group's total is NULL — values are never summed across currencies and no FX conversion is
          applied.
      supplier_id:
        type: string
      supplier_name:
        type: string
      country:
        type: string
        description: ISO country code of the procuring jurisdiction.
      observed_at:
        type: timestamp_tz
      source_ref:
        type: string
        description: Reference back to the OCDS release package.
      license_class:
        type: string
        description: License Register code (OCDS feed is openly licensed).
    quality: *id001
  gold_tender_party_published:
    description: African Public Procurement — gold_tender_party_published.
    type: table
    fields:
      ocid:
        type: string
        description: Open Contracting ID — globally unique process identifier.
      party_id:
        type: string
      name:
        type: string
        description: 'Party name exactly as published, in gold_tender_party_published. MAY BE EMPTY: three
          of 3,831 parties in the SA eTenders feed (S007) carry a party_id and a role but no name, and
          that is reproduced rather than filled or dropped. An invented name would be fabrication and
          a dropped row would silently lose an otherwise complete award record, so the blank is carried
          as the fact it is. Natural persons are excluded from this table entirely — a blank name is a
          publisher omission, not a suppression.'
      roles:
        type: string
      identifier_scheme:
        type: string
      identifier_id:
        type: string
      party_type:
        type: string
      country:
        type: string
        description: ISO country code of the procuring jurisdiction.
      observed_at:
        type: timestamp_tz
      source_ref:
        type: string
        description: Reference back to the OCDS release package.
      license_class:
        type: string
        description: License Register code (OCDS feed is openly licensed).
    quality: *id001
  gold_tender_supplier_stats:
    description: African Public Procurement — gold_tender_supplier_stats.
    type: table
    fields:
      supplier_name:
        type: string
      country:
        type: string
        description: ISO country code of the procuring jurisdiction.
      award_count:
        type: long
      tender_count:
        type: long
      total_award_value:
        type: double
      currency:
        type: string
      first_award_date:
        type: timestamp_ntz
      last_award_date:
        type: timestamp_ntz
    quality: *id001
  gold_tender_buyer_stats:
    description: African Public Procurement — gold_tender_buyer_stats.
    type: table
    fields:
      buyer_name:
        type: string
        description: Procuring entity (organization only).
      country:
        type: string
        description: ISO country code of the procuring jurisdiction.
      tender_count:
        type: long
      total_tender_value:
        type: double
      currency:
        type: string
      first_tender_date:
        type: timestamp_ntz
      last_tender_date:
        type: timestamp_ntz
    quality: *id001
  gold_tender_monthly:
    description: African Public Procurement — gold_tender_monthly.
    type: table
    fields:
      country:
        type: string
        description: ISO country code of the procuring jurisdiction.
      month:
        type: date
      category:
        type: string
        description: Procurement category (goods / works / services).
      tender_count:
        type: long
      total_value:
        type: double
      currency:
        type: string
    quality: *id001
  gold_issuer_award:
    description: African Public Procurement — gold_issuer_award.
    type: table
    fields:
      ocid:
        type: string
        description: Open Contracting ID — globally unique process identifier.
      country:
        type: string
        description: ISO country code of the procuring jurisdiction.
      title:
        type: string
        description: Tender title as published.
      status:
        type: string
        description: Tender status from the OCDS release.
      category:
        type: string
        description: Procurement category (goods / works / services).
      value_amount:
        type: double
        description: 'Tender value exactly as published — NOT normalised. SA eTenders (S007) sends 0 on
          every release rather than omitting the field, so a 0 here means ''not disclosed'', not ''no
          value''. The roll-up tables apply that reading: they sum only positive amounts and report NULL
          where a group discloses none.'
      value_currency:
        type: string
        description: ISO currency of the value, as published; may be stated even where no amount is. In
          the roll-up tables this column reports 'MIXED' when a group spans more than one currency, and
          the group's total is NULL — values are never summed across currencies and no FX conversion is
          applied.
      release_date:
        type: date
        description: OCDS release date.
      name:
        type: string
        description: 'Party name exactly as published, in gold_tender_party_published. MAY BE EMPTY: three
          of 3,831 parties in the SA eTenders feed (S007) carry a party_id and a role but no name, and
          that is reproduced rather than filled or dropped. An invented name would be fabrication and
          a dropped row would silently lose an otherwise complete award record, so the blank is carried
          as the fact it is. Natural persons are excluded from this table entirely — a blank name is a
          publisher omission, not a suppression.'
      buyer_name:
        type: string
        description: Procuring entity (organization only).
      source_ref:
        type: string
        description: Reference back to the OCDS release package.
      license_class:
        type: string
        description: License Register code (OCDS feed is openly licensed).
      value_check:
        type: string
        description: 'How far to trust an award''s value_amount. Five flags, on which nothing is removed:
          shared_contract_value, two or more different suppliers on the tender carry this identical value,
          usually a panel contract''s ceiling repeated per supplier (equal lots also match), so it is
          left out of every award total; over_10x_tender, more than ten times its own tender''s published
          value; large_share_of_total, 5% or more of all award value in its country and currency; no_tender_value,
          the tender published no value to compare; consistent_with_tender, otherwise. Ten times is a
          flag, not proof: many tender values are unit prices. withheld: value_amount is NULL because
          Certvas withheld it by a recorded decision on the publisher''s own record (pipelines/tenders/award_value_withheld.yml);
          the award, supplier and dates remain. NULL when the award has no value.'
      value_withheld:
        type: boolean
        description: 'gold_tender only: true when the tender''s value_amount is NULL because Certvas withheld
          it by a recorded decision on the publisher''s own record (pipelines/tenders/award_value_withheld.yml,
          withheld_tender_values). The tender, its buyer and its dates remain.'
      dataco_id:
        type: string
        description: Certvas ID of the listed issuer (gold_issuer_award, gold_entity_served); joins AF-FUND's
          dataco_id.
      match_method:
        type: string
        description: 'How an award''s supplier was linked to the issuer: via_lei (the supplier resolves
          to the issuer''s GLEIF-verified LEI), strict_name (the same legal name in the same country),
          or both joined by ''+''. A supplier matching two issuers is not linked.'
      lei:
        type: string
        description: ISO 17442 Legal Entity Identifier, where GLEIF confirms one for the issuer; NULL
          for an issuer with no GLEIF record (common on the smaller African exchanges).
      legal_name:
        type: string
        description: The issuer's legal name (GLEIF's where it has an LEI, else the listing's).
      entity_type:
        type: string
        description: company, fund, gov_body or other. Sole proprietors are never carried.
      jurisdiction:
        type: string
        description: Jurisdiction of registration, ISO 3166 (e.g. ZA, NG, KE-30).
      entity_status:
        type: string
        description: Registration status as GLEIF reports it (ACTIVE / INACTIVE); NULL for seed issuers.
      registry_ids:
        type: string
        description: 'JSON object of home-registry identifiers from GLEIF ({authority: id}); NULL when
          none.'
      award_date: {}
      award_id: {}
      observed_at: {}
      supplier_name: {}
    quality: *id001
  gold_entity_served:
    description: African Public Procurement — gold_entity_served.
    type: table
    fields:
      ocid:
        type: string
        description: Open Contracting ID — globally unique process identifier.
      country:
        type: string
        description: ISO country code of the procuring jurisdiction.
      title:
        type: string
        description: Tender title as published.
      status:
        type: string
        description: Tender status from the OCDS release.
      category:
        type: string
        description: Procurement category (goods / works / services).
      value_amount:
        type: double
        description: 'Tender value exactly as published — NOT normalised. SA eTenders (S007) sends 0 on
          every release rather than omitting the field, so a 0 here means ''not disclosed'', not ''no
          value''. The roll-up tables apply that reading: they sum only positive amounts and report NULL
          where a group discloses none.'
      value_currency:
        type: string
        description: ISO currency of the value, as published; may be stated even where no amount is. In
          the roll-up tables this column reports 'MIXED' when a group spans more than one currency, and
          the group's total is NULL — values are never summed across currencies and no FX conversion is
          applied.
      release_date:
        type: date
        description: OCDS release date.
      name:
        type: string
        description: 'Party name exactly as published, in gold_tender_party_published. MAY BE EMPTY: three
          of 3,831 parties in the SA eTenders feed (S007) carry a party_id and a role but no name, and
          that is reproduced rather than filled or dropped. An invented name would be fabrication and
          a dropped row would silently lose an otherwise complete award record, so the blank is carried
          as the fact it is. Natural persons are excluded from this table entirely — a blank name is a
          publisher omission, not a suppression.'
      buyer_name:
        type: string
        description: Procuring entity (organization only).
      source_ref:
        type: string
        description: Reference back to the OCDS release package.
      license_class:
        type: string
        description: License Register code (OCDS feed is openly licensed).
      value_check:
        type: string
        description: 'How far to trust an award''s value_amount. Five flags, on which nothing is removed:
          shared_contract_value, two or more different suppliers on the tender carry this identical value,
          usually a panel contract''s ceiling repeated per supplier (equal lots also match), so it is
          left out of every award total; over_10x_tender, more than ten times its own tender''s published
          value; large_share_of_total, 5% or more of all award value in its country and currency; no_tender_value,
          the tender published no value to compare; consistent_with_tender, otherwise. Ten times is a
          flag, not proof: many tender values are unit prices. withheld: value_amount is NULL because
          Certvas withheld it by a recorded decision on the publisher''s own record (pipelines/tenders/award_value_withheld.yml);
          the award, supplier and dates remain. NULL when the award has no value.'
      value_withheld:
        type: boolean
        description: 'gold_tender only: true when the tender''s value_amount is NULL because Certvas withheld
          it by a recorded decision on the publisher''s own record (pipelines/tenders/award_value_withheld.yml,
          withheld_tender_values). The tender, its buyer and its dates remain.'
      dataco_id:
        type: string
        description: Certvas ID of the listed issuer (gold_issuer_award, gold_entity_served); joins AF-FUND's
          dataco_id.
      match_method:
        type: string
        description: 'How an award''s supplier was linked to the issuer: via_lei (the supplier resolves
          to the issuer''s GLEIF-verified LEI), strict_name (the same legal name in the same country),
          or both joined by ''+''. A supplier matching two issuers is not linked.'
      lei:
        type: string
        description: ISO 17442 Legal Entity Identifier, where GLEIF confirms one for the issuer; NULL
          for an issuer with no GLEIF record (common on the smaller African exchanges).
      legal_name:
        type: string
        description: The issuer's legal name (GLEIF's where it has an LEI, else the listing's).
      entity_type:
        type: string
        description: company, fund, gov_body or other. Sole proprietors are never carried.
      jurisdiction:
        type: string
        description: Jurisdiction of registration, ISO 3166 (e.g. ZA, NG, KE-30).
      entity_status:
        type: string
        description: Registration status as GLEIF reports it (ACTIVE / INACTIVE); NULL for seed issuers.
      registry_ids:
        type: string
        description: 'JSON object of home-registry identifiers from GLEIF ({authority: id}); NULL when
          none.'
      observed_at: {}
    quality: *id001
servicelevels:
  freshness:
    description: "Maximum age of the newest observation, per table, from quality/freshness_sla.yml. Suspensions\
      \ are stated rather than hidden:\n  - gold_tender: 20 days"
    timestampField: observed_at
  availability:
    description: 'No uptime commitment. Certvas is run by one person and the API is best-effort: automated
      checks alert the founder when it fails, but there is no on-call rota. Data is delivered as versioned
      files, which do not depend on the API being up.'
  support:
    description: No contractual response time. One person, one time zone (Africa/Gaborone, UTC+2). mandase@certvas.com
      is read daily on working days.
  backup:
    description: No stated recovery objective. Raw source documents are immutable and archived; a published
      pack is reproducible from them, but no RTO or RPO is promised.
  retention:
    description: No deletion schedule. Published versions are permanent by design — a point-in-time dataset
      whose history can be withdrawn is not point-in-time. Superseded versions stay resolvable via their
      DOI.
