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
| File | Table | Format | Rows | Size | SHA-256 |
|---|---|---|---|---|---|
Download product table (Parquet, 1.9 MB)product.parquet | product | Parquet | 19,846 | 1.9 MB | e5606b71d95f3fb603a8465f768f434392ace9632f0b40d2af8b837038d98f28 |
Download product table (CSV, 8.8 MB)product.csv | product | CSV | 19,846 | 8.8 MB | 44ab879f5a5fd895622315226cb0be4ef7c151d5edd5485b66016e34839f1414 |
Download attribute provenance table (Parquet, 971 kB)product_attribute_provenance.parquet | product_attribute_provenance | Parquet | 95,921 | 971 kB | 4b37d16c25ff112b25c5363337cc2c563a49ce0144f5b961ecd0cef82f4f8961 |
Download attribute provenance table (CSV, 14.2 MB)product_attribute_provenance.csv | product_attribute_provenance | CSV | 95,921 | 14.2 MB | 9ba6f4012d9edc92a0a193253e15f2e946026b1005a12d1e4a6959daa95bd4c2 |
Download product source cross-reference table (Parquet, 1.0 MB)product_source_xref.parquet | product_source_xref | Parquet | 22,316 | 1.0 MB | d244f981a53a413a53b5a619500e21313cb9c64070affb2f1d03ce2ec01725a2 |
Download product source cross-reference table (CSV, 6.4 MB)product_source_xref.csv | product_source_xref | CSV | 22,316 | 6.4 MB | d148980030b22fe6ec2d129b4bca9fd42e51f5d786a62205bcc2d3ddc8edffc9 |
Download product relationships table (Parquet, 180 kB)product_relationship.parquet | product_relationship | Parquet | 3,043 | 180 kB | 0b642ccf62a69bcf0760cd9dfeeeace43b8ae26869095e0c773b852ab24c226f |
Download product relationships table (CSV, 776 kB)product_relationship.csv | product_relationship | CSV | 3,043 | 776 kB | 42deba8c60beecb038609429bd44afec94cf7652f5cd7e89dffdbd97d58d60d8 |
Download product lineage events table (Parquet, 245 kB)product_lineage_event.parquet | product_lineage_event | Parquet | 2,808 | 245 kB | b2ef9cc448b161af4644b7af0575d70020a8cb201539f0764059229ddd6a5773 |
Download product lineage events table (CSV, 1.0 MB)product_lineage_event.csv | product_lineage_event | CSV | 2,808 | 1.0 MB | 62d20e70bcec4a4ef5cd3739e80592064f3767334d791810703d35eed22f8a59 |
Download change set since ve-audit-sample-r2-2026-09-12 table (Parquet, 13 kB)product_change_set.parquet | product_change_set | Parquet | 93 | 13 kB | f42f27c8ce3c9356c19e4f981b9e48cb0885834538e0aeb67f3645017fbc293b |
Download change set since ve-audit-sample-r2-2026-09-12 table (CSV, 78 kB)product_change_set.csv | product_change_set | CSV | 93 | 78 kB | 56370f5a1216d0a4b7ddac3bc44e6ad64732ec465b487168c702b627ca5e339a |
Download LVEP membership table (Parquet, 6 kB)lvep_membership.parquet | lvep_membership | Parquet | 13 | 6 kB | 821e5ca741f7c77dc45e47273a0c09e4a4332db1b38bbf0a34e7a7001857bcc6 |
Download LVEP membership table (CSV, 2 kB)lvep_membership.csv | lvep_membership | CSV | 13 | 2 kB | 5e3a1c502de2cf9670d3b84b8f606fa096e2d21a1ca34e7df6c76ddf65f683a3 |
ve_product_id registry (CSV)ve_product_id_registry.csv | not a table | CSV | 19,846 | 3.4 MB | 47c9f259dd98787d471d836fbd16f0b9d749c6d0201cf176a9907b37a7c67f3d |
Match precision test: 819 pairs (CSV)match_precision_pairs.csv | not a table | CSV | 819 | 128 kB | 4e025bfb2c095772047607d3fdfd89a698393a46f135db8c8ee7dbfdf183052b |
Same-name pairs (CSV)same_name_pairs.csv | not a table | CSV | 197 | 30 kB | c6e64a8c6bd20d59fc4078d56e74bac3b44f393c0e98bfa67d02828d18151c6d |
Match test figures (JSON)match_precision_summary.json | not a table | JSON | 2 kB | 873e11c0192f3fe2a2b1ae6364d561d69deef6dab984d5cdaf10273a9d87c5dd | |
Match test script (Python)match_precision_test.py | not a table | Python | 12 kB | 737f761df487044a04dee5447a92f14ba53f9751fda266fe6f8f5f5b3c71586a | |
Data dictionary (JSON)data_dictionary.json | not a table | JSON | 57 kB | 0114ccf80a96ee3228b539204dd83fff59ffc5fa96a634489cc6d571c766229c | |
Databricks SQL DDL (SQL)ddl.sql | not a table | SQL | 21 kB | 7ced54343801ba00f7a564cb733e79b0b60b842847db06669e71cb95a7d34f94 | |
Spark read check log (text)spark-read-log.txt | not a table | Text | 35 kB | 1bf5d7a8c62671a6c3da669237ad2eaa7f3d9668b15e26855dfcdd7a6cba99ac | |
Release manifest (JSON)manifest.json | not a table | JSON | 19 kB | c42cf4f788bab0ca0badf19d9eef43961251b877de97aaf55f554ccb4973a5e0 |
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 onve_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.
- Merge: when two products become one, the product keeps the id that holds most of its source records; the other id is retired with
replaced_byand aproduct_mergeevent. - Split: when one product becomes two, the part holding most of the old source records keeps the id; the other part gets a new id and a
splitevent. - Retirement: an id whose source records have all gone is retired, never reused.
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:
- Latent heritage rule for area designations: World Heritage Sites, registered parks and gardens and registered battlefields are now measured from their Historic England boundary (boundaries retrieved 13 September 2026, pipeline/fetch_nhle_areas.py and area_rule.py) instead of one representative point. A visitor product record inside the site or within 500 m of its boundary counts, so the check latent_asset is removed where one exists (for example the Cornwall and West Devon Mining Landscape, with East Pool Mine and Heartlands inside), and nearest_product_m is the distance to the boundary (0 inside). Other sources keep the 12 September 2026 snapshot; no other rule changed.
| Column | Changed values |
|---|---|
nearest_product_m | 72 |
checks | 21 |
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.
| LVEP | Visitor product records | Stated | Inferred all year | No month evidence |
|---|---|---|---|---|
| Cornwall | 4,198 | 24 (0.6%) | 379 (9.0%) | 3,795 |
| Greater Manchester | 5,674 | 3 (0.1%) | 726 (12.8%) | 4,945 |
| York & North Yorkshire | 6,687 | 26 (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:
- Radisson (way/29450342) -> The Edwardian Manchester, A Radisson Collection Hotel (Q111368147), tagged Free Trade Hall
- Hyatt Regency Manchester (way/452432847) -> Hyatt House Manchester (Q111873746), tagged Hyatt Regency Manchester
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:
- merge: 2,470 rows. A source record merged into a product, for example a Wikidata item linked to an OpenStreetMap feature by a shared identifier.
- review_candidate_kept_separate: 228 rows. Two records that scored in the review band (0.60 to 0.85) and were kept separate for a reviewer.
- same_name_candidate: 108 rows. Two records with the same name within 150 m that the matcher did not link.
- unit_group: 2 rows. OpenStreetMap units, such as numbered chalets, listed as one site.
- split: 0 rows; product_merge: 0 rows; retire: 0 rows. Id registry events against the previous 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
- Illustrative sample built for the VisitBritain/VisitEngland tender BTA2235 response. It is not the production tool.
- Open data only: OpenStreetMap, Wikidata, Historic England NHLE and ONS. It contains no BTA or VisitEngland data and does not follow VisitEngland's schema.
- Covers 3 of 38 LVEPs (Cornwall, Greater Manchester, York & North Yorkshire). The LVEP to local authority membership is illustrative; Cornwall excludes the Isles of Scilly.
- prototype_id is not stable between builds. Use ve_product_id.
- Matching and classification are rules only: no machine learning and no human review. Some merges may be wrong, and pairs in the review band are kept separate rather than resolved. Category confidence is fixed per rule, not calibrated.
- Opening months are stated for only 53 of 16,559 visitor product records (0.3%): the source states the months. A further 1,593 are inferred to open all year only because their opening hours have no month rules; the rest have no month evidence. By LVEP, stated / inferred: Cornwall 24 / 379 of 4,198; Greater Manchester 3 / 726 of 5,674; York & North Yorkshire 26 / 488 of 6,687. 34 records show months closed.
- Open data coverage differs between places and categories. Missing records are not evidence that a product does not exist.
- Values are as retrieved on 2026-09-12 and may have changed at source since.