Recorded votes · weighted by authority

Who decides here?

Pick a point on the map and say how you would have voted on the measures below. The page works out which elected bodies decide each issue for that address, how much of their authority voted your way, and which upcoming elections put that authority on the ballot. Every figure comes from a recorded vote.

Where you live

Click the map to move your address. Dashed lines are parliamentary constituencies.

How would you have voted?

Each measure is one recorded vote. Skip any you have no view on.

Starting SurrealDB in your browser…

Your issues

Elections to watch

Ranked by how much authority over your issues is held by people who voted against you and is on that ballot.

Your representatives

The people elected for your address and how they voted on your measures. Large councils are shown by group.

The SurrealQL this page just ran

				
How it is built

What SurrealDB does here

The whole model is one schema file of about 300 lines, most of them comments. Each card shows one feature it relies on, with the lines that use it.

Relations are tables

Each link in the chain of authority is a record of its own: a place lies within another, a body acts_for a place and appoints members of another body, and an official holds a seat. A relation table names the tables it connects, so SurrealDB refuses a link between the wrong kinds of record.

DEFINE TABLE acts_for TYPE RELATION IN body OUT place SCHEMAFULL;
DEFINE TABLE appoints TYPE RELATION IN body OUT body SCHEMAFULL;

Relations carry data

An edge holds fields like any other record. decides holds the weight a body has over an issue and the reason for that weight, and voted holds the position, so the evidence for each figure is one record away from it.

RELATE body:smmag->decides->issue:public_transport CONTENT {
	weight: 0.35,
	basis: 'Runs public transport around Grenoble, ...',
};

Recursive graph walks

A district lies in a city, which lies in a metropolitan area, a region, a country and the EU. One expression follows within to any depth and collects every place on the way, so the dataset sets the number of levels and the query does not change.

$p.{..+collect}(->within->place)

Geometry in the same query

Outlines are GeoJSON polygons stored on each place. Finding the places that contain the address is one WHERE clause, and its result goes directly into the graph walk.

SELECT VALUE id FROM place
	WHERE area != NONE AND $at INSIDE area;

One relation, two kinds of voter

Some councils record each member's vote, and others only each group's. voted accepts an official or a group as its source, and fn::vote uses the group's vote when a member has none of their own. A literal type limits the position to three values.

DEFINE TABLE voted TYPE RELATION IN official | faction OUT measure SCHEMAFULL;
DEFINE FIELD position ON voted TYPE 'for' | 'against' | 'abstain';

Closures and record links

Array methods take closures, and a record link reads like a field: $p.measure.issue follows a position to its measure and then to the issue. The weighting is a few lines of map, filter and math::sum, with no joins.

LET $known = $members.filter(|$m| $m.alignment != NONE);
LET $agree = math::sum($known.map(|$m| $m.weight * $m.alignment));

SELECT over any array

SELECT takes an array as its source, including one the function built a line earlier. The group tallies for a large council are a GROUP BY over the votes just collected.

SELECT party, they, count() AS members FROM $cast
	GROUP BY party, they ORDER BY party, they

The logic is in the database

fn::agreement, fn::elections and fn::representatives are defined in the schema with typed parameters. This page and the command-line scripts call the same functions and get the same figures.

DEFINE FUNCTION fn::agreement(
	$at: geometry<point>, $positions: array<object>
) -> array<object> { ... };

The same engine in the browser

This page runs SurrealDB compiled to WebAssembly, in memory, and loads the schema file unchanged. Every query on this page runs in your browser, and the page has no server of its own.

const db = new Surreal({ engines: createWasmEngines() });
await db.connect("mem://");

A new city is new data

Paris and Grenoble are two data files over one schema, and the schema names no country, city or issue. A new city, issue or body is a set of new records, and the schema and functions stay as they are. The French version of this page is one example: a file of UPDATE statements on the text fields, loaded into a second database.

CREATE issue:public_transport CONTENT { ... };
RELATE body:smmag->decides->issue:public_transport CONTENT { ... };
The whole schema (schema.surql)
Loading…