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 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