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.
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.
Tender links
162,684
normalized_as- 161,371
declares_contract- 1,313
Award links
88,817
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
Procurement lifecycle links with Python: the short answer
Use this sequence:
- Pin both release tags and independently record each manifest's exact byte count and SHA-256.
- Verify each manifest before trusting the schema, source-health, or lifecycle-link hashes declared inside it.
- Require the expected release dates, schema version
2.0, product grains, source statuses, and lifecycle-link row counts. - Verify the two 15-column CSV headers exactly and load all columns as text.
- Add a
dataset_idnamespace before combining the two edge ledgers. Do not assume an internal hash in one product names the same node in the other. - Require one unique
link_idper file, non-empty typed endpoints,link_method = exact_official_identifier,confidence = 1.0, andis_inferred = False. - Group by dataset, source, relationship, entity type, and lifecycle stage before interpreting any count.
- Treat
normalized_asas a representation edge. It does not mean that a planning or opportunity record became an award. - Traverse
source_notice → award → supplieronly throughdeclares_awardfollowed byawarded_to. - Traverse
source_notice → award → contractonly throughdeclares_awardfollowed byresults_in_contract. - Keep every supplier path. Collapse to award grain before joining award-level money from another product.
- Publish source-specific coverage. An absent
results_in_contractedge can be structural for that source and release, not proof that no contract exists. - Keep candidate or fuzzy reconciliation outside the exact graph and label it for human review.
- 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.
Separate links, nodes, paths and procurements
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.
Source notice
Source-local notice identifier
declares_award30,463Award
Source-local award identifier
awarded_toNamed supplier
results_in_contractExplicit contract
The tender graph provides a parallel evidence lane
normalized_as161,371 edges connect a source tender record to its normalized tender record.
declares_contract1,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:
| Asset | Bytes | SHA-256 |
|---|---|---|
| schema.json | 18,717 | 0a31059590fcb48919458e7af9f97ebce04a1c9a41b6c91883578568634ba8b3 |
| source-health.json | 2,673 | 35f2266b60aa6f46f127b0b7254782768d074d354d6c35ad65d85507b6044665 |
| lifecycle-links.csv | 73,352,297 | d2600170c9e3f44b4471778c00556d5862eda9d8bb376661f861ad6e02397d62 |
The verified awards manifest points to:
| Asset | Bytes | SHA-256 |
|---|---|---|
| schema.json | 15,089 | 4fb97f596bb4c0ea34f8bdbeea6cfb986f4e94344f9bce739c45986c7746ff81 |
| source-health.json | 1,994 | 3c769d2cdd3f16a7c27feca8d77b19e7dbdf2eb704d27a54d6bfa263570a6704 |
| lifecycle-links.csv | 41,722,673 | 6781b8516de652d3727ec1c30699134a2c57cd8ed38bd33f37027b9267479527 |
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 root | Sources required | Lifecycle grain | Rows |
|---|---|---|---|
| Tenders and RFPs | Contracts Finder, Find a Tender, SAM.gov, TED | One exact official-identifier relationship | 162,684 |
| Contract awards | Contracts Finder, Find a Tender, TED | One exact official-identifier relationship | 88,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.
Verify manifests, schema, source health and links
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_idis non-empty and unique within its file; - the typed endpoints and relationship are non-empty;
- every row uses
exact_official_identifier, confidence1.0, andFalseforis_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_noticemeans the source representation normalized into one published record.source_notice → awardmeans a result notice declares an award.award → suppliermeans the source names a supplier for that award.award → contractmeans 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
- 01Tender graph
161,371
normalized_asSource tender record → normalized tender record
- 02Tender graph
1,313
declares_contractTender evidence → explicit source contract
- 03Award graph
30,463
declares_awardNotice evidence → source-local award
- 04Award graph
57,469
awarded_toSource-local award → named supplier
- 05Award graph
885
results_in_contractSource-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
- 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:
- Aggregate money at award grain before any relationship expansion.
- Keep the full supplier path ledger for relationship analysis and do not attach an attributable amount unless the source supplies one.
- 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.
Measure link coverage by source
Tender graph
162,684 edge rows across four source-local partitions
Contracts Finder
normalized_as- 5,129
declares_contract- 0
Normalization only
Find a Tender
normalized_as- 1,455
declares_contract- 1,313
Normalization + contract
SAM.gov
normalized_as- 38,843
declares_contract- 0
Normalization only
TED
normalized_as- 115,944
declares_contract- 0
Normalization only
Award graph
88,817 edge rows across three source-local partitions
Contracts Finder
declares_award- 2,823
awarded_to- 3,153
results_in_contract- 0
Find a Tender
declares_award- 1,597
awarded_to- 1,955
results_in_contract- 885
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 / source | Normalization | Declared awards | Supplier edges | Contract edges | Interpretation |
|---|---|---|---|---|---|
| Tender · SAM.gov | 38,843 | — | — | — | Normalization evidence only; no US award graph. |
| Tender · TED | 115,944 | — | — | — | Notice normalization, not procedure reconstruction. |
| Awards · Contracts Finder | — | 2,823 | 3,153 | 0 | Award and supplier edges; no explicit contract product. |
| Awards · Find a Tender | — | 1,597 | 1,955 | 885 | Award, supplier and explicit contract edges. |
| Awards · TED | — | 26,043 | 52,361 | 0 | Flattened 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:
- stay inside one compatible source and jurisdiction;
- require a plausible time order and bounded interval;
- compare exact source identifiers before text;
- use buyer identity and procedure or lot identifiers where published;
- treat taxonomy and title similarity as supporting signals;
- preserve every competing candidate;
- require a human or authoritative source record before promoting the edge; and
- 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:
| Output | Grain and purpose |
|---|---|
| procurement-lifecycle-links-release-receipts.csv | One verified dataset root with release, generation time, assets and row counts. |
| procurement-lifecycle-links-edge-composition.csv | Dataset × source × relationship × typed endpoints and stages. |
| procurement-lifecycle-links-node-coverage.csv | Source-aware distinct from/to node counts and relationship coverage. |
| procurement-lifecycle-links-award-supplier-paths.csv | Bounded notice → award → supplier paths, deterministically sorted. |
| procurement-lifecycle-links-award-contract-paths.csv | Bounded notice → award → explicit contract paths. |
| procurement-lifecycle-links-graph-boundary-checks.csv | Orphan, exact-method, source-coverage and contract-target overlap checks. |
| procurement-lifecycle-links-provenance.json | Inputs, 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.
Choose published links, source records or managed reconciliation
- 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.
Procurement lifecycle link checklist
- [ ] 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_idvalues 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.0means 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_asconnects 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.