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 +21 -12
- package/package.json +34 -30
- package/src/fetch.js +131 -272
- package/src/global-data.json +34 -4
- package/src/index.js +7 -0
- package/src/render.js +552 -0
- package/src/sparql-builder.js +25 -8
- package/src/wikidata.js +67 -13
- package/styles/style.css +22 -5
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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.
|
|
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
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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:
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
|
99
|
+
function rangeUnionAdv(value, min, max)
|
|
107
100
|
{
|
|
108
|
-
|
|
101
|
+
var aggregate = value ?? {}
|
|
102
|
+
if (min)
|
|
109
103
|
{
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
130
|
-
return undefined
|
|
112
|
+
return aggregate
|
|
131
113
|
}
|
|
132
114
|
|
|
133
|
-
|
|
134
|
-
function produceOutput(items)
|
|
115
|
+
function rangeUnion(a, b)
|
|
135
116
|
{
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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 ? " " : "",
|
|
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 ? " " : "",
|
|
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 ? " " : "",
|
|
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
|
-
|
|
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
|
-
|
|
301
|
+
// clone the item for each result beyond the first
|
|
302
|
+
for (var i = 1; i < entityResult.length; ++i)
|
|
473
303
|
{
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
508
|
-
item.
|
|
509
|
-
item.
|
|
510
|
-
item.
|
|
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
|
-
|
|
517
|
-
item.
|
|
518
|
-
item.
|
|
519
|
-
item.
|
|
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:`)
|
package/src/global-data.json
CHANGED
|
@@ -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
|
-
"
|
|
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
|
}
|