wikidata_position_history 1.11.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e59634d8233371767e5a151e60d8bb5f3c8f5703302654c59f5db407faea4a4
4
- data.tar.gz: 763b5794f41ad395b045fedfb7b132fc6139698e3514df0caf96a47bd5d224d2
3
+ metadata.gz: 52e5490ed1d01167632b8adc9cb5d9a37912a5accad858b5d2ada5aaa43522ff
4
+ data.tar.gz: ab39a9aed506e9c77834ed7a7fdc6a9a06006eaee84f14ee9fb0c71eb069c5a8
5
5
  SHA512:
6
- metadata.gz: 8ad33ca550ad0a5a83ffd42bc03ce8de8df93f77b932ec8ae74d21c528160e8b47d55ff8569641d34801e7f181f0b3b9c856e2d27f5aef91914d73522e5e63de
7
- data.tar.gz: 7dba23307f13045710a4bc3b7c0869e024fcf206b023e9740c3c8c5366ea7aa260a1dcc342cf7c0875ed1b635c2bc3b96e350720e32c01e35051b1bc1d37e747
6
+ metadata.gz: e7aae1fb45f43c0ba961d878de171e4150194514dc406e0059a8c9bc2544ce2e9549d4e9e07c5da6b7ccfe8a6aec534735c1f645fb026a5eb04512b4cee7f9cb
7
+ data.tar.gz: 797ab543a4a0ee636ed833a1f200c266ebed7b60872084b6f9b0156b11920dc619c15d4bc19b8326ca747c9f5585df4a81be7b23c5fbdd07a177e3ded1f3f702
@@ -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
@@ -8,4 +8,4 @@ if ARGV.size != 1
8
8
  e.g. #{$PROGRAM_NAME} Q14211'"
9
9
  end
10
10
 
11
- puts WikidataPositionHistory::Report.new(ARGV.first).wikitext_with_header
11
+ puts WikidataPositionHistory::Report.new(ARGV.first).wikitext
@@ -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] * 3
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
- def successor
20
+ # TODO: replace these with objects instead of strings
21
+ def successor_qlink
21
22
  current.next
22
23
  end
23
24
 
24
- def predecessor
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.item} is missing #{missing.map { |field| "{{P|#{field_map[field]}}}" }.join(', ')}"
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.item == current.item # sucessive terms by same person
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.item == current.item # sucessive terms by same person
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? && !!predecessor && (earlier.item != predecessor)
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.item} has a {{P|1365}} of #{predecessor}, but follows #{earlier.item} here"
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
- predecessor && !earlier_holder?
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.item} has a {{P|1365}} of #{predecessor}, but does not follow anyone here"
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? && !!successor && (later.item != successor)
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.item} has a {{P|1366}} of #{successor}, but is followed by #{later.item} here"
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
- successor && !later_holder?
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.item} has a {{P|1366}} of #{successor}, but is not followed by anyone here"
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
- "#{current.item} has a {{P|582}} of #{current.end_date}, which #{overlap_explanation} the {{P|580}} of #{later.start_date} for #{later.item}"
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
@@ -22,8 +22,8 @@ module WikidataPositionHistory
22
22
  "#{ordinal}."
23
23
  end
24
24
 
25
- def person
26
- current.item
25
+ def officeholder
26
+ current.officeholder
27
27
  end
28
28
 
29
29
  def dates
@@ -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 ||= SPARQL::BioQuery.new(position_id).results_as(BioRow)
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 ||= SPARQL::MandatesQuery.new(position_id)
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.person %></span> <%= mandate.dates %>
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=]]&nbsp;<span style="color: #d33; font-weight: bold; vertical-align: middle;"><%= warning.headline %></span>&nbsp;<ref><%= warning.explanation %></ref></span>\
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WikidataPositionHistory
4
- VERSION = '1.11.0'
4
+ VERSION = '2.0.0'
5
5
  end
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: 1.11.0
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-14 00:00:00.000000000 Z
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