Skip to article

Procurement lifecycle data · Directed graph workflow

Procurement lifecycle links audited as typed evidence.

Verify procurement lifecycle-link releases with Python and DuckDB, then traverse typed notice, award, supplier and contract edges without inventing missing links.

Published September 2, 202625 min readBy DanielReviewed by Alexandra

Treat procurement lifecycle data as a typed directed graph: verify every edge file, preserve its source and node types, traverse only relationships the publisher actually declares, and report unresolved paths instead of manufacturing a complete tender-to-contract story. A link row is not a procurement, confidence = 1.0 is not independent verification, and a normalization edge is not an opportunity-to-award transition.

This worked example pins the two WebTruffle releases dated August 31, 2026: government tenders and RFPs and government contract awards. Their lifecycle-link products contain 251,501 exact official-identifier edges in total, but the edge types answer different questions:

  • the tender release contributes 162,684 edges, mostly source-release-to-normalized-notice mappings;
  • the awards release contributes 88,817 edges from result notices to awards and from awards to suppliers or explicit contracts; and
  • neither file claims a universal end-to-end match from every pre-award opportunity to its eventual result.

The downloadable Python and DuckDB recipe verifies both evidence roots and six declared assets, proves the edge invariants, builds typed node and edge summaries, traverses only permitted two-hop paths, and writes bounded outputs with a provenance receipt.

Lifecycle-link release receiptTwo link products preserve five typed relationships.
Exact identifier links

Tender links

162,684

Edge rows
normalized_as
161,371
declares_contract
1,313

Award links

88,817

Edge rows
declares_award
30,463
awarded_to
57,469
results_in_contract
885

Fields shared by every released link row

Identifier match
exact
Confidence
1.0
Inferred
false

Use this sequence:

  1. Pin both release tags and independently record each manifest's exact byte count and SHA-256.
  2. Verify each manifest before trusting the schema, source-health, or lifecycle-link hashes declared inside it.
  3. Require the expected release dates, schema version 2.0, product grains, source statuses, and lifecycle-link row counts.
  4. Verify the two 15-column CSV headers exactly and load all columns as text.
  5. Add a dataset_id namespace before combining the two edge ledgers. Do not assume an internal hash in one product names the same node in the other.
  6. Require one unique link_id per file, non-empty typed endpoints, link_method = exact_official_identifier, confidence = 1.0, and is_inferred = False.
  7. Group by dataset, source, relationship, entity type, and lifecycle stage before interpreting any count.
  8. Treat normalized_as as a representation edge. It does not mean that a planning or opportunity record became an award.
  9. Traverse source_notice → award → supplier only through declares_award followed by awarded_to.
  10. Traverse source_notice → award → contract only through declares_award followed by results_in_contract.
  11. Keep every supplier path. Collapse to award grain before joining award-level money from another product.
  12. Publish source-specific coverage. An absent results_in_contract edge can be structural for that source and release, not proof that no contract exists.
  13. Keep candidate or fuzzy reconciliation outside the exact graph and label it for human review.
  14. Export deterministic bounded path samples plus hashes, queries, input identities, limitations, and runtime details.

Start with the tenders dataset and awards dataset when you need the published files. This page owns lifecycle-link artifact verification and directed graph analysis. The government contracts database guide owns the conceptual cross-region model, while the contract tracker guide owns recurring change detection and alert routing.

A graph vocabulary prevents the most common counting error.

  • A node is one typed entity in one namespace: a source release, normalized notice, award, supplier, or contract.
  • An edge is one directed, typed assertion between two nodes.
  • A path is a permitted sequence of edges, such as notice → award → supplier.
  • A procurement process is a real-world lifecycle that may contain many notices, lots, awards, suppliers, contracts, amendments and payments.

Those objects are not interchangeable. The combined 251,501 rows are edge assertions, not 251,501 procurements. One award can have several suppliers, one result notice can declare several awards, and one award can result in more than one contract.

The Open Contracting Data Standard models planning, tender, award, contract and implementation stages within a contracting process. Its identifier guidance says an OCID joins information about one process, while release, award and contract identifiers have scopes that must be preserved. It also explicitly permits multiple contracts to refer to one award. (OCDS identifiers)

The release edge schema is deliberately narrower than “everything that might be related.” Every row states:

Each directed edge runs from the typed from_* endpoint to the typed to_* endpoint; the relationship name does not override that direction.

dataset namespace
source
from entity type + ID + stage
to entity type + ID + stage
relationship
link method
confidence
inference flag
evidence + source URL + observation date

confidence = 1.0 means the identifiers were equal under the declared deterministic method. It does not prove that the source publisher's relationship is legally complete, current, or factually correct, and it does not authorize a new relationship between nodes that merely have similar titles.

Typed lifecycle pathNotice → award → supplier or contract.
Follow endpoints, not labels alone
Node 01

Source notice

Source-local notice identifier

declares_award30,463
Node 02

Award

Source-local award identifier

awarded_to

Named supplier

57,469
results_in_contract

Explicit contract

885

The tender graph provides a parallel evidence lane

normalized_as

161,371 edges connect a source tender record to its normalized tender record.

declares_contract

1,313 edges preserve an explicit source contract declared by tender evidence.

Pin both August 31 releases

The two tags share a target date but were built independently. Treat them as two evidence roots, not one transaction.

tenders release tag         2026-08-31
tenders generated at        2026-09-01T13:25:10Z
tenders manifest bytes      33,720
tenders manifest SHA-256    c50e511d5ff5fe80411d80cf0e07490389f138fa2afca5572167ad7b2ba2c55f
tenders lifecycle rows      162,684

awards release tag          2026-08-31
awards generated at         2026-09-01T13:38:49Z
awards manifest bytes       31,102
awards manifest SHA-256     570b8ad004459405201393cb8ba3c81386f55a625ff44175b29840ad5e438d29
awards lifecycle rows       88,817

The verified tender manifest points to:

AssetBytesSHA-256
schema.json18,7170a31059590fcb48919458e7af9f97ebce04a1c9a41b6c91883578568634ba8b3
source-health.json2,67335f2266b60aa6f46f127b0b7254782768d074d354d6c35ad65d85507b6044665
lifecycle-links.csv73,352,297d2600170c9e3f44b4471778c00556d5862eda9d8bb376661f861ad6e02397d62

The verified awards manifest points to:

AssetBytesSHA-256
schema.json15,0894fb97f596bb4c0ea34f8bdbeea6cfb986f4e94344f9bce739c45986c7746ff81
source-health.json1,9943c769d2cdd3f16a7c27feca8d77b19e7dbdf2eb704d27a54d6bfa263570a6704
lifecycle-links.csv41,722,6736781b8516de652d3727ec1c30699134a2c57cd8ed38bd33f37027b9267479527

For a machine-verifiable receipt, pin the tender manifest and lifecycle-link CSV separately from the awards manifest and lifecycle-link CSV. The release tag is the human-facing root; the pinned byte count and SHA-256 are the acceptance evidence.

Evidence rootSources requiredLifecycle grainRows
Tenders and RFPsContracts Finder, Find a Tender, SAM.gov, TEDOne exact official-identifier relationship162,684
Contract awardsContracts Finder, Find a Tender, TEDOne exact official-identifier relationship88,817

The matching target dates do not imply synchronized source clocks. A notice publication time, OCDS release date, source update time, observation date, award date, contract signature date and dataset generation time remain separate. In particular, an observation date is not an event, award or contract date.

The trusted order is root first, declarations second:

import hashlib
from pathlib import Path

def sha256_and_size(path: Path) -> tuple[str, int]:
    digest = hashlib.sha256()
    size = 0
    with path.open("rb") as source:
        for chunk in iter(lambda: source.read(1024 * 1024), b""):
            digest.update(chunk)
            size += len(chunk)
    return digest.hexdigest(), size

actual_hash, actual_bytes = sha256_and_size(
    Path("tenders-manifest.json")
)
assert actual_bytes == 33_720
assert actual_hash == (
    "c50e511d5ff5fe80411d80cf0e074903"
    "89f138fa2afca5572167ad7b2ba2c55f"
)

Once the independently pinned manifest passes, require its dataset ID, target date, schema version, lifecycle product declaration, and exact asset identities. Repeat that process for the awards root. Do not let one verified manifest authenticate a file from the other repository.

Before analysis, the recipe also requires:

  • every expected source has an accepted status in its own source-health.json;
  • the lifecycle product declares inference_policy: No fuzzy, name-based, or probabilistic links.;
  • both CSVs have exactly the same 15 headers in the same order;
  • every link_id is non-empty and unique within its file;
  • the typed endpoints and relationship are non-empty;
  • every row uses exact_official_identifier, confidence 1.0, and False for is_inferred; and
  • the exact relationship composition reconciles to the declared row count.

Load the verified local CSVs as text. DuckDB's all_varchar option disables type inference, which protects opaque identifiers, dates and decimal strings until one query explicitly casts them. (DuckDB CSV reader)

CREATE TABLE tender_edges AS
SELECT
  'government-tenders-rfps' AS dataset_id,
  *
FROM read_csv(
  $tender_file,
  header = true,
  all_varchar = true
);

CREATE TABLE award_edges AS
SELECT
  'government-contract-awards' AS dataset_id,
  *
FROM read_csv(
  $award_file,
  header = true,
  all_varchar = true
);

Use parameters or safely quoted file paths rather than concatenating untrusted input into SQL.

Model a typed directed edge ledger

An identifier string alone is not a global key. Build a node key from at least:

dataset_id | source | entity_type | entity_id

Keep stage as an attribute rather than silently embedding it into identity. The same real-world process can publish several stage-specific objects. Conversely, an identifier that looks the same in two different source systems need not name the same thing.

CREATE VIEW all_edges AS
SELECT * FROM tender_edges
UNION ALL BY NAME
SELECT * FROM award_edges;

CREATE VIEW typed_nodes AS
SELECT DISTINCT
  dataset_id,
  source,
  from_entity_type AS entity_type,
  from_entity_id AS entity_id,
  from_stage AS lifecycle_stage
FROM all_edges
UNION
SELECT DISTINCT
  dataset_id,
  source,
  to_entity_type,
  to_entity_id,
  to_stage
FROM all_edges;

Do not remove dataset_id merely because both releases use the same column names. Their internal normalized IDs were generated inside different products. A cross-product bridge needs an explicitly documented source identifier or a separate reconciled-link assertion.

The edge direction matters:

  • source_release → normalized_notice means the source representation normalized into one published record.
  • source_notice → award means a result notice declares an award.
  • award → supplier means the source names a supplier for that award.
  • award → contract means the source exposes an explicit related contract.

Reversing an edge can be useful for a query, but it does not change the underlying assertion.

Audit relationship and node composition

Edge compositionThe relationship type determines what each row can answer.
Five edge types
  1. 01Tender graph

    161,371

    normalized_as

    Source tender record → normalized tender record

  2. 02Tender graph

    1,313

    declares_contract

    Tender evidence → explicit source contract

  3. 03Award graph

    30,463

    declares_award

    Notice evidence → source-local award

  4. 04Award graph

    57,469

    awarded_to

    Source-local award → named supplier

  5. 05Award graph

    885

    results_in_contract

    Source-local award → explicit source contract

Tender-link reconciliation
161,371 + 1,313 = 162,684
Award-link reconciliation
30,463 + 57,469 + 885 = 88,817

These are counts of typed edge rows. Do not add them together and call the result procurements, notices, awards, suppliers or contracts.

The tender link file reconciles exactly:

161,371 normalized_as
  1,313 declares_contract
----------------------
162,684 edges

The awards link file reconciles exactly:

30,463 declares_award
57,469 awarded_to
   885 results_in_contract
--------------------------
88,817 edges

That arithmetic is a file audit, not a lifecycle funnel. Dividing 885 contract edges by 30,463 awards would produce 2.91%, but calling it a “contract conversion rate” would be false. The explicit contract relationship is exposed only for the applicable Find a Tender structures in this release; zero contract edges for Contracts Finder or TED is a product/source boundary.

At the node grain (dataset_id, source, entity_type, entity_id), the two ledgers contain 413,485 namespaced nodes: 315,571 in the tender graph and 97,914 in the award graph. Keeping dataset_id in that key prevents the 851 contract targets shared by both products from collapsing into one unqualified namespace.

Group through all typed dimensions:

SELECT
  dataset_id,
  source,
  relationship,
  from_entity_type,
  from_stage,
  to_entity_type,
  to_stage,
  count(*) AS edge_count,
  count(DISTINCT from_entity_id) AS from_nodes,
  count(DISTINCT to_entity_id) AS to_nodes
FROM all_edges
GROUP BY ALL
ORDER BY dataset_id, source, relationship,
         from_entity_type, to_entity_type;

This query reveals non-one-to-one cases that a total hides. In the tender graph, 38,843 SAM.gov normalization edges originate from 30,359 distinct source-release keys and reach 38,843 normalized notice nodes. In the award graph, 889 distinct Find a Tender source notices declare 1,597 awards. A source identifier is therefore not a safe proxy for “one row” without the edge and node types beside it.

The full source totals also reconcile independently. The tender graph contains 5,129 Contracts Finder, 2,768 Find a Tender, 38,843 SAM.gov and 115,944 TED edges. The award graph contains 5,976 Contracts Finder, 4,437 Find a Tender and 78,404 TED edges.

Traverse notice to award to supplier or contract

The awards edge ledger supports two explicit two-hop paths. Use relationship and type predicates on both joins; joining on IDs alone makes accidental paths possible.

WITH notice_award AS (
  SELECT
    source,
    from_entity_id AS notice_id,
    to_entity_id AS award_id,
    source_url,
    observation_date
  FROM award_edges
  WHERE relationship = 'declares_award'
    AND from_entity_type = 'source_notice'
    AND to_entity_type = 'award'
),
award_supplier AS (
  SELECT
    source,
    from_entity_id AS award_id,
    to_entity_id AS supplier_id
  FROM award_edges
  WHERE relationship = 'awarded_to'
    AND from_entity_type = 'award'
    AND to_entity_type = 'supplier'
)
SELECT
  notice.source,
  notice.notice_id,
  notice.award_id,
  supplier.supplier_id,
  notice.source_url,
  notice.observation_date
FROM notice_award AS notice
JOIN award_supplier AS supplier
  ON supplier.source = notice.source
 AND supplier.award_id = notice.award_id
ORDER BY notice.source, notice.notice_id,
         notice.award_id, supplier.supplier_id;

The source notice to award to supplier path has 57,469 rows. Every awarded_to edge starts from an award that is declared by a notice in the same source: the orphan count is zero.

Replace the second CTE with relationship = 'results_in_contract' and to_entity_type = 'contract'. The source notice to award to contract path has 885 rows, and those contract edges also have zero undeclared-award orphans.

The tender ledger supports a separate directed two-hop path from a source release to a normalized notice to an explicit contract. It produces 1,313 paths through 849 middle notice nodes, all in the Find a Tender partition. Keep that path grain separate from the awards ledger's source_notice → award → contract path even where a contract target identifier later overlaps.

DuckDB supports ordinary, outer, semi and anti joins. An anti join is useful for publishing missing-edge denominators without confusing NULL logic. (DuckDB joins)

SELECT source, award_id
FROM declared_awards
ANTI JOIN supplier_linked_awards
USING (source, award_id);

The expected supplier anti-join is empty. Do not require the contract anti-join to be empty across all sources; that would turn a source-specific optional relationship into a universal acceptance test.

Preserve one-to-many paths without repeating value

Supplier multiplicityOne award node can point to many supplier relationships.
Different grains
Declared award nodes
30,463
declares_award
Award–supplier edges
57,469
awarded_to
Multi-supplier awards
6,523
source-local award keys
Largest supplier fan-out
146
edges on one source-local award key

Cardinality model

Award 1 → N suppliers

Count the award once at award grain. Count each named supplier relationship once at edge grain.

The released maximum is 146 supplier edges on one source-local award key. That is a relationship count, not 146 independently resolved companies.

The award graph contains 30,463 declared award nodes and 57,469 award-to-supplier edges. Exactly 23,940 awards have one supplier path, 6,523 have multiple supplier paths, and the largest observed award node has 146 supplier edges.

That is not duplication. It is a one-to-many relationship. The error begins when an award-level value is joined onto every path and then summed at path grain.

Use one of three defensible strategies:

  1. Aggregate money at award grain before any relationship expansion.
  2. Keep the full supplier path ledger for relationship analysis and do not attach an attributable amount unless the source supplies one.
  3. If a separate source explicitly allocates value by supplier, use that allocation and retain its value basis and evidence.

Never divide a shared value equally among suppliers merely to make a chart. Yesterday's EU and UK awards Python guide demonstrates the same grain boundary inside one release. This lifecycle workflow generalizes it to paths: the number of reachable supplier nodes is not a monetary allocation rule.

One-to-many appears earlier in the graph too. Contracts Finder has 2,778 distinct result-notice nodes declaring 2,823 awards. Find a Tender has 889 notices declaring 1,597 awards. The correct path key includes the award node; joining notice directly to supplier collapses distinct awards and loses the reason the relationship exists.

Source boundaryThe same graph product has different edge coverage by source.
Keep source in every key

Tender graph

162,684 edge rows across four source-local partitions

161,371 normalized · 1,313 contract
  1. Contracts Finder

    normalized_as
    5,129
    declares_contract
    0

    Normalization only

  2. Find a Tender

    normalized_as
    1,455
    declares_contract
    1,313

    Normalization + contract

  3. SAM.gov

    normalized_as
    38,843
    declares_contract
    0

    Normalization only

  4. TED

    normalized_as
    115,944
    declares_contract
    0

    Normalization only

Award graph

88,817 edge rows across three source-local partitions

30,463 awards · 57,469 suppliers · 885 contracts
  1. Contracts Finder

    declares_award
    2,823
    awarded_to
    3,153
    results_in_contract
    0
  2. Find a Tender

    declares_award
    1,597
    awarded_to
    1,955
    results_in_contract
    885
  3. TED

    declares_award
    26,043
    awarded_to
    52,361
    results_in_contract
    0

A zero is a boundary in this release receipt, not permission to infer “no procurement” or manufacture a cross-source edge. SAM.gov appears in the tender normalization partition; the stated award-link receipt contains Contracts Finder, Find a Tender and TED.

Source-conditioned coverage is part of the result:

Graph / sourceNormalizationDeclared awardsSupplier edgesContract edgesInterpretation
Tender · SAM.gov38,843Normalization evidence only; no US award graph.
Tender · TED115,944Notice normalization, not procedure reconstruction.
Awards · Contracts Finder2,8233,1530Award and supplier edges; no explicit contract product.
Awards · Find a Tender1,5971,955885Award, supplier and explicit contract edges.
Awards · TED26,04352,3610Flattened result-notice winner relationships; no explicit contract edge.

Find a Tender publishes OCDS release and record packages and allows retrieval by notice ID or OCID. An OCID is the contracting-process key; release, award and contract IDs remain scoped beneath it. (Find a Tender data and API documentation; OCDS API details)

Contracts Finder likewise exposes OCDS record and release routes. Use the OCID or the explicit source relationship rather than buyer and title text when reconstructing the source process. (Contracts Finder API documentation)

TED eForms distinguishes notice UUIDs from globally unique procedure UUIDs. A notice can have versions, and a procedure can have several notices. The flattened TED Search fields used by these releases do not justify inventing every procedure-level edge; XML is required for lifecycle-sensitive reconstruction where those relationships are needed. (TED eForms identifiers)

Do not invent cross-dataset edges

The two verified edge files can live in one analytical database, but their union does not create a relationship between them.

A constrained endpoint comparison is still useful. Among Find a Tender contract targets, 851 source-local contract IDs are matched across both artifacts, 462 are tender-only, and 34 are award-only. That is a source-scoped coverage reconciliation of explicit contract nodes, not a new exact cross-dataset edge and not evidence that every upstream notice, opportunity or award belongs to the same end-to-end process. Keep each edge in its original dataset namespace and publish the overlap state separately.

Safe states for a reconciliation layer are:

  • exact: an official identifier and its scope match under a documented rule;
  • reconciled: a source-specific deterministic mapping resolves a known representation difference and retains the evidence;
  • candidate: bounded signals produce a review queue, not an accepted edge; and
  • unresolved: the evidence is insufficient.

Buyer names, titles, CPV codes and dates can generate candidates. They are not identity. A buyer can run similar procurements repeatedly, titles can be copied, classifications can be broad, and framework or lot structures can be one-to-many.

For a candidate workflow:

  1. stay inside one compatible source and jurisdiction;
  2. require a plausible time order and bounded interval;
  3. compare exact source identifiers before text;
  4. use buyer identity and procedure or lot identifiers where published;
  5. treat taxonomy and title similarity as supporting signals;
  6. preserve every competing candidate;
  7. require a human or authoritative source record before promoting the edge; and
  8. publish the unresolved denominator.

Do not encode a heuristic score as confidence = 1.0. The release's confidence field belongs to exact identifier equality under its declared method, not a calibrated probability of real-world correctness.

The existing government contracts database guide explains the broader exact, reconciled, candidate and unlinked model. This page supplies the artifact-level implementation boundary.

Export bounded paths and provenance

The recipe writes six deterministic CSVs and one JSON receipt:

OutputGrain and purpose
procurement-lifecycle-links-release-receipts.csvOne verified dataset root with release, generation time, assets and row counts.
procurement-lifecycle-links-edge-composition.csvDataset × source × relationship × typed endpoints and stages.
procurement-lifecycle-links-node-coverage.csvSource-aware distinct from/to node counts and relationship coverage.
procurement-lifecycle-links-award-supplier-paths.csvBounded notice → award → supplier paths, deterministically sorted.
procurement-lifecycle-links-award-contract-paths.csvBounded notice → award → explicit contract paths.
procurement-lifecycle-links-graph-boundary-checks.csvOrphan, exact-method, source-coverage and contract-target overlap checks.
procurement-lifecycle-links-provenance.jsonInputs, hashes, policies, queries, runtime, output identities and limitations.

The default path limit is 50. Change it explicitly when reviewing a larger deterministic slice:

python procurement-lifecycle-links-python.py \
  --data-dir ./evidence/procurement-lifecycle-2026-08-31 \
  --path-limit 100

The script rejects limits below 1 or above 500. A bounded sample is not the evidence base; the full verified ledger and its aggregate checks remain the evidence base.

Write data files to unique temporary names, hash them, and replace the final outputs only after every query succeeds. Publish the provenance receipt last. A per-directory writer lock prevents two runs with different limits from interleaving outputs. The script also records its own identity at entry and rechecks it before publication.

  • Use the two published lifecycle-link products for reproducible exact-edge analysis at their declared scope and cutoff.
  • Use the source OCDS record package when you need the full release history for a known OCID, including stage-specific local identifiers and amendments.
  • Use TED XML when procedure, lot, previous-notice or eForms relationship fields are required beyond the flattened Search result.
  • Use SAM.gov Opportunities for U.S. opportunity records and USAspending or SAM.gov Contract Awards for U.S. award and action data. This tender graph's SAM portion contains normalization edges only; it is not a U.S. opportunity-to-award graph.
  • Build a managed reconciliation layer when the job needs retained multi-source history, source-specific identifier repair, reviewed candidate matching, unresolved queues, refresh SLAs and downstream delivery.

The Find a Tender API guide owns cursor-safe source collection. The TED Search API guide owns published-notice retrieval and XML capture. The tender Python guide and award Python guide own analysis within their individual releases.

Use the government contract analysis guide for spend, supplier concentration, competition and market-sizing measures. An edge or reachable path is evidence of a declared relationship; it is not, by itself, spend, market size or competition evidence.

  • [ ] Pin both release tags, manifest byte counts and independent SHA-256 values.
  • [ ] Verify each manifest before accepting its asset declarations.
  • [ ] Verify both schema and source-health artifacts.
  • [ ] Require exact 15-column lifecycle-link headers.
  • [ ] Load identifiers, dates and confidence as source text first.
  • [ ] Namespace nodes by dataset, source, entity type and identifier.
  • [ ] Require unique link_id values inside each file.
  • [ ] Reconcile every relationship count to the file total.
  • [ ] Prove exact method, confidence and inference flags rather than assuming them.
  • [ ] Join paths on source, typed award node and permitted relationship.
  • [ ] Check supplier and contract edges for undeclared-award orphans.
  • [ ] Keep multi-supplier paths without allocating shared award values.
  • [ ] Report source-specific contract and lifecycle coverage.
  • [ ] Keep cross-product candidates separate from accepted exact edges.
  • [ ] Export unresolved and non-claim boundaries beside path samples.
  • [ ] Hash outputs and publish provenance last.
  • [ ] Verify time-sensitive decisions in the official source record.

Limitations and interpretation boundaries

  • Both releases are dated snapshots with retained-history policies; they are not complete procurement histories.
  • Link rows count relationships, not procedures, notices, awards, contracts or suppliers interchangeably.
  • confidence = 1.0 means exact equality under the published method, not independent verification.
  • An exact identifier link is not proof of a complete lifecycle, legal causality or a current signed agreement.
  • normalized_as connects a source representation to a normalized notice. It is not a stage transition.
  • The SAM.gov tender partition is normalization evidence, not a U.S. award artifact, and its edges do not connect to U.S. awards.
  • An absent or unmatched contract edge does not prove that no contract exists.
  • Explicit contract edges are source- and product-dependent. Their absence does not prove no signed contract exists.
  • TED flattened result-notice data does not expose every procedure or lot relationship needed for end-to-end reconstruction.
  • Supplier identifiers and identifier schemes can be missing: 56,591 of 57,469 supplier edges lack a supplier identifier or identifier scheme; resolution is unknown, and source/name hashes are not global or cross-source corporate identities.
  • A result notice can declare several awards, and an award can link to several suppliers or contracts.
  • The lifecycle files carry no aggregation-safe monetary allocation. Join award values only at the documented award grain.
  • Similar buyer names, titles, codes and dates can generate review candidates but cannot create exact edges.
  • Source publishers can correct, replace or withdraw records after the pinned cutoff.

Frequently asked questions

Can lifecycle-link rows be counted as procurements?

No. They are directed relationship assertions. One procurement can generate several notices, awards, suppliers, contracts and therefore many edges. Count a declared business object only at its own typed node grain.

Does confidence 1.0 mean the relationship was independently verified?

No. In these products it means exact official-identifier equality under the declared deterministic method. The source assertion and current legal or factual state still need source verification.

Does normalized_as mean a tender became an award?

No. It means a source release was normalized into one notice representation. A lifecycle transition requires an explicit relationship or a source-process history that supports it.

Can I join the tender and award files by internal hash?

Not safely. Internal normalized IDs belong to their dataset namespaces. Use an explicit official identifier with documented scope or publish a separate reconciled edge with evidence.

Why are there more supplier edges than award nodes?

Some awards name more than one supplier. The release contains 57,469 supplier edges from 30,463 award nodes; 6,523 awards have multiple supplier paths. Preserve those relationships and aggregate money at award grain.

Does zero contract edges mean there was no contract?

No. Explicit contract relationships are available only where the source structure and normalized product expose them. In this release the 885 award-to-contract edges are from Find a Tender. Other sources require their official records and lifecycle semantics.

Can title and buyer similarity create an automatic link?

They can create a bounded candidate queue, not an exact accepted edge. Repeated titles, broad classifications, frameworks and recurring buyers make false positives likely. Preserve competing candidates and require authoritative evidence or review.

Do I need a graph database?

No. A typed edge ledger and ordinary DuckDB joins are enough for bounded two-hop audits and exports. Use recursive traversal only when the question genuinely requires longer paths, and always impose relationship and depth limits. DuckDB supports recursive CTEs for that purpose. (DuckDB WITH RECURSIVE)

Are SAM.gov opportunities linked to U.S. awards here?

No. The SAM.gov portion of the tender lifecycle file contains normalization edges only. Use USAspending or SAM.gov Contract Awards with a separately designed, evidence-bearing U.S. reconciliation workflow.

Which source record should I verify before acting?

Follow the edge's retained source_url, then inspect the current official notice, OCDS record package or source award/contract page. A cryptographically verified derivative proves which bytes you analyzed, not that the source has not changed since the cutoff.