vis-chronicle 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,23 @@
2
2
 
3
3
  vis-chronicle is a tool to generate web-based timelines from Wikidata SPARQL queries. You feed the tool a specification JSON file that describes what items to put on the timeline. The tool gathers the needed data from Wikidata and produces a JSON file suitable for feeding directly to [vis-timeline](https://github.com/visjs/vis-timeline).
4
4
 
5
- [vis-chronicle-demo](https://github.com/BrianMacIntosh/vis-chronicle-demo) is a demo application using this tool.
5
+ [vis-chronicle-demo](https://github.com/BrianMacIntosh/vis-chronicle-demo) is a demo application using this tool. View the output at https://brianmacintosh.github.io/vis-chronicle-demo/.
6
+
7
+ ## Installation
8
+
9
+ `npm install vis-chronicle --save-dev`
10
+
11
+ ## Execution
12
+
13
+ The tool can be run as a package script like `vis-chronicle ./src/timeline.json -v`.
14
+
15
+ The tool takes these parameters on the command line:
16
+ * (Mandatory) The name of the input file or directory. If a directory, all JSON files in the directory will be combined together.
17
+ * (Optional) The name of the output file. Defaults to `intermediate/timeline.json`.
18
+ * `-v[erbose]`: Print verbose output, including the actual queries being run.
19
+ * `-skip-wd-cache`: Do not read anything from the local cache, query all data fresh.
20
+ * `-q[uery-url]`: The URL of the SPARQL endpoint. Defaults to `https://query.wikidata.org/sparql`.
21
+ * `-l[ang]`: Language priorities to use when fetching labels. Defaults to `en,mul`. See [SPARQL/SERVICE - Label](https://en.wikibooks.org/wiki/SPARQL/SERVICE_-_Label).
6
22
 
7
23
  ## Example
8
24
 
@@ -82,14 +98,7 @@ Item properties:
82
98
  * `max`: The absolute maximum duration.
83
99
  * `avg`: The average duration.
84
100
 
85
- ## Parameters
86
-
87
- The tool can be run as a package script like `vis-chronicle ./src/timeline.json -v`.
88
-
89
- The tool takes these parameters on the command line:
90
- * (Mandatory) The name of the input file or directory. If a directory, all JSON files in the directory will be combined together.
91
- * (Optional) The name of the output file. Defaults to `intermediate/timeline.json`.
92
- * `-v[erbose]`: Print verbose output, including the actual queries being run.
93
- * `-skip-wd-cache`: Do not read anything from the local cache, query all data fresh.
94
- * `-q[uery-url]`: The URL of the SPARQL endpoint. Defaults to `https://query.wikidata.org/sparql`.
95
- * `-l[ang]`: Language priorities to use when fetching labels. Defaults to `en,mul`. See (SPARQL/SERVICE - Label)[https://en.wikibooks.org/wiki/SPARQL/SERVICE_-_Label].
101
+ `chronicle` contains configuration for chronicle itself (not passed through to the output):
102
+ * `defaultLabel`: Default format string to use for item labels without `label`. See `label`.
103
+ * `maxUncertainTimePrecision`: Default `10` (month). Maximum Wikidata time precision to treat as uncertain. Cleans up tiny, artifacty uncertainty ranges.
104
+ * `shareSuccessiveUncertainty`: Default `true`. If true, uncertain ends of successive items (related by P1365 'replaces' or P1366 'replaced by') can be truncated such that the items fit togther.
package/package.json CHANGED
@@ -1,30 +1,34 @@
1
- {
2
- "name": "vis-chronicle",
3
- "version": "0.0.2",
4
- "description": "Generates JSON for populating a vis.js timeline from Wikidata queries.",
5
- "keywords": [
6
- "wikidata", "visjs", "timeline"
7
- ],
8
- "homepage": "https://github.com/BrianMacIntosh/vis-chronicle",
9
- "bugs": "https://github.com/BrianMacIntosh/vis-chronicle/issues",
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/BrianMacIntosh/vis-chronicle.git"
13
- },
14
- "bin": {
15
- "vis-chronicle": "src/fetch.js"
16
- },
17
- "scripts": {
18
- },
19
- "author": {
20
- "name": "Brian MacIntosh",
21
- "email": "brianamacintosh@gmail.com",
22
- "url": "https://brianmacintosh.com"
23
- },
24
- "license": "ISC",
25
- "dependencies": {
26
- },
27
- "devDependencies": {
28
- "moment": "2.30.1"
29
- }
30
- }
1
+ {
2
+ "name": "vis-chronicle",
3
+ "version": "0.0.3",
4
+ "description": "Generates JSON for populating a vis.js timeline from Wikidata queries.",
5
+ "keywords": [
6
+ "wikidata", "visjs", "timeline"
7
+ ],
8
+ "homepage": "https://github.com/BrianMacIntosh/vis-chronicle",
9
+ "bugs": "https://github.com/BrianMacIntosh/vis-chronicle/issues",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/BrianMacIntosh/vis-chronicle.git"
13
+ },
14
+ "bin": {
15
+ "vis-chronicle": "src/fetch.js"
16
+ },
17
+ "exports": {
18
+ "./styles/style.css": "./styles/style.css",
19
+ ".": "./src/index.js"
20
+ },
21
+ "scripts": {
22
+ },
23
+ "author": {
24
+ "name": "Brian MacIntosh",
25
+ "email": "brianamacintosh@gmail.com",
26
+ "url": "https://brianmacintosh.com"
27
+ },
28
+ "license": "ISC",
29
+ "dependencies": {
30
+ },
31
+ "devDependencies": {
32
+ "moment": "2.30.1"
33
+ }
34
+ }
package/src/fetch.js CHANGED
@@ -37,30 +37,8 @@ if (!outputFile)
37
37
  const moment = require('moment')
38
38
  const fs = require('fs');
39
39
  const wikidata = require('./wikidata.js')
40
+ const renderer = require('./render.js')
40
41
  const mypath = require("./mypath.js")
41
- const globalData = require("./global-data.json")
42
-
43
- function postprocessDuration(duration)
44
- {
45
- if (duration.min)
46
- duration.min = moment.duration(duration.min)
47
- if (duration.max)
48
- duration.max = moment.duration(duration.max)
49
- if (duration.avg)
50
- duration.avg = moment.duration(duration.avg)
51
- return duration
52
- }
53
- function postprocessGlobalData()
54
- {
55
- for (const expectation of globalData.expectations)
56
- {
57
- if (expectation.duration)
58
- {
59
- postprocessDuration(expectation.duration)
60
- }
61
- }
62
- }
63
- postprocessGlobalData()
64
42
 
65
43
  wikidata.skipCache = values["skip-wd-cache"]
66
44
  wikidata.cacheBuster = values["cachebuster"]
@@ -69,8 +47,13 @@ wikidata.verboseLogging = values["verbose"]
69
47
  wikidata.setLang(values["lang"])
70
48
  wikidata.initialize()
71
49
 
72
- // produces a moment from a Wikidata time
73
- function wikidataToMoment(inTime)
50
+ const wikidataToMomentPrecision = [
51
+ undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined,
52
+ 'year', 'month', 'day', 'hour', 'minute', 'second'
53
+ ]
54
+
55
+ // produces a range of moments {min,max} from a Wikidata time
56
+ function wikidataToRange(inTime)
74
57
  {
75
58
  if (!inTime || !inTime.value)
76
59
  {
@@ -81,243 +64,62 @@ function wikidataToMoment(inTime)
81
64
  // moment has trouble with negative years unless they're six digits
82
65
  const date = moment.utc(inTime.value, 'YYYYYY-MM-DDThh:mm:ss')
83
66
 
84
- //TODO: do something nice in the GUI to indicate imprecision of times
85
67
  switch (inTime.precision)
86
68
  {
87
- case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9:
69
+ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
88
70
  const yearBase = Math.pow(10, 9 - inTime.precision)
89
- const roundedYear = Math.round(date.year() / yearBase) * yearBase
90
- return date.year(roundedYear).startOf('year')
91
- case 10: // month precision
92
- return date.startOf('month')
93
- case 11: // day precision
94
- return date.startOf('day')
95
- case 12: // hour precision
96
- return date.startOf('hour')
97
- case 13: // minute precision
98
- return date.startOf('minute')
99
- case 14: // second precision
100
- return date.startOf('second')
71
+ var roundedYear = Math.floor(date.year() / yearBase) * yearBase
72
+
73
+ // correct for lack of year 0 ("-19" is actually "-20 BC")
74
+ // and also for the fact that "-20 BC" is really on the positive side of e.g. -18
75
+ if (date.year() < 0) roundedYear += 2
76
+
77
+ return {
78
+ min: moment({year:roundedYear}),
79
+ max: moment({year:roundedYear + yearBase}).subtract(1, 'minute').endOf('year')
80
+ }
101
81
  default:
102
- throw `Unrecognized date precision ${inTime.precision}`
82
+ if (inTime.precision > wikidata.inputSpec.chronicle.maxUncertainTimePrecision)
83
+ {
84
+ const momentPrecision = wikidataToMomentPrecision[inTime.precision]
85
+ return { min: date.clone().startOf(momentPrecision), max: date.clone().startOf(momentPrecision) }
86
+ }
87
+ else if (inTime.precision < wikidataToMomentPrecision.length)
88
+ {
89
+ const momentPrecision = wikidataToMomentPrecision[inTime.precision]
90
+ return { min: date.clone().startOf(momentPrecision), max: date.clone().endOf(momentPrecision) }
91
+ }
92
+ else
93
+ {
94
+ throw `Unrecognized date precision ${inTime.precision}`
95
+ }
103
96
  }
104
97
  }
105
98
 
106
- function getExpectation(item)
99
+ function rangeUnionAdv(value, min, max)
107
100
  {
108
- if (item.expectedDuration)
101
+ var aggregate = value ?? {}
102
+ if (min)
109
103
  {
110
- return { "duration": item.expectedDuration };
104
+ assert(min.min)
105
+ aggregate.min = aggregate.min ? moment.max(min.min, aggregate.min) : min.min
111
106
  }
112
-
113
- for (const expectation of globalData.expectations)
107
+ if (max)
114
108
  {
115
- if (expectation.startQuery && item.startQuery != expectation.startQuery)
116
- {
117
- continue;
118
- }
119
- if (expectation.endQuery && item.endQuery != expectation.endQuery)
120
- {
121
- continue;
122
- }
123
- if (expectation.startEndQuery && item.startEndQuery != expectation.startEndQuery)
124
- {
125
- continue;
126
- }
127
- return expectation;
109
+ assert(max.max)
110
+ aggregate.max = aggregate.max ? moment.min(max.max, aggregate.max) : max.max
128
111
  }
129
- assert(false) // expect at least a universal fallback expectation
130
- return undefined
112
+ return aggregate
131
113
  }
132
114
 
133
- // produces JSON output from the queried data
134
- function produceOutput(items)
115
+ function rangeUnion(a, b)
135
116
  {
136
- const finalizeItem = function(item)
137
- {
138
- if (item.start)
139
- item.start = item.start.format("YYYYYY-MM-DDThh:mm:ss")
140
- if (item.end)
141
- item.end = item.end.format("YYYYYY-MM-DDThh:mm:ss")
142
- outputObject.items.push(item)
117
+ if (!a) return b
118
+ if (!b) return a
119
+ return {
120
+ min: a.min && b.min ? moment.min(a.min, b.min) : a.min || b.min,
121
+ max: a.max && b.max ? moment.max(a.max, b.max) : a.max || b.max
143
122
  }
144
-
145
- var outputObject = { items: [], groups: wikidata.inputSpec.groups, options: wikidata.inputSpec.options }
146
- for (const item of items)
147
- {
148
- var outputItem = {
149
- id: item.id,
150
- content: item.label,
151
- className: item.className,
152
- comment: item.comment,
153
- type: item.type
154
- }
155
- if (item.group)
156
- {
157
- outputItem.group = item.group
158
- outputItem.subgroup = item.subgroup ? item.subgroup : item.entity
159
- }
160
-
161
- const isRangeType = !outputItem.type || outputItem.type == "range" || outputItem.type == "background"
162
-
163
- // for debugging
164
- outputItem.className = [ outputItem.className, item.entity ].join(' ')
165
-
166
- // look up duration expectations
167
- const expectation = getExpectation(item)
168
-
169
- assert(expectation && expectation.duration && expectation.duration.avg) // expect at least a universal fallback expectation
170
-
171
- if (!item.start && !item.end
172
- && !item.start_min && !item.start_max
173
- && !item.end_min && !item.end_max)
174
- {
175
- console.warn(`Item ${item.id} has no date data at all.`)
176
- continue
177
- }
178
-
179
- outputItem.start = wikidataToMoment(item.start)
180
- outputItem.end = wikidataToMoment(item.end)
181
- const start_min = item.start_min ? wikidataToMoment(item.start_min) : outputItem.start
182
- const start_max = item.start_max ? wikidataToMoment(item.start_max) : outputItem.start
183
- const end_min = item.end_min ? wikidataToMoment(item.end_min) : outputItem.end
184
- const end_max = item.end_max ? wikidataToMoment(item.end_max) : outputItem.end
185
-
186
- // exclude items that violate itemRange constraints
187
- //OPT: do this at an earlier stage? (e.g. when running the first query)
188
- if (item.itemRange)
189
- {
190
- if (item.itemRange.min && moment(item.itemRange.min).isAfter(end_max))
191
- continue
192
- if (item.itemRange.max && moment(item.itemRange.max).isBefore(end_max))
193
- continue
194
- }
195
-
196
- // no certainty at all
197
- if (start_max >= end_min)
198
- {
199
- outputItem.className = [ outputItem.className, 'visc-uncertain' ].join(' ')
200
- outputItem.start = start_min
201
- outputItem.end = end_max
202
-
203
- finalizeItem(outputItem)
204
- continue
205
- }
206
-
207
- if (!isRangeType)
208
- {
209
- finalizeItem(outputItem)
210
- continue
211
- }
212
-
213
- // handle end date
214
- if (end_min && end_max && end_min < end_max)
215
- {
216
- // uncertain end
217
-
218
- // find lower bound of uncertain region
219
- var uncertainMin
220
- if (item.end_min)
221
- uncertainMin = end_min
222
- else if (outputItem.end)
223
- uncertainMin = outputItem.end
224
- else
225
- uncertainMin = outputItem.start //TODO: use min/max start
226
- assert(uncertainMin)
227
-
228
- // add uncertain range
229
- outputObject.items.push({
230
- id: outputItem.id + "-unc-end",
231
- className: [outputItem.className, "visc-uncertain", "visc-left-connection"].join(' '),
232
- content: item.label ? "&nbsp;" : "",
233
- start: uncertainMin.format("YYYYYY-MM-DDThh:mm:ss"),
234
- end: end_max.format("YYYYYY-MM-DDThh:mm:ss"),
235
- group: item.group,
236
- subgroup: outputItem.subgroup
237
- })
238
-
239
- // adjust normal range to match
240
- outputItem.end = uncertainMin
241
- outputItem.className = [ outputItem.className, 'visc-right-connection' ].join(' ')
242
- }
243
- else if (!outputItem.end)
244
- {
245
- // open-ended end
246
- const useMax = expectation.duration.max ? expectation.duration.max : moment(expectation.duration.avg.asMilliseconds() * 2)
247
- if (outputItem.start.clone().add(useMax) < moment())
248
- {
249
- // 'max' is less than 'now'; it is likely this duration is not ongoing but has an unknown end
250
- //TODO: accomodate wikidata special 'no value'
251
- outputItem.end = outputItem.start.clone().add(expectation.duration.avg)
252
- }
253
- else
254
- {
255
- // 'now' is within 'max' and so it is a reasonable guess that this duration is ongoing
256
- outputItem.end = moment()
257
- }
258
-
259
- const avgDuration = moment.duration(expectation.duration.avg) //HACK: TODO: consistently postprocess expectations, or don't
260
- const actualDuration = moment.duration(outputItem.end.diff(outputItem.start))
261
- var excessDuration = moment.duration(avgDuration.asMilliseconds()).subtract(actualDuration)
262
- excessDuration = moment.duration(Math.max(excessDuration.asMilliseconds(), avgDuration.asMilliseconds() * 0.25))
263
-
264
- // add a "tail" item after the end
265
- outputObject.items.push({
266
- id: outputItem.id + "-tail",
267
- className: [outputItem.className, "visc-right-tail"].join(' '),
268
- content: item.label ? "&nbsp;" : "",
269
- start: outputItem.end.format("YYYYYY-MM-DDThh:mm:ss"),
270
- end: outputItem.end.clone().add(excessDuration).format("YYYYYY-MM-DDThh:mm:ss"), //HACK: magic number
271
- group: item.group,
272
- subgroup: outputItem.subgroup
273
- })
274
-
275
- outputItem.className = [ outputItem.className, 'visc-right-connection' ].join(' ')
276
- }
277
-
278
- // handle start date
279
- if (start_min && start_max && start_max > start_min)
280
- {
281
- // uncertain start
282
-
283
- // find upper bound of uncertain region
284
- var uncertainMax
285
- if (item.start_max)
286
- uncertainMax = start_max
287
- else if (outputItem.start)
288
- uncertainMax = outputItem.start
289
- else
290
- uncertainMax = outputItem.end //TODO: use min/max end
291
- assert(uncertainMax)
292
-
293
- // add uncertain range
294
- outputObject.items.push({
295
- id: outputItem.id + "-unc-start",
296
- className: [outputItem.className, "visc-uncertain", "visc-right-connection"].join(' '),
297
- content: item.label ? "&nbsp;" : "",
298
- start: start_min.format("YYYYYY-MM-DDThh:mm:ss"),
299
- end: uncertainMax.format("YYYYYY-MM-DDThh:mm:ss"),
300
- group: item.group,
301
- subgroup: outputItem.subgroup
302
- })
303
-
304
- // adjust normal range to match
305
- outputItem.start = uncertainMax
306
- outputItem.className = [ outputItem.className, 'visc-left-connection' ].join(' ')
307
- }
308
- else if (!outputItem.start)
309
- {
310
- // open-ended start
311
- outputItem.start = outputItem.end.clone().subtract(expectation.duration.avg)
312
- outputItem.className = [outputItem.className, "visc-open-left"].join(' ')
313
- }
314
-
315
- //TODO: missing death dates inside expected duration: solid to NOW, fade after NOW
316
- //TODO: accept expected durations and place uncertainly before/after those
317
-
318
- finalizeItem(outputItem)
319
- }
320
- return JSON.stringify(outputObject, undefined, "\t") //TODO: configure space
321
123
  }
322
124
 
323
125
  async function entryPoint() {}
@@ -458,34 +260,91 @@ entryPoint()
458
260
 
459
261
  const representativeItem = bundle[0]
460
262
 
461
- const copyResult = function(result, func)
263
+ // Populates output items from a query result.
264
+ // Multiple values will be treated as multiple items.
265
+ // Also expects both start and end ranges.
266
+ const copyMultipleResult = function(result, func)
462
267
  {
268
+ const aggregateHelper = function(item, entityResult)
269
+ {
270
+ var aggregateStart = rangeUnionAdv(
271
+ wikidataToRange(entityResult.start),
272
+ wikidataToRange(entityResult.start_min),
273
+ wikidataToRange(entityResult.start_max))
274
+ var aggregateEnd = rangeUnionAdv(
275
+ wikidataToRange(entityResult.end),
276
+ wikidataToRange(entityResult.end_min),
277
+ wikidataToRange(entityResult.end_max))
278
+
279
+ var aggregateResult = {
280
+ start_min: aggregateStart.min,
281
+ start_max: aggregateStart.max,
282
+ end_min: aggregateEnd.min,
283
+ end_max: aggregateEnd.max,
284
+ previous: entityResult.previous,
285
+ next: entityResult.next
286
+ }
287
+ func(item, aggregateResult)
288
+ item.finished = true
289
+ }
290
+
463
291
  for (const entityId in result)
464
292
  {
465
293
  var entityResult = result[entityId]
294
+ if (!(entityResult instanceof Array)) entityResult = [ entityResult ]
466
295
 
467
296
  // there may be multiple source items making the same query
468
297
  for (const item of bundle)
469
298
  {
470
299
  if (item.entity == entityId)
471
300
  {
472
- if (entityResult instanceof Array)
301
+ // clone the item for each result beyond the first
302
+ for (var i = 1; i < entityResult.length; ++i)
473
303
  {
474
- // clone the item for each result beyond the first
475
- for (var i = 1; i < entityResult.length; ++i)
476
- {
477
- const newItem = structuredClone(item)
478
- newItem.id = `${newItem.id}-v${i}`
479
- wikidata.inputSpec.items.push(newItem) //HACK: modifying original array
480
- func(newItem, entityResult[i])
481
- newItem.finished = true
482
- }
483
-
484
- // populate the first result into the original item
485
- entityResult = entityResult[0]
304
+ const newItem = structuredClone(item)
305
+ newItem.id = `${newItem.id}-v${i}`
306
+
307
+ // clones get new subgroups so they are treated as separate objects for stacking
308
+ newItem.subgroup = `${item.subgroup ? item.subgroup : item.entity}-clone${i}`
309
+
310
+ wikidata.inputSpec.items.push(newItem) //HACK: modifying original array
311
+ aggregateHelper(newItem, entityResult[i])
486
312
  }
487
-
488
- func(item, entityResult)
313
+
314
+ // populate the first result into the original item
315
+ aggregateHelper(item, entityResult[0])
316
+ }
317
+ }
318
+ }
319
+ }
320
+
321
+ // Populates output items from a query result.
322
+ // Multiple values will be treated as uncertainty.
323
+ const copySingleResult = function(result, func)
324
+ {
325
+ for (const entityId in result)
326
+ {
327
+ var entityResult = result[entityId]
328
+ if (!(entityResult instanceof Array)) entityResult = [ entityResult ]
329
+
330
+ var aggregateResult = {}
331
+ for (var i = 0; i < entityResult.length; ++i)
332
+ {
333
+ var selfAggregate = rangeUnionAdv(
334
+ wikidataToRange(entityResult[i].value),
335
+ wikidataToRange(entityResult[i].min),
336
+ wikidataToRange(entityResult[i].max))
337
+ aggregateResult = rangeUnion(aggregateResult, selfAggregate)
338
+ aggregateResult.previous = aggregateResult.previous ?? entityResult[i].previous //HACK: does not handle multiple values
339
+ aggregateResult.next = aggregateResult.previous ?? entityResult[i].next
340
+ }
341
+
342
+ // there may be multiple source items making the same query
343
+ for (const item of bundle)
344
+ {
345
+ if (item.entity == entityId)
346
+ {
347
+ func(item, aggregateResult)
489
348
  item.finished = true
490
349
  }
491
350
  }
@@ -495,7 +354,7 @@ entryPoint()
495
354
  if (representativeItem.startEndQuery)
496
355
  {
497
356
  const result = await wikidata.runTimeQueryTerm(representativeItem.startEndQuery, bundle)
498
- copyResult(result, function(item, result) {
357
+ copyMultipleResult(result, function(item, result) {
499
358
  Object.assign(item, result)
500
359
  })
501
360
  }
@@ -504,19 +363,19 @@ entryPoint()
504
363
  if (representativeItem.startQuery)
505
364
  {
506
365
  const result = await wikidata.runTimeQueryTerm(representativeItem.startQuery, bundle)
507
- copyResult(result, function(item, result) {
508
- item.start = result.value;
509
- item.start_min = result.min;
510
- item.start_max = result.max;
366
+ copySingleResult(result, function(item, result) {
367
+ item.start_min = result.min
368
+ item.start_max = result.max
369
+ item.previous = result.previous
511
370
  })
512
371
  }
513
372
  if (representativeItem.endQuery)
514
373
  {
515
374
  const result = await wikidata.runTimeQueryTerm(representativeItem.endQuery, bundle)
516
- copyResult(result, function(item, result) {
517
- item.end = result.value;
518
- item.end_min = result.min;
519
- item.end_max = result.max;
375
+ copySingleResult(result, function(item, result) {
376
+ item.end_min = result.min
377
+ item.end_max = result.max
378
+ item.next = result.next
520
379
  })
521
380
  }
522
381
  }
@@ -527,7 +386,7 @@ entryPoint()
527
386
  await mypath.ensureDirectoryForFile(outputFile)
528
387
 
529
388
  // write the output file
530
- const output = produceOutput(wikidata.inputSpec.items)
389
+ const output = renderer.produceOutput(wikidata.inputSpec, wikidata.inputSpec.items)
531
390
  await fs.writeFile(outputFile, output, err => {
532
391
  if (err) {
533
392
  console.error(`Error writing output file:`)
@@ -19,24 +19,42 @@
19
19
  "start_max": "?_prop pqv:P8555 ?_start_max_value.",
20
20
  "end": "?_prop pqv:P582 ?_end_value.",
21
21
  "end_min": "?_prop pqv:P8554 ?_end_min_value.",
22
- "end_max": "?_prop pqv:P12506 ?_end_max_value."
22
+ "end_max": "?_prop pqv:P12506 ?_end_max_value.",
23
+ "previous": "?_prop pq:P1365 ?_previous_value.",
24
+ "next": "?_prop pq:P1366 ?_next_value."
23
25
  },
24
- "positionJurisdictionHeldStartEnd": {
26
+ "positionHeldJurisdictionStartEnd": {
25
27
  "general": "{entity} p:P39 ?_prop. ?_prop ps:P39 {position}; pq:P1001 {jurisdiction}.",
26
28
  "start": "?_prop pqv:P580 ?_start_value.",
27
29
  "start_min": "?_prop pqv:P1319 ?_start_min_value.",
28
30
  "start_max": "?_prop pqv:P8555 ?_start_max_value.",
29
31
  "end": "?_prop pqv:P582 ?_end_value.",
30
32
  "end_min": "?_prop pqv:P8554 ?_end_min_value.",
31
- "end_max": "?_prop pqv:P12506 ?_end_max_value."
33
+ "end_max": "?_prop pqv:P12506 ?_end_max_value.",
34
+ "previous": "?_prop pq:P1365 ?_previous_value.",
35
+ "next": "?_prop pq:P1366 ?_next_value."
36
+ },
37
+ "positionOrSubclassHeldJurisdictionStartEnd": {
38
+ "general": "{entity} p:P39 ?_prop. ?_prop ps:P39 [ wdt:P279* {position} ]; pq:P1001 {jurisdiction}.",
39
+ "start": "?_prop pqv:P580 ?_start_value.",
40
+ "start_min": "?_prop pqv:P1319 ?_start_min_value.",
41
+ "start_max": "?_prop pqv:P8555 ?_start_max_value.",
42
+ "end": "?_prop pqv:P582 ?_end_value.",
43
+ "end_min": "?_prop pqv:P8554 ?_end_min_value.",
44
+ "end_max": "?_prop pqv:P12506 ?_end_max_value.",
45
+ "previous": "?_prop pq:P1365 ?_previous_value.",
46
+ "next": "?_prop pq:P1366 ?_next_value."
32
47
  }
33
48
  },
34
49
  "itemQueryTemplates":
35
50
  {
36
51
  "withPosition": "?_node wdt:P39 {position}.",
52
+ "withPositionJurisdiction": "?_node p:P39 [ ps:P39 {position}; pq:P1001 {jurisdiction} ].",
37
53
 
38
54
  "humansWithPosition": "?_node wdt:P39 {position}. ?_node wdt:P31 wd:Q5.",
39
- "humansWithPositionJurisdiction": "?_node p:P39 [ ps:P39 {position}; pq:P1001 {jurisdiction} ]. ?_node wdt:P31 wd:Q5."
55
+ "humansWithPositionJurisdiction": "?_node p:P39 [ ps:P39 {position}; pq:P1001 {jurisdiction} ]. ?_node wdt:P31 wd:Q5.",
56
+
57
+ "humansWithPositionOrSubclassJurisdiction": "?_node p:P39 [ ps:P39 [ wdt:P279* {position} ]; pq:P1001 {jurisdiction}]."
40
58
  },
41
59
  "expectations":
42
60
  [
@@ -45,6 +63,18 @@
45
63
  "endQuery": "#dateOfDeath",
46
64
  "duration": { "avg": "P80Y", "max": "P150Y" }
47
65
  },
66
+ {
67
+ "startEndQuery": "#positionHeldStartEnd",
68
+ "duration": { "max": "P150Y" }
69
+ },
70
+ {
71
+ "startEndQuery": "#positionHeldJurisdictionStartEnd",
72
+ "duration": { "max": "P150Y" }
73
+ },
74
+ {
75
+ "startEndQuery": "#positionOrSubclassHeldJurisdictionStartEnd",
76
+ "duration": { "max": "P150Y" }
77
+ },
48
78
  {
49
79
  "duration": { "avg": "P100Y" }
50
80
  }
package/src/index.js ADDED
@@ -0,0 +1,7 @@
1
+
2
+ module.exports = {
3
+
4
+ SparqlBuilder: require('./sparql-builder'),
5
+ Wikidata: require('./wikidata.js'),
6
+
7
+ }