Election results in the London Borough of Sutton.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

293 lignes
7.1 KiB

  1. #!/usr/bin/env ruby
  2. # Generate a static site
  3. require 'logger'
  4. require 'haml'
  5. require_relative '../models'
  6. OUTPUT_DIR = '_site'
  7. VIEWS_DIR = File.join('..', 'views')
  8. LAYOUT_FN = File.join(VIEWS_DIR, 'layout.haml')
  9. @log = Logger.new($stdout)
  10. @log.level = Logger::INFO
  11. @log.info "Build starts."
  12. @log.info "Output directory is: #{OUTPUT_DIR}"
  13. class String
  14. def pluralize(num)
  15. if num == 1
  16. return self
  17. end
  18. case self[-1]
  19. when 'y'
  20. self[0..-2] + 'ies'
  21. when 's'
  22. self + "es"
  23. else
  24. self + "s"
  25. end
  26. end
  27. end
  28. def commify(num)
  29. num.to_s.reverse.gsub(/(\d\d\d)(?=\d)(?!\d*\.)/,'\1,').reverse
  30. end
  31. # From http://snippets.dzone.com/posts/show/593
  32. def to_ordinal(num)
  33. num = num.to_i
  34. if (10...20) === num
  35. "#{num}th"
  36. else
  37. g = %w{ th st nd rd th th th th th th }
  38. a = num.to_s
  39. c = a[-1..-1].to_i
  40. a + g[c]
  41. end
  42. end
  43. def format_percent(num)
  44. sprintf("%.0f%%", num)
  45. end
  46. def short_date(d)
  47. d.strftime("%e %b %Y")
  48. end
  49. def long_date(d)
  50. d.strftime("%e %B %Y")
  51. end
  52. # Exception for Labour/Co-operative candidacies
  53. def party_name(labcoop, party_name)
  54. labcoop ? "Labour and Co-operative Party" : party_name
  55. end
  56. def write_page(path_items, template, locals = {})
  57. dir = File.join(path_items)
  58. FileUtils.mkdir_p(dir)
  59. @log.debug dir
  60. fn = File.join(dir, 'index.html')
  61. # https://stackoverflow.com/questions/6125265/using-layouts-in-haml-files-independently-of-rails
  62. html = Haml::Engine.new(File.read(LAYOUT_FN)).render do
  63. Haml::Engine.new(File.read(File.join(VIEWS_DIR, "#{template}.haml"))).render(Object.new, locals)
  64. end
  65. File.write(fn, html)
  66. @log.info fn
  67. # TODO - add page to sitemap.xml or sitemap.txt
  68. # https://support.google.com/webmasters/answer/183668?hl=en&ref_topic=4581190
  69. end
  70. test_dir = File.join(Dir.pwd, OUTPUT_DIR)
  71. # FIXME - clear output directory at the start of every run
  72. # FileUtils.rm_rf(test_dir)
  73. Dir.mkdir(test_dir) unless File.directory?(test_dir)
  74. Dir.chdir(test_dir)
  75. # Copy `public` dir to output dir
  76. FileUtils.copy_entry(File.join('..', 'public'), '.')
  77. # Home page
  78. locals = {
  79. future_elections: Election.future,
  80. past_elections: Election.past
  81. }
  82. write_page('.', 'index', locals)
  83. # Election pages
  84. Election.each do |e|
  85. locals = {
  86. body: Body.first(:slug => e.body.slug),
  87. election: Election.first(:body => e.body, :d => e.d),
  88. elections_for_this_body: Election.all(:body => e.body, :order => [:d]),
  89. total_seats: Candidacy.sum(:seats, :election => e),
  90. total_votes: Candidacy.sum(:votes, :election => e)
  91. }
  92. # There's got to be a better way to do this, either with SQL or Datamapper
  93. locals['total_districts'] = repository(:default).adapter.select("
  94. SELECT district_id
  95. FROM candidacies
  96. WHERE election_id = ?
  97. GROUP BY district_id
  98. ORDER BY district_id
  99. ", e.id).count
  100. locals['results_by_party'] = repository(:default).adapter.select("
  101. SELECT
  102. p.colour,
  103. p.name,
  104. SUM(c.votes) AS votez,
  105. SUM(c.seats) AS seatz,
  106. COUNT(*) AS cands
  107. FROM candidacies c
  108. LEFT JOIN parties p ON p.id = c.party_id
  109. WHERE c.election_id = ?
  110. GROUP BY c.party_id, p.colour, p.name
  111. ORDER BY seatz DESC, votez DESC
  112. ", e.id)
  113. write_page(['bodies', e.body.slug, 'elections', e.d.to_s], 'electionsummary', locals)
  114. # District results for this election (resultsdistrict)
  115. # Loop through all districts in this election
  116. e.candidacies.districts.each do |d|
  117. total_seats = Candidacy.sum(:seats, :district => d, :election => e)
  118. total_votes = Candidacy.sum(:votes, :district => d, :election => e)
  119. poll = Poll.get(d.id, e.id)
  120. locals = {
  121. district: d,
  122. body: d.body,
  123. election: e,
  124. candidacies: Candidacy.all(:district => d, :election => e, :order => [:position]),
  125. total_votes: total_votes,
  126. total_candidates: Candidacy.count(:district => d, :election => e),
  127. total_seats: total_seats,
  128. districts_in_this_election: e.candidacies.districts,
  129. poll: poll
  130. }
  131. locals['share_message'] = nil
  132. if total_seats == 1
  133. locals['share_denominator'] = total_votes
  134. elsif poll && poll.valid_ballot_papers
  135. locals['share_denominator'] = poll.valid_ballot_papers
  136. else
  137. locals['share_denominator'] = total_votes / total_seats
  138. locals['share_message'] = "The vote share percentages have been estimated as we don't have data for the number of valid ballot papers in this poll."
  139. end
  140. # Postgres: All the columns selected when using GROUP BY must either be aggregate functions or appear in the GROUP BY clause
  141. locals['results_by_party'] = repository(:default).adapter.select("
  142. SELECT
  143. p.name AS party_name,
  144. p.colour AS party_colour,
  145. COUNT(c.id) AS num_candidates,
  146. SUM(c.seats) AS num_seats,
  147. SUM(c.votes) AS total_votes
  148. FROM candidacies c
  149. LEFT JOIN parties p
  150. ON c.party_id = p.id
  151. WHERE c.district_id = ?
  152. AND c.election_id = ?
  153. GROUP BY p.name, p.colour
  154. ORDER BY total_votes DESC
  155. ", d.id, e.id)
  156. write_page(['bodies', e.body.slug, 'elections', e.d.to_s, e.body.districts_name, d.slug], 'resultsdistrict', locals)
  157. end
  158. end
  159. # Candidate index
  160. locals = { candidates: Candidate.all(:order => [ :surname, :forenames ]) }
  161. write_page('candidates', 'candidates', locals)
  162. # Candidate pages
  163. # FIXME: What do we do about deleted candidates/redirects?
  164. Candidate.each do |c|
  165. locals = {
  166. candidate: c
  167. }
  168. locals['candidacies'] = repository(:default).adapter.select("
  169. SELECT
  170. e.d,
  171. c.*,
  172. p.name AS party_name,
  173. p.colour AS party_colour,
  174. b.name AS body_name,
  175. b.slug AS body_slug,
  176. b.districts_name AS districts_name,
  177. d.name AS district_name,
  178. d.slug AS district_slug
  179. FROM candidacies c
  180. INNER JOIN elections e
  181. ON c.election_id = e.id
  182. INNER JOIN parties p
  183. ON c.party_id = p.id
  184. INNER JOIN bodies b
  185. ON e.body_id = b.id
  186. INNER JOIN districts d
  187. ON c.district_id = d.id
  188. WHERE c.candidate_id = ?
  189. ORDER BY d
  190. ", c.id)
  191. write_page(['candidates', c.id.to_s], 'candidate', locals)
  192. end
  193. # Bodies index
  194. dir = 'bodies'
  195. FileUtils.mkdir_p(dir)
  196. @log.debug dir
  197. fn = File.join(dir, 'index.html')
  198. FileUtils.touch(fn) # empty file
  199. @log.info fn
  200. # Body detail pages
  201. Body.each do |b|
  202. locals = {
  203. body: b,
  204. districts: District.all(:body => b, :order => [:name])
  205. }
  206. locals['elections'] = repository(:default).adapter.select("
  207. SELECT
  208. e.id,
  209. e.kind,
  210. e.d,
  211. SUM(p.ballot_papers_issued)::float / SUM(p.electorate) * 100 AS turnout_percent
  212. FROM elections e
  213. LEFT JOIN polls p
  214. ON e.id = p.election_id
  215. WHERE e.body_id = ?
  216. GROUP BY p.election_id, e.id
  217. ORDER BY e.d DESC
  218. ", b.id)
  219. write_page(['bodies', b.slug], 'body', locals)
  220. # Districts for this body
  221. b.districts.each do |d|
  222. locals = {
  223. district: d,
  224. body: b
  225. }
  226. write_page(['bodies', b.slug, b.districts_name, d.slug], 'district', locals)
  227. end
  228. end
  229. write_page('about', 'about')
  230. write_page('guides', 'guides')
  231. write_page(%w(guides how-the-parliament-election-works), 'parliament')
  232. write_page(%w(guides how-the-council-election-works), 'election')
  233. @log.info "Build complete."