Browse Source

Initial commit

pull/3/head
Adrian Short 14 years ago
commit
9776c93ff7
25 changed files with 993 additions and 0 deletions
  1. +3
    -0
      .gitignore
  2. +0
    -0
      README
  3. +111
    -0
      app.rb
  4. +45
    -0
      import2009Q4.rb
  5. +39
    -0
      import2010Q1.rb
  6. +58
    -0
      lib/models.rb
  7. BIN
      public/breadcrumb/bc_bg.gif
  8. BIN
      public/breadcrumb/bc_separator.gif
  9. +42
    -0
      public/breadcrumb/breadcrumb.css
  10. BIN
      public/breadcrumb/home.gif
  11. +0
    -0
      public/favicon.ico
  12. +338
    -0
      public/grid.css
  13. BIN
      public/od_80x15_blue.png
  14. +9
    -0
      public/print.css
  15. +121
    -0
      public/style.css
  16. +55
    -0
      views/about.haml
  17. +30
    -0
      views/directorate.haml
  18. +5
    -0
      views/error.haml
  19. +11
    -0
      views/home.haml
  20. +26
    -0
      views/layout.haml
  21. +7
    -0
      views/not_found.haml
  22. +33
    -0
      views/service.haml
  23. +13
    -0
      views/services.haml
  24. +34
    -0
      views/supplier.haml
  25. +13
    -0
      views/suppliers.haml

+ 3
- 0
.gitignore View File

@@ -0,0 +1,3 @@
data/
*sqlite3


+ 0
- 0
README View File


+ 111
- 0
app.rb View File

@@ -0,0 +1,111 @@
require 'rubygems'
require 'sinatra'
require 'sinatra-helpers/haml/partials'
require 'haml'
require 'lib/models'


get '/' do
@directorates = Directorate.all
# @results = repository(:default).adapter.query("
# SELECT p.name,
# sum(c.votes_2010) AS votes,
# p.colour
#
# FROM parties p,
# councilcandidates c
#
# WHERE p.id = c.party_id
#
# GROUP BY p.name, p.colour
#
# ORDER BY votes desc
# ;")

# select p.name, count(c.*) AS seats
# FROM parties p, councilcandidates c
# GROUP BY p.id
haml :home
end

get '/directorates/:id' do
@directorate = Directorate.get(params[:id])
haml :directorate
end

get '/suppliers/:id.csv' do
@supplier = Supplier.get(params[:id])

headers "Content-Disposition" => "attachment;filename=supplier#{@supplier.id}.csv",
"Content-Type" => "application/octet-stream"

result = "Date,Trans No,Directorate,Service,Amount ex. VAT\n"

for payment in @supplier.payments
result += "#{payment.d.strftime("%d %b %Y")},#{payment.trans_no},\"#{payment.directorate.name}\",#{payment.service.name},#{sprintf("%0.2f", payment.amount)}\n"
end

result
end

get '/suppliers/:id' do
@supplier = Supplier.get(params[:id])
haml :supplier
end

get '/suppliers/?' do
@suppliers = Supplier.all( :order => ['name'] )
haml :suppliers
end

get '/services/:id.csv' do
@service = Service.get(params[:id])

headers "Content-Disposition" => "attachment;filename=service#{@service.id}.csv",
"Content-Type" => "application/octet-stream"

result = "Date,Trans No,Directorate,Supplier,Amount ex. VAT\n"

for payment in @service.payments
result += "#{payment.d.strftime("%d %b %Y")},#{payment.trans_no},\"#{payment.directorate.name}\",#{payment.supplier.name},#{sprintf("%0.2f", payment.amount)}\n"
end

result
end

get '/services/:id' do
@service = Service.get(params[:id])
haml :service
end

get '/services/?' do
@services = Service.all( :order => ['name'] )
haml :services
end

get '/wards/:slug/postcode/:postcode/?' do
@ward = Ward.first(:slug => params[:slug])
@postcode = params[:postcode]
haml :wards
end

get '/wards/:slug/?' do
@ward = Ward.first(:slug => params[:slug])
haml :wards
end

get '/error' do
haml :error
end

get '/about' do
haml :about
end

not_found do
haml :not_found
end

+ 45
- 0
import2009Q4.rb View File

@@ -0,0 +1,45 @@
require 'lib/models'
require 'csv'

count = 0

CSV::Reader.parse(File.open('data/2009Q4.csv', 'rb')) do |row|
# 2009Q4 Columns:
# 0: Directorate
# 1: Updated
# 2: TransNo
# 3: Service
# 4: Cost Centre
# 5: Supplier Name
# 6: Amount excl vat
# 7: Type

count += 1
if (count > 4) # skip first four lines that don't contain data
p row
directorate = Directorate.first_or_create(:name => row[0].strip)
service = Service.first_or_create(:name => row[3].strip)
supplier = Supplier.first_or_create(:name => row[5].strip)
payment = Payment.first_or_create(
'trans_no' => row[2],
'directorate' => directorate,
'service' => service,
'supplier' => supplier,
'cost_centre' => row[4].strip,
'amount' => row[6].strip.gsub(/,/, ''),
'd' => row[1],
'tyype' => row[7].strip
)
unless payment.save
puts "ERROR: Failed to save payment"
payment.errors.each do |e|
puts e
end
end
end
end

+ 39
- 0
import2010Q1.rb View File

@@ -0,0 +1,39 @@
require 'lib/models'
require 'csv'

count = 0

# 2010Q1: 0-Directorate,1-Updated,2-Service,3-Supplier Name,4-Amount excl vat £,5-Type

CSV::Reader.parse(File.open('data/2010Q1.csv', 'rb')) do |row|


count += 1
if (count > 4) # skip first four lines that don't contain data
p row
directorate = Directorate.first_or_create(:name => row[0].strip)
service = Service.first_or_create(:name => row[2].strip)
supplier = Supplier.first_or_create(:name => row[3].strip)
dt = row[1].strip.split('/')
payment = Payment.first_or_create(
'directorate' => directorate,
'service' => service,
'supplier' => supplier,
'amount' => row[4].strip.gsub(/,/, ''),
'd' => Date.new(dt[2].to_i, dt[1].to_i, dt[0].to_i),
'tyype' => row[5].strip
)
unless payment.save
puts "ERROR: Failed to save payment"
payment.errors.each do |e|
puts e
end
end
end
end

+ 58
- 0
lib/models.rb View File

@@ -0,0 +1,58 @@
require 'rubygems'
require 'dm-core'
require 'dm-validations'
require 'dm-timestamps'
require 'dm-aggregates'

class Payment
include DataMapper::Resource
property :id, Serial
property :trans_no, Integer, :required => false # "TransNo" in RBWM CSV files
property :directorate_id, Integer, :required => true
property :service_id, Integer, :required => true
property :supplier_id, Integer, :required => true
property :cost_centre, String, :required => false
property :amount, BigDecimal, :precision => 10, :scale => 2, :required => true # ex VAT
property :d, Date, :required => true # "Updated" in RBWM CSV files
property :tyype, String, :required => true # Capital or Revenue
belongs_to :directorate
belongs_to :service
belongs_to :supplier
end


class Directorate
include DataMapper::Resource
property :id, Serial
property :name, String, :length => 255, :required => true
has n, :payments, :order => ['d']
end

class Service
include DataMapper::Resource
property :id, Serial
property :name, String, :length => 255, :required => true
has n, :payments, :order => ['d']
end

class Supplier
include DataMapper::Resource
property :id, Serial
property :name, String, :length => 255, :required => true
has n, :payments, :order => ['d']
# def self.slugify(name)
# name.gsub(/[^\w\s-]/, '').gsub(/\s+/, '-').downcase
# end
end

DataMapper.setup(:default, ENV['DATABASE_URL'] || "sqlite3://#{Dir.pwd}/db.sqlite3")
DataMapper.auto_upgrade!

BIN
public/breadcrumb/bc_bg.gif View File

Before After
Width: 1  |  Height: 30  |  Size: 155 B

BIN
public/breadcrumb/bc_separator.gif View File

Before After
Width: 8  |  Height: 30  |  Size: 546 B

+ 42
- 0
public/breadcrumb/breadcrumb.css View File

@@ -0,0 +1,42 @@

#breadcrumb {
font: 11px Arial, Helvetica, sans-serif;
background-image:url('/breadcrumb/bc_bg.gif');
background-repeat:repeat-x;
height:30px;
line-height:30px;
color:#888;
border:solid 1px #cacaca;
width:100%;
overflow:hidden;
margin:0px;
padding:0px;
}

#breadcrumb li {
list-style-type:none;
padding-left:10px;
display:inline-block;
float:left;
}

#breadcrumb a {
display:inline-block;
background-image:url('/breadcrumb/bc_separator.gif');
background-repeat:no-repeat;
background-position:right;
padding-right: 15px;
text-decoration: none;
color:#333333;
outline:none;
}

.home {
border:none;
margin: 7px 0px;
background-image:url('/breadcrumb/bc_separator.gif');
}

#breadcrumb a:hover {
color:#35acc5;
}

BIN
public/breadcrumb/home.gif View File

Before After
Width: 16  |  Height: 16  |  Size: 588 B

+ 0
- 0
public/favicon.ico View File


+ 338
- 0
public/grid.css View File

@@ -0,0 +1,338 @@
/*
Variable Grid System.
Learn more ~ http://www.spry-soft.com/grids/
Based on 960 Grid System - http://960.gs/

Licensed under GPL and MIT.
*/


/* Containers
----------------------------------------------------------------------------------------------------*/
.container_12 {
margin-left: auto;
margin-right: auto;
width: 960px;
}

/* Grid >> Global
----------------------------------------------------------------------------------------------------*/

.grid_1,
.grid_2,
.grid_3,
.grid_4,
.grid_5,
.grid_6,
.grid_7,
.grid_8,
.grid_9,
.grid_10,
.grid_11,
.grid_12 {
display:inline;
float: left;
position: relative;
margin-left: 15px;
margin-right: 15px;
}

/* Grid >> Children (Alpha ~ First, Omega ~ Last)
----------------------------------------------------------------------------------------------------*/

.alpha {
margin-left: 0;
}

.omega {
margin-right: 0;
}

/* Grid >> 12 Columns
----------------------------------------------------------------------------------------------------*/

.container_12 .grid_1 {
width:50px;
}

.container_12 .grid_2 {
width:130px;
}

.container_12 .grid_3 {
width:210px;
}

.container_12 .grid_4 {
width:290px;
}

.container_12 .grid_5 {
width:370px;
}

.container_12 .grid_6 {
width:450px;
}

.container_12 .grid_7 {
width:530px;
}

.container_12 .grid_8 {
width:610px;
}

.container_12 .grid_9 {
width:690px;
}

.container_12 .grid_10 {
width:770px;
}

.container_12 .grid_11 {
width:850px;
}

.container_12 .grid_12 {
width:930px;
}



/* Prefix Extra Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/

.container_12 .prefix_1 {
padding-left:80px;
}

.container_12 .prefix_2 {
padding-left:160px;
}

.container_12 .prefix_3 {
padding-left:240px;
}

.container_12 .prefix_4 {
padding-left:320px;
}

.container_12 .prefix_5 {
padding-left:400px;
}

.container_12 .prefix_6 {
padding-left:480px;
}

.container_12 .prefix_7 {
padding-left:560px;
}

.container_12 .prefix_8 {
padding-left:640px;
}

.container_12 .prefix_9 {
padding-left:720px;
}

.container_12 .prefix_10 {
padding-left:800px;
}

.container_12 .prefix_11 {
padding-left:880px;
}



/* Suffix Extra Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/

.container_12 .suffix_1 {
padding-right:80px;
}

.container_12 .suffix_2 {
padding-right:160px;
}

.container_12 .suffix_3 {
padding-right:240px;
}

.container_12 .suffix_4 {
padding-right:320px;
}

.container_12 .suffix_5 {
padding-right:400px;
}

.container_12 .suffix_6 {
padding-right:480px;
}

.container_12 .suffix_7 {
padding-right:560px;
}

.container_12 .suffix_8 {
padding-right:640px;
}

.container_12 .suffix_9 {
padding-right:720px;
}

.container_12 .suffix_10 {
padding-right:800px;
}

.container_12 .suffix_11 {
padding-right:880px;
}



/* Push Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/

.container_12 .push_1 {
left:80px;
}

.container_12 .push_2 {
left:160px;
}

.container_12 .push_3 {
left:240px;
}

.container_12 .push_4 {
left:320px;
}

.container_12 .push_5 {
left:400px;
}

.container_12 .push_6 {
left:480px;
}

.container_12 .push_7 {
left:560px;
}

.container_12 .push_8 {
left:640px;
}

.container_12 .push_9 {
left:720px;
}

.container_12 .push_10 {
left:800px;
}

.container_12 .push_11 {
left:880px;
}



/* Pull Space >> 12 Columns
----------------------------------------------------------------------------------------------------*/

.container_12 .pull_1 {
left:-80px;
}

.container_12 .pull_2 {
left:-160px;
}

.container_12 .pull_3 {
left:-240px;
}

.container_12 .pull_4 {
left:-320px;
}

.container_12 .pull_5 {
left:-400px;
}

.container_12 .pull_6 {
left:-480px;
}

.container_12 .pull_7 {
left:-560px;
}

.container_12 .pull_8 {
left:-640px;
}

.container_12 .pull_9 {
left:-720px;
}

.container_12 .pull_10 {
left:-800px;
}

.container_12 .pull_11 {
left:-880px;
}




/* Clear Floated Elements
----------------------------------------------------------------------------------------------------*/

/* http://sonspring.com/journal/clearing-floats */

.clear {
clear: both;
display: block;
overflow: hidden;
visibility: hidden;
width: 0;
height: 0;
}

/* http://perishablepress.com/press/2008/02/05/lessons-learned-concerning-the-clearfix-css-hack */

.clearfix:after {
clear: both;
content: ' ';
display: block;
font-size: 0;
line-height: 0;
visibility: hidden;
width: 0;
height: 0;
}

.clearfix {
display: inline-block;
}

* html .clearfix {
height: 1%;
}

.clearfix {
display: block;
}

BIN
public/od_80x15_blue.png View File

Before After
Width: 80  |  Height: 15  |  Size: 446 B

+ 9
- 0
public/print.css View File

@@ -0,0 +1,9 @@
.noprint, #footer, #breadcrumb
{
display: none;
}

body
{
margin: 0 auto;
}

+ 121
- 0
public/style.css View File

@@ -0,0 +1,121 @@
body
{
background-color: #fff;
color: #555;
font-family: Helvetica, Arial, sans-serif;
font-size: 100%;
line-height: 1.5em;
}

p
{
font-size: 110%;
}

input
{
font-size: 130%;
background-color: #fff;
}



#main
{
margin: 30px 0;
}

#footer
{
font-size: 100%;
background-color: #fff;
text-align: left;
margin: 40px 0 40px 0;
}

a
{
background-color: #dce9b0;
padding: 1px 4px;
color: #111;
text-decoration: none;
}

a:visited
{
background-color: #eee;
padding: 1px 4px;
color: #111;
text-decoration: none;
}

a:hover
{
background-color: #4f4f4f;
color: #fff;
}

h1
{
margin-top: 20px;
line-height: 1.4em;
font-weight: bold;
color: #86a11d;
}

h2
{
margin-top: 20px;
line-height: 1.5em;
font-weight: bold;
color: #86a11d;
}

form
{
font-size: 150%;
}



.highlight
{
background-color: #fff7c0;
padding: 5px;
}

strong
{
color: #000;
}

table
{
border-collapse: collapse;
}

td, th
{
padding: 6px;
}

th
{
text-align: left;
}

tr
{
border-bottom: 1px solid #eee;
}

.right
{
text-align: right;
}


.noborder
{
border: 0;
}

+ 55
- 0
views/about.haml View File

@@ -0,0 +1,55 @@
.grid_9
%h1= @page_title = "About this website"
%blockquote
The swift and simple changes we are calling for today [to encourage councils to publish spending data] will unleash an army of armchair auditors and quite rightly make those charged with doling out the pennies stop and think twice about whether they are getting value for money.
%p.right — Eric Pickles, Communities & Local Government Secretary, 5 June 2010
<!-- http://news.bbc.co.uk/1/hi/politics/10241522.stm -->
%p.vcard
This website was designed and written by
%a.fn.url{ :href => 'http://adrianshort.co.uk' } Adrian Short
\.
You can contact me by email at
%a.email{ :href => "mailto:adrian.short@gmail.com" } adrian.short@gmail.com
and
%a.url{ :href => "http://twitter.com/adrianshort" } follow me on Twitter
\.
%p.highlight
This site is made with
%a{ :href => 'http://www.rbwm.gov.uk/web/finance_payments_to_suppliers.htm' }
the Royal Borough of Windsor and Maidenhead's spending data
and is entirely indepdendent of the council. The council did not commission or pay for this website.
%p Every page on this website prints beautifully.
%p
This site is written in
%a{ :href => "http://www.ruby-lang.org/en/" }Ruby
\ using the
%a{ :href => "http://www.sinatrarb.com/" }Sinatra framework.
%p
The code for this website is
%a{ :href => "http://github.com/adrianshort/Sutton-Elections" }open source and managed on Github.
It is hosted by
%a{ :href => "http://heroku.com/" }Heroku.
%p
The page templates use
%a{ :href => "http://haml-lang.com/" }Haml
and SprySoft's
%a{ :href => "http://www.spry-soft.com/grids/" }Variable Grid System
\. The database is
%a{ :href => "http://www.sqlite.org/" }SQLite
for development and
%a{ :href => "http://www.postgresql.org/" }PostgreSQL
for production, abstracted through
%a{ :href => "http://datamapper.org/" }DataMapper.
%p
Source control and deployment is done with
%a{ :href => "http://git-scm.com/" }Git.


+ 30
- 0
views/directorate.haml View File

@@ -0,0 +1,30 @@
.grid_12
%h2= @page_title = @directorate.name + " Directorate"
%ul#breadcrumb
%li.home
%a{ :href => '/'} Home
%li
%a{ :href => '/directorates' } Directorates
%li
= @directorate.name
%table
%tr
%th Date
%th Service
%th Supplier
%th &pound;
- for payment in @directorate.payments
%tr
%td= payment.d.strftime("%d&nbsp;%b&nbsp;%Y")
%td
%a{ :href => '/services/' + payment.service.id.to_s }
= payment.service.name
%td
%a{ :href => '/suppliers/' + payment.supplier.id.to_s }
= payment.supplier.name
%td.right= sprintf("%0d", payment.amount)

+ 5
- 0
views/error.haml View File

@@ -0,0 +1,5 @@
.grid_12
%h1 Invalid postcode
%p
%a{ :href => "/" }Please go back and try again

+ 11
- 0
views/home.haml View File

@@ -0,0 +1,11 @@
.grid_12

%ul#breadcrumb
%li.home
%h2 Directorates
- for directorate in @directorates
%p
%a{ :href=> "/directorates/#{directorate.id}" }
= directorate.name

+ 26
- 0
views/layout.haml View File

@@ -0,0 +1,26 @@
!!! XML
!!!
%html
%head
%title= @page_title ? @page_title + " - Armchair Auditor" : "Armchair Auditor"
%link{ :rel => 'stylesheet', :type => 'text/css', :href => '/style.css' }
%link{ :rel => 'stylesheet', :type => 'text/css', :href => '/breadcrumb/breadcrumb.css' }
%link{ :rel => 'stylesheet', :type => 'text/css', :href => '/print.css', :media => 'print' }
%link{ :rel => 'stylesheet', :type => 'text/css', :href => '/grid.css' }

%body
.container_12
.grid_12
%h1 Royal Borough of Windsor &amp; Maidenhead Armchair Auditor
= yield
.clear
#footer
.grid_12
%p
%a{ :href => '/' } Home
%p
%a{ :href => '/services' } Services
%p
%a{ :href => '/suppliers' } Suppliers
%p
%a{ :href => '/about' } About this website

+ 7
- 0
views/not_found.haml View File

@@ -0,0 +1,7 @@
.grid_9
%h1 Not Found
%p Oops, we can't find that page.
%p
%a{ :href => '/' }Go back to the home page

+ 33
- 0
views/service.haml View File

@@ -0,0 +1,33 @@
.grid_12
%h2= @page_title = @service.name + " (Service)"
%ul#breadcrumb
%li.home
%a{ :href => '/'} Home
%li
%a{ :href => '/services' } Services
%li
= @service.name
%p
%a{ :href => "/services/#{@service.id}.csv" }
Download data as CSV
%table
%tr
%th Date
%th Directorate
%th Supplier
%th &pound;
- for payment in @service.payments
%tr
%td= payment.d.strftime("%d&nbsp;%b&nbsp;%Y")
%td
%a{ :href => '/directorates/' + payment.directorate.id.to_s }
= payment.directorate.name
%td
%a{ :href => '/suppliers/' + payment.supplier.id.to_s }
= payment.supplier.name
%td.right= sprintf("%0d", payment.amount)

+ 13
- 0
views/services.haml View File

@@ -0,0 +1,13 @@
.grid_12
%h2 Services
%ul#breadcrumb
%li.home
%a{ :href => '/'} Home
%li
Services
- for service in @services
%p
%a{ :href=> "/services/#{service.id}" }
= service.name

+ 34
- 0
views/supplier.haml View File

@@ -0,0 +1,34 @@
.grid_12
%h2= @page_title = @supplier.name + " (Supplier)"

%ul#breadcrumb
%li.home
%a{ :href => '/'} Home
%li
%a{ :href => '/suppliers' } Suppliers
%li
= @supplier.name

%p.noprint
%a{ :href => "/suppliers/#{@supplier.id}.csv" }
Download data as CSV
%table
%tr
%th Date
%th Directorate
%th Service
%th &pound;
- for payment in @supplier.payments
%tr
%td= payment.d.strftime("%d&nbsp;%b&nbsp;%Y")
%td
%a{ :href => '/directorates/' + payment.directorate.id.to_s }
= payment.directorate.name
%td
%a{ :href => '/services/' + payment.service.id.to_s }
= payment.service.name
%td.right= sprintf("%0d", payment.amount)

+ 13
- 0
views/suppliers.haml View File

@@ -0,0 +1,13 @@
.grid_12
%h2 Suppliers
%ul#breadcrumb
%li.home
%a{ :href => '/'} Home
%li
Suppliers
- for supplier in @suppliers
%p
%a{ :href=> "/suppliers/#{supplier.id}" }
= supplier.name

Loading…
Cancel
Save