Back to the illustrative prototype

Sample release (Databricks-ready)

This is release r3 of the prototype visitor product inventory for 3 sample LVEPs: Cornwall, Greater Manchester and York & North Yorkshire. The snapshot date is 12 September 2026. It replaces ve-audit-sample-r2-2026-09-12; the change set lists what changed.

It holds 19,846 product records built from open data: OpenStreetMap, Wikidata, the National Heritage List for England and ONS. Hestia Forge made it for the VisitBritain/VisitEngland tender BTA2235 to show how the inventory could be delivered as tables that load into Databricks.

Release id: ve-audit-sample-r3-2026-09-12. The files are Parquet (snappy) and CSV (UTF-8), with a data dictionary, SQL DDL and a manifest with SHA-256 checksums.

Files

Files in release ve-audit-sample-r3-2026-09-12
FileTableFormatRowsSizeSHA-256
Download product table (Parquet, 1.9 MB)
product.parquet
productParquet19,8461.9 MBe5606b71d95f3fb603a8465f768f434392ace9632f0b40d2af8b837038d98f28
Download product table (CSV, 8.8 MB)
product.csv
productCSV19,8468.8 MB44ab879f5a5fd895622315226cb0be4ef7c151d5edd5485b66016e34839f1414
Download attribute provenance table (Parquet, 971 kB)
product_attribute_provenance.parquet
product_attribute_provenanceParquet95,921971 kB4b37d16c25ff112b25c5363337cc2c563a49ce0144f5b961ecd0cef82f4f8961
Download attribute provenance table (CSV, 14.2 MB)
product_attribute_provenance.csv
product_attribute_provenanceCSV95,92114.2 MB9ba6f4012d9edc92a0a193253e15f2e946026b1005a12d1e4a6959daa95bd4c2
Download product source cross-reference table (Parquet, 1.0 MB)
product_source_xref.parquet
product_source_xrefParquet22,3161.0 MBd244f981a53a413a53b5a619500e21313cb9c64070affb2f1d03ce2ec01725a2
Download product source cross-reference table (CSV, 6.4 MB)
product_source_xref.csv
product_source_xrefCSV22,3166.4 MBd148980030b22fe6ec2d129b4bca9fd42e51f5d786a62205bcc2d3ddc8edffc9
Download product relationships table (Parquet, 180 kB)
product_relationship.parquet
product_relationshipParquet3,043180 kB0b642ccf62a69bcf0760cd9dfeeeace43b8ae26869095e0c773b852ab24c226f
Download product relationships table (CSV, 776 kB)
product_relationship.csv
product_relationshipCSV3,043776 kB42deba8c60beecb038609429bd44afec94cf7652f5cd7e89dffdbd97d58d60d8
Download product lineage events table (Parquet, 245 kB)
product_lineage_event.parquet
product_lineage_eventParquet2,808245 kBb2ef9cc448b161af4644b7af0575d70020a8cb201539f0764059229ddd6a5773
Download product lineage events table (CSV, 1.0 MB)
product_lineage_event.csv
product_lineage_eventCSV2,8081.0 MB62d20e70bcec4a4ef5cd3739e80592064f3767334d791810703d35eed22f8a59
Download change set since ve-audit-sample-r2-2026-09-12 table (Parquet, 13 kB)
product_change_set.parquet
product_change_setParquet9313 kBf42f27c8ce3c9356c19e4f981b9e48cb0885834538e0aeb67f3645017fbc293b
Download change set since ve-audit-sample-r2-2026-09-12 table (CSV, 78 kB)
product_change_set.csv
product_change_setCSV9378 kB56370f5a1216d0a4b7ddac3bc44e6ad64732ec465b487168c702b627ca5e339a
Download LVEP membership table (Parquet, 6 kB)
lvep_membership.parquet
lvep_membershipParquet136 kB821e5ca741f7c77dc45e47273a0c09e4a4332db1b38bbf0a34e7a7001857bcc6
Download LVEP membership table (CSV, 2 kB)
lvep_membership.csv
lvep_membershipCSV132 kB5e3a1c502de2cf9670d3b84b8f606fa096e2d21a1ca34e7df6c76ddf65f683a3
ve_product_id registry (CSV)
ve_product_id_registry.csv
not a tableCSV19,8463.4 MB47c9f259dd98787d471d836fbd16f0b9d749c6d0201cf176a9907b37a7c67f3d
Match precision test: 819 pairs (CSV)
match_precision_pairs.csv
not a tableCSV819128 kB4e025bfb2c095772047607d3fdfd89a698393a46f135db8c8ee7dbfdf183052b
Same-name pairs (CSV)
same_name_pairs.csv
not a tableCSV19730 kBc6e64a8c6bd20d59fc4078d56e74bac3b44f393c0e98bfa67d02828d18151c6d
Match test figures (JSON)
match_precision_summary.json
not a tableJSON2 kB873e11c0192f3fe2a2b1ae6364d561d69deef6dab984d5cdaf10273a9d87c5dd
Match test script (Python)
match_precision_test.py
not a tablePython12 kB737f761df487044a04dee5447a92f14ba53f9751fda266fe6f8f5f5b3c71586a
Data dictionary (JSON)
data_dictionary.json
not a tableJSON57 kB0114ccf80a96ee3228b539204dd83fff59ffc5fa96a634489cc6d571c766229c
Databricks SQL DDL (SQL)
ddl.sql
not a tableSQL21 kB7ced54343801ba00f7a564cb733e79b0b60b842847db06669e71cb95a7d34f94
Spark read check log (text)
spark-read-log.txt
not a tableText35 kB1bf5d7a8c62671a6c3da669237ad2eaa7f3d9668b15e26855dfcdd7a6cba99ac
Release manifest (JSON)
manifest.json
not a tableJSON19 kBc42cf4f788bab0ca0badf19d9eef43961251b877de97aaf55f554ccb4973a5e0

The manifest lists the checksums of the data files, the dictionary, the DDL and the Spark log. The checksums do not cover this page or its stylesheet.

Tables

product
One row per product record, with category, role, LVEP and local authority, location, website, opening hours, months open, checks and field-level sources.
product_attribute_provenance
One row per attribute value: the source record that supplied it, the method (tag, property or rule), the rule confidence where there is one (category only; fixed per rule, not calibrated), and the last edit or check date of that record.
product_source_xref
One row per source record merged into a product: source identifier and link, licence, retrieval and edit dates, how it was linked and the match score. This is the crosswalk from source ids to ve_product_id.
product_relationship
Relationships between products: the nearest visitor product of each heritage asset within 1,000 m, and the possible duplicate and same-name pairs kept separate for a reviewer.
product_lineage_event
One row per merge event, review candidate pair, same-name candidate pair, unit group, and id registry event (split, product merge, retirement).
product_change_set
What changed since ve-audit-sample-r2-2026-09-12, keyed on ve_product_id: inserted and retired products, and one row per changed column of an updated product.
lvep_membership
Which ONS local authorities each sample LVEP covers, with mid-year population. The membership is illustrative.

ve_product_id_registry.csv lists every ve_product_id ever published, the release that minted it, whether it is active or retired, and the id that replaced it.

The data dictionary gives each column's type, nullability, meaning, derivation and an example.

How to load in Databricks

-- 1. In Databricks, run ddl.sql (creates ve_audit.sample and the four tables).
-- 2. Create a volume and upload the four .parquet files to it.
CREATE VOLUME IF NOT EXISTS ve_audit.sample.release;
-- (Catalog Explorer: Upload to volume, or the CLI:)
-- databricks fs cp product.parquet dbfs:/Volumes/ve_audit/sample/release/product.parquet
-- 3. Load, parents first.
COPY INTO ve_audit.sample.lvep_membership
  FROM '/Volumes/ve_audit/sample/release/lvep_membership.parquet' FILEFORMAT = PARQUET;
COPY INTO ve_audit.sample.product
  FROM '/Volumes/ve_audit/sample/release/product.parquet' FILEFORMAT = PARQUET;
COPY INTO ve_audit.sample.product_source_xref
  FROM '/Volumes/ve_audit/sample/release/product_source_xref.parquet' FILEFORMAT = PARQUET;
COPY INTO ve_audit.sample.product_lineage_event
  FROM '/Volumes/ve_audit/sample/release/product_lineage_event.parquet' FILEFORMAT = PARQUET;
-- 4. Check against manifest.json.
SELECT count(*) FROM ve_audit.sample.product;  -- 19,846

The CSV files hold the same rows. They join array values with “; ”, so Parquet is easier to load. To read a CSV file in Spark, use header=true, multiLine=true and escape='"'.

Spark read check

We ran pipeline/spark_read_check.py on a local Apache Spark session (local[*]). It reads every Parquet file with spark.read.parquet, prints the schemas, counts rows and compares them with the manifest. It also reads each CSV file with a header, checks keys, and creates the tables from ddl.sql.

Spark version
4.2.0
Java version
21.0.12
Python and pyarrow
3.12.3, pyarrow 25.0.1
Row counts match the manifest (Parquet and CSV)
yes
ve_product_id unique, and every id active in the id registry
yes
Every cross-reference and lineage ve_product_id exists in product
yes
DDL tables created in local Spark and match the Parquet schemas
yes
All checks passed
yes

For the DDL check, local Spark has no Unity Catalog or Delta Lake, so the script substituted USING DELTA with USING PARQUET and ve_audit.sample. with a local database, ve_audit_sample_local. Local Spark rejected the informational PRIMARY KEY / FOREIGN KEY clauses (they need Unity Catalog), so they were removed for this check; their syntax was not validated locally. The CREATE CATALOG statement was not run locally.

Read the full Spark read check log (real output, with versions and timestamps).

Persistent identifiers

Use ve_product_id. It is a surrogate key: it is minted once, when a product is first published, stored in an id registry, and from then on only looked up, never recomputed. Every source record of the product is stored against the id (the product_source_xref table), so the id stays the same when a different source record becomes primary, for example when a BTA or LVEP listing arrives for a product first found in OpenStreetMap.

In this release 19,846 of 19,846 products kept the id they had in ve-audit-sample-r2-2026-09-12; 0 ids were minted and 0 retired. The ids of release 1 were minted from the SHA-256 hash of the then-primary source record key; that formula is not applied again. The logic is in pipeline/assign_ids.py, with a unit test for a change of primary record, a merge, a split, a new product and a retirement.

prototype_id (for example CW-00001) is the number shown in the web prototype. It is renumbered at every build, so do not store it or join on it across releases.

Change set since ve-audit-sample-r2-2026-09-12

0 products inserted, 0 retired, and 72 updated (93 changed values). Changes in this release come from these pipeline corrections:

Updated values by product column
ColumnChanged values
nearest_product_m72
checks21

Opening months: stated and inferred

Stated means the source states the months (OpenStreetMap opening_hours month rules or the seasonal tag). Inferred means opening hours with no month rules, assumed to be open all year: an assumption, not evidence. The column seasonality_evidence keeps them apart.

Visitor product records with opening months, by sample LVEP
LVEPVisitor product recordsStatedInferred all yearNo month evidence
Cornwall4,19824 (0.6%)379 (9.0%)3,795
Greater Manchester5,6743 (0.1%)726 (12.8%)4,945
York & North Yorkshire6,68726 (0.4%)488 (7.3%)6,173

Match precision test and same-name check

These files let anyone reproduce the matching figures: the 819 pairs, the same-name pairs, the figures and the script (it reads the raw source downloads, which are not published).

Identifier hold-out. 819 OpenStreetMap features carry the Wikidata ID of an item within 500 m. With the ID hidden, the name + distance + category rule scored every Wikidata item within 500 m. 690 pairs reached the auto-merge band and 688 of them chose the tagged item: precision 99.7% (Wilson 95% interval 98.95–99.92%). 115 went to review and 14 found no match; 98.0% were merged correctly or sent to review. Tagged features lean towards well-known places, so this tests the rule; it does not replace a human-labelled sample. Wrong auto-merges:

Same name nearby, two rules. Text rule (names equal after removing stop words such as “the” and “St”, within 100 m, every pair): 161 pairs, 94 in the same category (candidate missed duplicates) and 67 in different categories (different products at one site). Release rule (names equal with stop words kept, within 150 m, not a review-band pair, names used 5 or more times in one LVEP skipped): 108 pairs, flagged same_name_nearby and listed as same_name_candidate events.

Merge and split events

The product_lineage_event table records how products were formed in this release:

Licences and attribution

OpenStreetMap

Licence: Open Database Licence (ODbL) 1.0. Attribution: © OpenStreetMap contributors.

The OpenStreetMap-derived layer of this release is a Derivative Database of OpenStreetMap and is made available under the Open Database Licence (ODbL) 1.0, with the attribution © OpenStreetMap contributors. Rows and values in that layer are identified by product.osm_derived = true, product.primary_source = 'osm', the product *_source columns with value osm, and product_source_xref rows with source = 'osm'. If you publicly use an adapted version of this layer, the ODbL share-alike terms apply to it.

Wikidata

Licence: Creative Commons CC0 1.0. Attribution: Wikidata, CC0 1.0 (attribution not required; given as a courtesy).

Wikidata values (identifiers, descriptions, websites, classes) are dedicated to the public domain under CC0 1.0.

National Heritage List for England

Licence: Open Government Licence v3.0. Attribution: © Historic England 2026. Contains Ordnance Survey data © Crown copyright and database right 2026.

NHLE list entry numbers, names, designations, grades and locations are used under the Open Government Licence v3.0.

ONS local authority boundaries (May 2026), mid-year population estimates, LSOA (2021) population-weighted centroids and Census 2021 residents

Licence: Open Government Licence v3.0. Attribution: Source: Office for National Statistics, licensed under the Open Government Licence v3.0. Contains OS data © Crown copyright and database right 2026.

Local authority codes and names (boundaries used to assign records) and mid-year population estimates are used under the Open Government Licence v3.0.

Caveats