wikidata_position_history 1.11.0 → 2.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/exe/position-history-for-item +1 -1
- data/lib/sparql/bio_query.rb +16 -0
- data/lib/sparql/mandates_query.rb +24 -5
- data/lib/sparql/position_query.rb +20 -1
- data/lib/wikidata_position_history/checks.rb +16 -14
- data/lib/wikidata_position_history/output_row.rb +2 -2
- data/lib/wikidata_position_history/report.rb +62 -17
- data/lib/wikidata_position_history/template.rb +1 -1
- data/lib/wikidata_position_history/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 52e5490ed1d01167632b8adc9cb5d9a37912a5accad858b5d2ada5aaa43522ff
|
|
4
|
+
data.tar.gz: ab39a9aed506e9c77834ed7a7fdc6a9a06006eaee84f14ee9fb0c71eb069c5a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e7aae1fb45f43c0ba961d878de171e4150194514dc406e0059a8c9bc2544ce2e9549d4e9e07c5da6b7ccfe8a6aec534735c1f645fb026a5eb04512b4cee7f9cb
|
|
7
|
+
data.tar.gz: 797ab543a4a0ee636ed833a1f200c266ebed7b60872084b6f9b0156b11920dc619c15d4bc19b8326ca747c9f5585df4a81be7b23c5fbdd07a177e3ded1f3f702
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
# [2.0.0] 2020-09-16
|
|
4
|
+
|
|
5
|
+
## Interface change
|
|
6
|
+
|
|
7
|
+
* `Report#wikitext_with_header` has been removed. This was undocumented,
|
|
8
|
+
and only used internally, so should not be a breaking change, but if
|
|
9
|
+
anything *was* using it, that will now break loudly (but, usefully,
|
|
10
|
+
should also break very early.)
|
|
11
|
+
|
|
12
|
+
## Enhancements
|
|
13
|
+
|
|
14
|
+
* A {{PositionHolderHistory}} template can now also be added to items
|
|
15
|
+
representing single-member constituencies, to see the history of
|
|
16
|
+
representatives for that seat.
|
|
17
|
+
|
|
3
18
|
# [1.11.0] 2020-09-14
|
|
4
19
|
|
|
5
20
|
## Enhancements
|
data/lib/sparql/bio_query.rb
CHANGED
|
@@ -21,6 +21,22 @@ module WikidataPositionHistory
|
|
|
21
21
|
SPARQL
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
# Biographical data for Members for a Constituency
|
|
26
|
+
class ConstituencyBioQuery < ItemQuery
|
|
27
|
+
def raw_sparql
|
|
28
|
+
<<~SPARQL
|
|
29
|
+
# constituency-biodata
|
|
30
|
+
|
|
31
|
+
SELECT DISTINCT ?item ?image
|
|
32
|
+
WHERE {
|
|
33
|
+
?item wdt:P31 wd:Q5 ; p:P39/pq:P768 wd:%s .
|
|
34
|
+
OPTIONAL { ?item wdt:P18 ?image }
|
|
35
|
+
}
|
|
36
|
+
ORDER BY ?item
|
|
37
|
+
SPARQL
|
|
38
|
+
end
|
|
39
|
+
end
|
|
24
40
|
end
|
|
25
41
|
|
|
26
42
|
# Represents a single row returned from the Position query
|
|
@@ -25,6 +25,30 @@ module WikidataPositionHistory
|
|
|
25
25
|
SPARQL
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
# SPARQL for fetching all mandates of a single-member district
|
|
30
|
+
class ConstituencyMandatesQuery < ItemQuery
|
|
31
|
+
def raw_sparql
|
|
32
|
+
<<~SPARQL
|
|
33
|
+
# constituency-mandates
|
|
34
|
+
|
|
35
|
+
SELECT DISTINCT ?ordinal ?item ?start_date ?start_precision ?end_date ?end_precision ?prev ?next ?nature
|
|
36
|
+
WHERE {
|
|
37
|
+
?item wdt:P31 wd:Q5 ; p:P39 ?posn .
|
|
38
|
+
?posn pq:P768 wd:%s .
|
|
39
|
+
FILTER NOT EXISTS { ?posn wikibase:rank wikibase:DeprecatedRank }
|
|
40
|
+
|
|
41
|
+
OPTIONAL { ?posn pqv:P580 [ wikibase:timeValue ?start_date; wikibase:timePrecision ?start_precision ] }
|
|
42
|
+
OPTIONAL { ?posn pqv:P582 [ wikibase:timeValue ?end_date; wikibase:timePrecision ?end_precision ] }
|
|
43
|
+
OPTIONAL { ?posn pq:P1365|pq:P155 ?prev }
|
|
44
|
+
OPTIONAL { ?posn pq:P1366|pq:P156 ?next }
|
|
45
|
+
OPTIONAL { ?posn pq:P1545 ?ordinal }
|
|
46
|
+
OPTIONAL { ?posn pq:P5102 ?nature }
|
|
47
|
+
}
|
|
48
|
+
ORDER BY DESC(?start_date) ?item
|
|
49
|
+
SPARQL
|
|
50
|
+
end
|
|
51
|
+
end
|
|
28
52
|
end
|
|
29
53
|
|
|
30
54
|
# Represents a single row returned from the Mandates query
|
|
@@ -37,11 +61,6 @@ module WikidataPositionHistory
|
|
|
37
61
|
item_from(:item)
|
|
38
62
|
end
|
|
39
63
|
|
|
40
|
-
# TODO: rename or remove. 'item' is meaningless/ambiguous
|
|
41
|
-
def item
|
|
42
|
-
officeholder.qlink
|
|
43
|
-
end
|
|
44
|
-
|
|
45
64
|
# TODO: switch to item_from
|
|
46
65
|
def prev
|
|
47
66
|
QueryService::WikidataItem.new(row.dig(:prev, :value)).qlink
|
|
@@ -11,10 +11,13 @@ module WikidataPositionHistory
|
|
|
11
11
|
SELECT DISTINCT ?item ?inception ?inception_precision ?abolition ?abolition_precision
|
|
12
12
|
?replaces ?replacedBy ?derivedReplaces ?derivedReplacedBy
|
|
13
13
|
?isPosition ?isLegislator
|
|
14
|
+
?isConstituency ?representative_count ?legislature
|
|
14
15
|
WHERE {
|
|
15
16
|
VALUES ?item { wd:%s }
|
|
16
17
|
BIND(EXISTS { wd:%s wdt:P279+ wd:Q4164871 } as ?isPosition)
|
|
17
18
|
BIND(EXISTS { wd:%s wdt:P279+ wd:Q4175034 } as ?isLegislator)
|
|
19
|
+
BIND(EXISTS { wd:%s wdt:P31/wdt:P279+ wd:Q192611 } as ?isConstituency)
|
|
20
|
+
|
|
18
21
|
OPTIONAL { ?item p:P571 [ a wikibase:BestRank ;
|
|
19
22
|
psv:P571 [ wikibase:timeValue ?inception; wikibase:timePrecision ?inception_precision ]
|
|
20
23
|
] }
|
|
@@ -25,12 +28,16 @@ module WikidataPositionHistory
|
|
|
25
28
|
OPTIONAL { ?item wdt:P1366 ?replacedBy }
|
|
26
29
|
OPTIONAL { ?derivedReplaces wdt:P1366 ?item }
|
|
27
30
|
OPTIONAL { ?derivedReplacedBy wdt:P1365 ?item }
|
|
31
|
+
|
|
32
|
+
OPTIONAL { # if constituency
|
|
33
|
+
?item p:P1410 [ a wikibase:BestRank ; ps:P1410 ?representative_count ; pq:P194 ?legislature ]
|
|
34
|
+
}
|
|
28
35
|
}
|
|
29
36
|
SPARQL
|
|
30
37
|
end
|
|
31
38
|
|
|
32
39
|
def sparql_args
|
|
33
|
-
[itemid] *
|
|
40
|
+
[itemid] * 4
|
|
34
41
|
end
|
|
35
42
|
end
|
|
36
43
|
end
|
|
@@ -72,5 +79,17 @@ module WikidataPositionHistory
|
|
|
72
79
|
def legislator?
|
|
73
80
|
raw(:isLegislator) == 'true'
|
|
74
81
|
end
|
|
82
|
+
|
|
83
|
+
def constituency?
|
|
84
|
+
raw(:isConstituency) == 'true'
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def legislature
|
|
88
|
+
item_from(:legislature)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def representative_count
|
|
92
|
+
raw(:representative_count).to_i
|
|
93
|
+
end
|
|
75
94
|
end
|
|
76
95
|
end
|
|
@@ -17,11 +17,12 @@ module WikidataPositionHistory
|
|
|
17
17
|
|
|
18
18
|
attr_reader :later, :current, :earlier
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
# TODO: replace these with objects instead of strings
|
|
21
|
+
def successor_qlink
|
|
21
22
|
current.next
|
|
22
23
|
end
|
|
23
24
|
|
|
24
|
-
def
|
|
25
|
+
def predecessor_qlink
|
|
25
26
|
current.prev
|
|
26
27
|
end
|
|
27
28
|
|
|
@@ -46,7 +47,7 @@ module WikidataPositionHistory
|
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
def possible_explanation
|
|
49
|
-
"#{current.
|
|
50
|
+
"#{current.officeholder.qlink} is missing #{missing.map { |field| "{{P|#{field_map[field]}}}" }.join(', ')}"
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
def missing
|
|
@@ -76,14 +77,14 @@ module WikidataPositionHistory
|
|
|
76
77
|
|
|
77
78
|
def expect_prev?
|
|
78
79
|
return unless earlier
|
|
79
|
-
return if earlier.
|
|
80
|
+
return if earlier.officeholder.id == current.officeholder.id # sucessive terms by same person
|
|
80
81
|
|
|
81
82
|
!current.acting?
|
|
82
83
|
end
|
|
83
84
|
|
|
84
85
|
def expect_next?
|
|
85
86
|
return unless later
|
|
86
|
-
return if later.
|
|
87
|
+
return if later.officeholder.id == current.officeholder.id # sucessive terms by same person
|
|
87
88
|
|
|
88
89
|
!current.acting?
|
|
89
90
|
end
|
|
@@ -92,7 +93,7 @@ module WikidataPositionHistory
|
|
|
92
93
|
# Does the 'replaces' match the previous item in the list?
|
|
93
94
|
class WrongPredecessor < Check
|
|
94
95
|
def problem?
|
|
95
|
-
earlier_holder? && !!
|
|
96
|
+
earlier_holder? && !!predecessor_qlink && (earlier.officeholder.qlink != predecessor_qlink)
|
|
96
97
|
end
|
|
97
98
|
|
|
98
99
|
def headline
|
|
@@ -100,14 +101,14 @@ module WikidataPositionHistory
|
|
|
100
101
|
end
|
|
101
102
|
|
|
102
103
|
def possible_explanation
|
|
103
|
-
"#{current.
|
|
104
|
+
"#{current.officeholder.qlink} has a {{P|1365}} of #{predecessor_qlink}, but follows #{earlier.officeholder.qlink} here"
|
|
104
105
|
end
|
|
105
106
|
end
|
|
106
107
|
|
|
107
108
|
# Is there a 'replaces' but no previous item in the list?
|
|
108
109
|
class MissingPredecessor < Check
|
|
109
110
|
def problem?
|
|
110
|
-
|
|
111
|
+
predecessor_qlink && !earlier_holder?
|
|
111
112
|
end
|
|
112
113
|
|
|
113
114
|
def headline
|
|
@@ -115,14 +116,14 @@ module WikidataPositionHistory
|
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
def possible_explanation
|
|
118
|
-
"#{current.
|
|
119
|
+
"#{current.officeholder.qlink} has a {{P|1365}} of #{predecessor_qlink}, but does not follow anyone here"
|
|
119
120
|
end
|
|
120
121
|
end
|
|
121
122
|
|
|
122
123
|
# Does the 'replaced by' match the next item in the list?
|
|
123
124
|
class WrongSuccessor < Check
|
|
124
125
|
def problem?
|
|
125
|
-
later_holder? && !!
|
|
126
|
+
later_holder? && !!successor_qlink && (later.officeholder.qlink != successor_qlink)
|
|
126
127
|
end
|
|
127
128
|
|
|
128
129
|
def headline
|
|
@@ -130,14 +131,14 @@ module WikidataPositionHistory
|
|
|
130
131
|
end
|
|
131
132
|
|
|
132
133
|
def possible_explanation
|
|
133
|
-
"#{current.
|
|
134
|
+
"#{current.officeholder.qlink} has a {{P|1366}} of #{successor_qlink}, but is followed by #{later.officeholder.qlink} here"
|
|
134
135
|
end
|
|
135
136
|
end
|
|
136
137
|
|
|
137
138
|
# Is there a 'replaced by' but no next item in the list?
|
|
138
139
|
class MissingSuccessor < Check
|
|
139
140
|
def problem?
|
|
140
|
-
|
|
141
|
+
successor_qlink && !later_holder?
|
|
141
142
|
end
|
|
142
143
|
|
|
143
144
|
def headline
|
|
@@ -145,7 +146,7 @@ module WikidataPositionHistory
|
|
|
145
146
|
end
|
|
146
147
|
|
|
147
148
|
def possible_explanation
|
|
148
|
-
"#{current.
|
|
149
|
+
"#{current.officeholder.qlink} has a {{P|1366}} of #{successor_qlink}, but is not followed by anyone here"
|
|
149
150
|
end
|
|
150
151
|
end
|
|
151
152
|
|
|
@@ -167,7 +168,8 @@ module WikidataPositionHistory
|
|
|
167
168
|
end
|
|
168
169
|
|
|
169
170
|
def possible_explanation
|
|
170
|
-
|
|
171
|
+
format('%s has a {{P|582}} of %s, which %s the {{P|580}} of %s for %s',
|
|
172
|
+
current.officeholder.qlink, current.end_date, overlap_explanation, later.start_date, later.officeholder.qlink)
|
|
171
173
|
end
|
|
172
174
|
|
|
173
175
|
protected
|
|
@@ -68,6 +68,15 @@ module WikidataPositionHistory
|
|
|
68
68
|
rows.map(&:legislator?).first
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
def constituency?
|
|
72
|
+
# this should be the same everywhere
|
|
73
|
+
rows.map(&:constituency?).first
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def representative_count
|
|
77
|
+
rows.map(&:representative_count).max
|
|
78
|
+
end
|
|
79
|
+
|
|
71
80
|
def replaces_combined
|
|
72
81
|
@replaces_combined ||= ImpliedList.new(uniq_by_id(:replaces), uniq_by_id(:derived_replaces))
|
|
73
82
|
end
|
|
@@ -93,6 +102,48 @@ module WikidataPositionHistory
|
|
|
93
102
|
end
|
|
94
103
|
end
|
|
95
104
|
|
|
105
|
+
# Construct the correct ReportConfig based on the position metadata
|
|
106
|
+
class ReportConfigFactory
|
|
107
|
+
def self.config(metadata)
|
|
108
|
+
return ReportConfig::Constituency.new if metadata.constituency?
|
|
109
|
+
|
|
110
|
+
ReportConfig::Position.new
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
attr_reader :metadata
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Encapsulates the different configuration for each type of position
|
|
119
|
+
module ReportConfig
|
|
120
|
+
# Configuration for 'default' single-holder position
|
|
121
|
+
class Position
|
|
122
|
+
def mandates_query
|
|
123
|
+
SPARQL::MandatesQuery
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def biodata_query
|
|
127
|
+
SPARQL::BioQuery
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Configuration for representatives of a single-member constituency
|
|
132
|
+
class Constituency
|
|
133
|
+
def mandates_query
|
|
134
|
+
SPARQL::ConstituencyMandatesQuery
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def biodata_query
|
|
138
|
+
SPARQL::ConstituencyBioQuery
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def multimember_error_template
|
|
142
|
+
"\n{{PositionHolderHistory/error_multimember}}\n"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
96
147
|
# The entire wikitext generated for this report
|
|
97
148
|
class Report
|
|
98
149
|
def initialize(position_id, template_class = ReportTemplate)
|
|
@@ -104,26 +155,12 @@ module WikidataPositionHistory
|
|
|
104
155
|
|
|
105
156
|
def wikitext
|
|
106
157
|
return legislator_template if metadata.legislator?
|
|
158
|
+
return config.multimember_error_template if metadata.constituency? && (metadata.representative_count != 1)
|
|
107
159
|
return no_items_output if mandates.empty?
|
|
108
160
|
|
|
109
161
|
template_class.new(template_params).output
|
|
110
162
|
end
|
|
111
163
|
|
|
112
|
-
def header
|
|
113
|
-
"== {{Q|#{position_id}}} officeholders #{position_dates} =="
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
def position_dates
|
|
117
|
-
dates = [metadata.inception.date, metadata.abolition.date]
|
|
118
|
-
return '' if dates.compact.empty?
|
|
119
|
-
|
|
120
|
-
format('(%s)', dates.join(' – '))
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def wikitext_with_header
|
|
124
|
-
[header, wikitext].join("\n")
|
|
125
|
-
end
|
|
126
|
-
|
|
127
164
|
def template_params
|
|
128
165
|
{
|
|
129
166
|
metadata: metadata,
|
|
@@ -139,7 +176,7 @@ module WikidataPositionHistory
|
|
|
139
176
|
end
|
|
140
177
|
|
|
141
178
|
def biodata
|
|
142
|
-
@biodata ||=
|
|
179
|
+
@biodata ||= biodata_sparql.results_as(BioRow)
|
|
143
180
|
end
|
|
144
181
|
|
|
145
182
|
def biodata_for(officeholder)
|
|
@@ -150,8 +187,16 @@ module WikidataPositionHistory
|
|
|
150
187
|
[nil, mandates, nil].flatten(1)
|
|
151
188
|
end
|
|
152
189
|
|
|
190
|
+
def config
|
|
191
|
+
@config ||= ReportConfigFactory.config(metadata)
|
|
192
|
+
end
|
|
193
|
+
|
|
153
194
|
def sparql
|
|
154
|
-
@sparql ||=
|
|
195
|
+
@sparql ||= config.mandates_query.new(position_id)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def biodata_sparql
|
|
199
|
+
config.biodata_query.new(position_id)
|
|
155
200
|
end
|
|
156
201
|
|
|
157
202
|
def mandates
|
|
@@ -49,7 +49,7 @@ module WikidataPositionHistory
|
|
|
49
49
|
|-
|
|
50
50
|
| style="padding:0.5em 2em" | <%= mandate.ordinal_string %>
|
|
51
51
|
| style="padding:0.5em 2em" | <%= bio.map(&:image_link).first %>
|
|
52
|
-
| style="padding:0.5em 2em" | <span style="font-size: <%= mandate.acting? ? '1.25em; font-style: italic;' : '1.5em' %>; display: block;"><%= mandate.
|
|
52
|
+
| style="padding:0.5em 2em" | <span style="font-size: <%= mandate.acting? ? '1.25em; font-style: italic;' : '1.5em' %>; display: block;"><%= mandate.officeholder.qlink %></span> <%= mandate.dates %>
|
|
53
53
|
| style="padding:0.5em 2em 0.5em 1em; border: none; background: #fff; text-align: left;" | \
|
|
54
54
|
<% mandate.warnings.each do |warning| -%>
|
|
55
55
|
<span style="display: block">[[File:Pictogram voting comment.svg|15px|link=]] <span style="color: #d33; font-weight: bold; vertical-align: middle;"><%= warning.headline %></span> <ref><%= warning.explanation %></ref></span>\
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wikidata_position_history
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tony Bowden
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: exe
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2020-09-
|
|
12
|
+
date: 2020-09-16 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: mediawiki-replaceable-content
|