fix(shipwrecks): an end-of-service date (P576) keeps its precision, so a year-only one is not shown as 1 January

The query read P576 through wdt:, which carries no precision, and a missing
precision counted as a day. It now reads the best-rank statement's value with
wikibase:timePrecision, and a value without a precision is trusted to the
year only. Against the live query service the new query takes 23 s and dates
16 more wrecks while every date the old one gave is unchanged.
This commit is contained in:
Sameh Khamis committed 2026-11-10 13:19:23 -08:00
1 parent 0da3a28d76
commit a1200efba6
2 files changed
+53 -8

No files matched your search

+8 -8
View File
@@ -16,16 +16,16 @@ export const SHIPWRECK_SOURCES = Object.freeze(['emodnet', 'wikidata']);
* Wikidata (CC0): shipwreck items that have at least one Wikipedia article,
* so the worldwide set stays to notable wrecks and skips bulk register
* imports, plus sinking events ("sinking of the RMS Titanic"), which are where
* Wikidata keeps the death toll. Dates come with their precision so a
* month-precise date is not shown as the 1st. Only ship and event facts are
* read: no items about people.
* Wikidata keeps the death toll. Dates, the end of service (P576) included,
* come with their precision so a month-precise date is not shown as the 1st.
* Only ship and event facts are read: no items about people.
*/
export const WIKIDATA_QUERY = `SELECT ?item ?itemLabel ?itemDescription ?coord ?sunk ?sunkPrecision ?end ?deaths ?survivors ?typeLabel ?enwiki ?ptwiki ?event WHERE {
export const WIKIDATA_QUERY = `SELECT ?item ?itemLabel ?itemDescription ?coord ?sunk ?sunkPrecision ?end ?endPrecision ?deaths ?survivors ?typeLabel ?enwiki ?ptwiki ?event WHERE {
{
?item wdt:P31/wdt:P279* wd:Q852190 ; wdt:P625 ?coord ; wikibase:sitelinks ?links .
FILTER(?links > 0)
OPTIONAL { ?item p:P793 ?wreck . ?wreck ps:P793 wd:Q906512 ; pqv:P585 [ wikibase:timeValue ?sunk ; wikibase:timePrecision ?sunkPrecision ] }
OPTIONAL { ?item wdt:P576 ?end }
OPTIONAL { ?item p:P576 [ a wikibase:BestRank ; psv:P576 [ wikibase:timeValue ?end ; wikibase:timePrecision ?endPrecision ] ] }
OPTIONAL { ?item wdt:P31 ?type . FILTER(?type != wd:Q852190) }
} UNION {
?item wdt:P31/wdt:P279* wd:Q906512 ; wdt:P625 ?coord .
@@ -203,8 +203,8 @@ function wikidataTime(value, precision) {
const none = { date: null, year: null, dating: null };
const match = /^([+-]?)(\d{4,})-(\d{2})-(\d{2})T/.exec(value || '');
if (!match) return none;
// A missing precision (P576 is read without one) counts as a day.
const p = precision == null ? 11 : Number(precision);
// A value without a precision is only trusted to the year.
const p = precision == null ? 9 : Number(precision);
// 11 = day; 10 = month; 9 = year; below 9 is a decade or coarser.
if (!Number.isFinite(p) || p < 9) return none;
const signed = Number(`${match[1] === '-' ? '-' : ''}${match[2]}`);
@@ -253,7 +253,7 @@ export function normalizeWikidataResults(payload) {
const qid = /\/entity\/(Q\d{1,12})$/.exec(value('item') || '')?.[1];
if (!qid) continue;
const sunk = wikidataTime(value('sunk'), value('sunkPrecision'));
const ended = wikidataTime(value('end'), null);
const ended = wikidataTime(value('end'), value('endPrecision'));
const known = byItem.get(qid);
if (known) {
// Extra rows only add what the first one lacked.
+45
View File
@@ -6,6 +6,7 @@ import {
normalizeShipwreckSnapshot,
normalizeWikidataResults,
parseYear,
WIKIDATA_QUERY,
} from './records.js';
function feature(id, properties, coordinates = [-4.35, 54.13]) {
@@ -86,6 +87,7 @@ test('Wikidata rows fold into one record per item', () => {
itemDescription: { value: '1939 G and H-class destroyer' },
coord: { value: 'Point(2.26 51.13)' },
sunk: { value: '1940-06-01T00:00:00Z' },
sunkPrecision: { value: '11' },
enwiki: { value: 'https://en.wikipedia.org/wiki/HMS_Havant_(H32)' },
},
{
@@ -262,3 +264,46 @@ test('a date before AD 100 is kept as ancient text, never read as an AD year', (
assert.equal(roman.year, 300);
assert.equal(roman.dating, null);
});
test('an end-of-service date keeps its precision: a year-only one is no day', () => {
const entity = (q) => ({ value: `http://www.wikidata.org/entity/${q}` });
const [yearOnly, dayPrecise, unknown] = normalizeWikidataResults({
results: {
bindings: [
{
item: entity('Q11'),
itemLabel: { value: 'Year only' },
coord: { value: 'Point(1 50)' },
end: { value: '1917-01-01T00:00:00Z' },
endPrecision: { value: '9' },
},
{
item: entity('Q12'),
itemLabel: { value: 'Day precise' },
coord: { value: 'Point(2 50)' },
end: { value: '1917-03-04T00:00:00Z' },
endPrecision: { value: '11' },
},
{
item: entity('Q13'),
itemLabel: { value: 'No precision' },
coord: { value: 'Point(3 50)' },
end: { value: '1917-01-01T00:00:00Z' },
},
],
},
});
assert.equal(yearOnly.date, null, 'not shown as 1 Jan 1917');
assert.equal(yearOnly.year, 1917);
assert.equal(dayPrecise.date, '1917-03-04');
assert.equal(unknown.date, null, 'an unknown precision is a year only');
assert.equal(unknown.year, 1917);
});
test('the query reads the end of service with its precision', () => {
assert.match(
WIKIDATA_QUERY,
/psv:P576 \[ wikibase:timeValue \?end ; wikibase:timePrecision \?endPrecision \]/,
);
assert.doesNotMatch(WIKIDATA_QUERY, /wdt:P576/);
});