Election results in the London Borough of Sutton.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

models.rb 8.8 KiB

13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
11 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
11 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. require 'data_mapper'
  2. class Poll
  3. include DataMapper::Resource
  4. property :district_id, Integer, :key => true
  5. property :election_id, Integer, :key => true
  6. property :electorate, Integer # The number of people eligible to vote in this district in this election
  7. property :ballot_papers_issued, Integer # The number of ballot papers issued (includes spoiled ballots)
  8. property :seats, Integer, :required => true # The number of seats to be elected in this district in this election
  9. property :rejected_no_official_mark, Integer
  10. property :rejected_too_many_candidates, Integer
  11. property :rejected_identifiable_voter, Integer
  12. property :rejected_blank_or_uncertain, Integer
  13. def turnout_percent
  14. @ballot_papers_issued.to_f / @electorate.to_f * 100.0
  15. end
  16. def total_rejected_ballots
  17. if @rejected_no_official_mark
  18. @rejected_no_official_mark + \
  19. @rejected_too_many_candidates + \
  20. @rejected_identifiable_voter + \
  21. @rejected_blank_or_uncertain
  22. else
  23. nil
  24. end
  25. end
  26. def valid_ballot_papers
  27. self.total_rejected_ballots ? @ballot_papers_issued - self.total_rejected_ballots : nil
  28. end
  29. def successful_candidacies # Candidacies where the candidate was elected
  30. Candidacy.all(:election => @election, :district => @district, :order => [:position], :limit => @seats)
  31. end
  32. # Set candidacy.position for every candidacy in this poll
  33. # Returns array of candidacies, or false if we don't have results for this poll
  34. def set_positions
  35. # Check that every candidacy for this poll has its votes recorded (ie that the election results are known)
  36. if Candidacy.count(:conditions => { :district_id => @district_id, :election_id => @election_id, :votes => nil }) == 0
  37. return false
  38. end
  39. # Get the candidacies for this poll
  40. ccys = Candidacy.all(:conditions => { :district_id => @district_id, :election_id => @election_id }, :order => [:votes.desc])
  41. puts "Found no candidacies for this poll" if ccys.size == 0
  42. position = 1
  43. ccys.each do |ccy|
  44. position <= @seats ? ccy.seats = 1 : ccy.seats = 0
  45. ccy.position = position
  46. ccy.save
  47. position += 1
  48. end
  49. ccys
  50. end
  51. belongs_to :election
  52. belongs_to :district
  53. end
  54. class PollingStation
  55. include DataMapper::Resource
  56. property :id, String, :key => true, :length => 2 # e.g. "KA"
  57. property :name, String, :length => 255#, :required => true
  58. property :address, String, :length => 255#, :required => true
  59. property :postcode, String#, :required => true
  60. property :easting, Float, :required => true
  61. property :northing, Float, :required => true
  62. property :lat, Float, :required => true
  63. property :lng, Float, :required => true
  64. has n, :postcodes
  65. end
  66. class Postcode
  67. include DataMapper::Resource
  68. # Postcode natural key, uppercase with space, eg. "SM1 1EA"
  69. # Column names derived from Ordnance Survey CodePoint Open
  70. property :postcode, String, :key => true
  71. property :positional_quality_indicator, Integer
  72. property :eastings, Integer, :required => true
  73. property :northings, Integer, :required => true
  74. property :country_code, String, :required => true
  75. property :nhs_regional_ha_code, String, :required => true
  76. property :nhs_ha_code, String, :required => true
  77. property :admin_county_code, String # NULL within Greater London
  78. property :admin_district_code, String, :required => true # e.g. London Borough of Sutton
  79. property :admin_ward_code, String, :required => true # e.g. Sutton Central
  80. property :lat, Float, :required => true
  81. property :lng, Float, :required => true
  82. property :ward_id, Integer, :required => true # Sutton Council
  83. property :constituency_id, Integer, :required => false # UK Parliament
  84. property :polling_station_id, String, :length => 2
  85. belongs_to :district, :child_key => [:ward_id]
  86. belongs_to :polling_station
  87. def self.finder(postcode)
  88. postcode = postcode.strip.upcase
  89. if o = self.get(postcode)
  90. return o
  91. end
  92. result = Pat.get(postcode)
  93. unless result.code == 404
  94. # cache API result
  95. self.create(
  96. :postcode => postcode,
  97. :lat => result['geo']['lat'],
  98. :lng => result['geo']['lng'],
  99. :district_name => result['administrative']['district']['title'],
  100. :district_code => result['administrative']['district']['uri'].match(/.+\/(.+)$/)[1],
  101. :ward_name => result['administrative']['ward']['title'],
  102. :ward_code => result['administrative']['ward']['uri'].match(/.+\/(.+)$/)[1]
  103. )
  104. else
  105. # invalid postcode
  106. nil
  107. end
  108. end
  109. end
  110. class Candidate
  111. include DataMapper::Resource
  112. property :id, Serial
  113. property :forenames, String, :required => true
  114. property :surname, String, :required => true, :index => true
  115. has n, :candidacies
  116. def short_name
  117. @forenames.split(' ')[0] + ' ' + @surname
  118. end
  119. def name
  120. @forenames + ' ' + @surname
  121. end
  122. def url
  123. "/candidates/" + @id.to_s
  124. end
  125. end
  126. class DeletedCandidate
  127. include DataMapper::Resource
  128. property :old_candidate_id, Integer, :key => true # ID of candidate that has been merged/deleted
  129. property :candidate_id, Integer, :required => true # ID of candidate that has been kept
  130. end
  131. class Candidacy
  132. include DataMapper::Resource
  133. property :id, Serial
  134. property :election_id, Integer, :required => true
  135. property :candidate_id, Integer, :required => true
  136. property :party_id, Integer
  137. property :district_id, Integer, :required => true
  138. property :votes, Integer
  139. property :address, String, :length => 200
  140. property :postcode, String
  141. property :position, Integer # Position of this candidate in this district. (1..n)
  142. property :seats, Integer # Number of seats won by this candidacy (0 or 1)
  143. property :labcoop, Boolean, :default => false # Candidacy is for joint Labour/Co-op party
  144. belongs_to :election
  145. belongs_to :candidate
  146. belongs_to :party
  147. belongs_to :district
  148. end
  149. class Campaign
  150. include DataMapper::Resource
  151. property :party_id, Integer, :key => true
  152. property :election_id, Integer, :key => true
  153. property :party_url, String, :length => 255
  154. property :manifesto_html_url, String, :length => 255
  155. property :manifesto_pdf_url, String, :length => 255
  156. belongs_to :party
  157. belongs_to :election
  158. end
  159. class Election
  160. include DataMapper::Resource
  161. property :id, Serial
  162. property :body_id, Integer, :required => true
  163. property :d, Date, :required => true, :index => true
  164. property :reason, String, :length => 255
  165. property :kind, String, :length => 255
  166. has n, :candidacies
  167. has n, :polls
  168. belongs_to :body
  169. has n, :campaigns
  170. def self.past
  171. self.all(:d.lt => Time.now.to_s, :order => [ :d.desc ])
  172. end
  173. def self.future
  174. self.all(:d.gte => Time.now.to_s, :order => [ :d.desc ])
  175. end
  176. # electorate and ballot_papers_issued assume there's a Poll object for every district in this election
  177. def electorate
  178. Poll.sum(:electorate, :election => self)
  179. end
  180. def ballot_papers_issued
  181. Poll.sum(:ballot_papers_issued, :election => self)
  182. end
  183. def set_positions
  184. polls.each { |p| p.set_positions }
  185. end
  186. end
  187. class District
  188. include DataMapper::Resource
  189. property :id, Serial
  190. property :body_id, Integer, :required => true
  191. property :name, String, :length => 255, :required => true
  192. property :slug, String
  193. property :ons_district_code, String
  194. belongs_to :body
  195. has n, :postcodes, :child_key => [:ward_id]
  196. has n, :polls
  197. def self.slugify(name)
  198. name.gsub(/[^\w\s-]/, '').gsub(/\s+/, '-').downcase
  199. end
  200. end
  201. class Body
  202. include DataMapper::Resource
  203. property :id, Serial
  204. property :name, String, :length => 255, :required => true
  205. property :district_name, String, :length => 255, :required => true # singular
  206. property :districts_name, String, :length => 255, :required => true # plural
  207. property :slug, String, :length => 255
  208. has n, :elections
  209. has n, :districts
  210. end
  211. class Party
  212. include DataMapper::Resource
  213. property :id, Serial
  214. property :name, String, :required => true
  215. property :colour, String
  216. has n, :candidacies
  217. has n, :campaigns
  218. end
  219. DataMapper.setup(:default, ENV['DATABASE_URL'] || "postgres://postgres@localhost:5432/suttonelections")
  220. DataMapper.auto_upgrade!