Merge branch 'master' into develop

This commit is contained in:
Tom Russell 2020-04-09 13:26:36 +01:00 committed by GitHub
commit 97953db742
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 858 additions and 78 deletions

2
.gitignore vendored
View File

@ -18,6 +18,8 @@ etl/**/*.xls
etl/**/*.xlsx
etl/**/*.zip
.DS_Store
# Cache
app/tilecache/**/*.png
app/tilecache/**/*.mbtiles

View File

@ -123,6 +123,40 @@
<PolygonSymbolizer fill="#980043" />
</Rule>
</Style>
<Style name="construction_core_material">
<Rule>
<Filter>[construction_core_material] = "Wood"</Filter>
<PolygonSymbolizer fill="#96613b" />
</Rule>
<Rule>
<Filter>[construction_core_material] = "Stone"</Filter>
<PolygonSymbolizer fill="#ffffe3" />
</Rule>
<Rule>
<Filter>[construction_core_material] = "Brick"</Filter>
<PolygonSymbolizer fill="#f5d96b" />
</Rule>
<Rule>
<Filter>[construction_core_material] = "Steel"</Filter>
<PolygonSymbolizer fill="#beffe8" />
</Rule>
<Rule>
<Filter>[construction_core_material] = "Reinforced Concrete"</Filter>
<PolygonSymbolizer fill="#fca89d" />
</Rule>
<Rule>
<Filter>[construction_core_material] = "Other Metal"</Filter>
<PolygonSymbolizer fill="#5c8970" />
</Rule>
<Rule>
<Filter>[construction_core_material] = "Other Natural Material"</Filter>
<PolygonSymbolizer fill="#b5a859" />
</Rule>
<Rule>
<Filter>[construction_core_material] = "Other Man-Made Material"</Filter>
<PolygonSymbolizer fill="#c48a85" />
</Rule>
</Style>
<Style name="date_year">
<Rule>
<Filter>[date_year] &gt;= 2020</Filter>
@ -317,7 +351,7 @@
</Rule>
<Rule>
<Filter>[current_landuse_order] = "Transport"</Filter>
<PolygonSymbolizer fill="#b3de69" />
<PolygonSymbolizer fill="#bee8ff" />
</Rule>
<Rule>
<Filter>[current_landuse_order] = "Utilities And Infrastructure"</Filter>

View File

@ -7,11 +7,11 @@ import { ApiParamError, ApiUserError } from './errors/api';
import { DatabaseError } from './errors/general';
import buildingsRouter from './routes/buildingsRouter';
import extractsRouter from './routes/extractsRouter';
import leaderboardRouter from './routes/leaderboardRouter';
import usersRouter from './routes/usersRouter';
import { queryLocation } from './services/search';
import { authUser, getNewUserAPIKey, logout } from './services/user';
const server = express.Router();
// parse POSTed json body
@ -20,6 +20,7 @@ server.use(bodyParser.json());
server.use('/buildings', buildingsRouter);
server.use('/users', usersRouter);
server.use('/extracts', extractsRouter);
server.use('/leaderboard', leaderboardRouter);
server.get('/history', editHistoryController.getGlobalEditHistory);
server.get('/autofill', autofillController.getAutofillOptions);

View File

@ -0,0 +1,22 @@
import express from 'express';
import asyncController from "../routes/asyncController";
import * as leadersService from '../services/leaderboard';
const getLeaders = asyncController(async (req: express.Request, res: express.Response) => {
try {
const number_limit = req.query.number_limit;
const time_limit = req.query.time_limit;
const result = await leadersService.getLeaders(number_limit, time_limit);
res.send({
leaders: result
});
} catch(error) {
console.error(error);
res.send({ error: 'Database error' });
}
});
export default{
getLeaders
};

View File

@ -0,0 +1,9 @@
import express from 'express';
import leaderboardController from '../controllers/leaderboardController';
const router = express.Router();
router.get('/leaders', leaderboardController.getLeaders);
export default router;

View File

@ -311,6 +311,9 @@ const BUILDING_FIELD_WHITELIST = new Set([
'size_floor_area_ground',
'size_floor_area_total',
'size_width_frontage',
'construction_core_material',
'construction_secondary_materials',
'construction_roof_covering',
'planning_portal_link',
'planning_in_conservation_area',
'planning_conservation_area_name',

View File

@ -0,0 +1,39 @@
import db from '../../db';
async function getLeaders(number_limit: number, time_limit: number) {
try {
if(time_limit > 0){
return await db.manyOrNone(
`SELECT count(log_id) as number_edits, username
FROM logs, users
WHERE logs.user_id=users.user_id
AND CURRENT_TIMESTAMP::DATE - log_timestamp::DATE <= $1
AND NOT (users.username = 'casa_friendly_robot')
AND NOT (users.username = 'colouringlondon')
GROUP by users.username
ORDER BY number_edits DESC
LIMIT $2`, [time_limit, number_limit]
);
}else{
return await db.manyOrNone(
`SELECT count(log_id) as number_edits, username
FROM logs, users
WHERE logs.user_id=users.user_id
AND NOT (users.username = 'casa_friendly_robot')
AND NOT (users.username = 'colouringlondon')
GROUP by users.username
ORDER BY number_edits DESC
LIMIT $1`, [number_limit]
);
}
} catch(error) {
console.error(error);
return [];
}
}
export {
getLeaders
};

View File

@ -14,6 +14,7 @@ import ContactPage from './pages/contact';
import ContributorAgreementPage from './pages/contributor-agreement';
import DataAccuracyPage from './pages/data-accuracy';
import DataExtracts from './pages/data-extracts';
import LeaderboardPage from './pages/leaderboard';
import OrdnanceSurveyLicencePage from './pages/ordnance-survey-licence';
import OrdnanceSurveyUprnPage from './pages/ordnance-survey-uprn';
import PrivacyPolicyPage from './pages/privacy-policy';
@ -113,6 +114,7 @@ class App extends React.Component<AppProps, AppState> {
<Route exact path="/data-accuracy.html" component={DataAccuracyPage} />
<Route exact path="/data-extracts.html" component={DataExtracts} />
<Route exact path="/contact.html" component={ContactPage} />
<Route exact path="/leaderboard.html" component={LeaderboardPage} />
<Route exact path="/history.html" component={ChangesPage} />
<Route exact path={App.mapAppPaths} render={(props) => (
<MapApp

View File

@ -74,9 +74,8 @@ const BuildingView: React.FunctionComponent<BuildingViewProps> = (props) => {
return <ConstructionContainer
{...props}
title="Construction"
intro="How are buildings built? Coming soon…"
intro="How are buildings built?"
help="https://pages.colouring.london/construction"
inactive={true}
/>;
case 'team':
return <TeamContainer

View File

@ -60,7 +60,7 @@ const Categories: React.FC<CategoriesProps> = (props) => (
desc="Methods & materials"
slug="construction"
help="https://pages.colouring.london/construction"
inactive={true}
inactive={false}
mode={props.mode}
building_id={props.building_id}
/>

View File

@ -1,55 +1,74 @@
import React, { Fragment } from 'react';
import { dataFields } from '../../data_fields';
import DataEntry from '../data-components/data-entry';
import SelectDataEntry from '../data-components/select-data-entry';
import withCopyEdit from '../data-container';
import { CategoryViewProps } from './category-view-props';
const ConstructionMaterialsOptions = [
'Wood',
'Stone',
'Brick',
'Steel',
'Reinforced Concrete',
'Other Metal',
'Other Natural Material',
'Other Man-Made Material'
];
const RoofCoveringOptions = [
'Slate',
'Clay Tile',
'Wood',
'Asphalt',
'Iron or Steel',
'Other Metal',
'Other Natural Material',
'Other Man-Made Material'
];
/**
* Construction view/edit section
*/
const ConstructionView = (props) => (
<Fragment>
<p className="data-intro">{props.intro}</p>
<ul>
<li>Construction system</li>
{
// "disabled": true,
// "slug": "construction_system",
// "type": "text"
}
<li>Primary materials</li>
{
// "disabled": true,
// "slug": "construction_primary_material",
// "type": "text"
}
<li>Secondary materials</li>
{
// "disabled": true,
// "slug": "construction_secondary_material",
// "type": "text"
}
<li>Roofing material</li>
{
// "disabled": true,
// "slug": "construction_roofing_material",
// "type": "text"
}
<li>Percentage of facade glazed</li>
{
// "disabled": true,
// "slug": "construction_facade_percentage_glazed",
// "type": "number",
// "step": 5
}
<li>BIM reference or link</li>
{
// "disabled": true,
// "slug": "construction_bim_reference",
// "type": "text",
// "placeholder": "https://..."
}
</ul>
</Fragment>
);
const ConstructionView: React.FunctionComponent<CategoryViewProps> = (props) => {
return (
<Fragment>
<SelectDataEntry
title={dataFields.construction_core_material.title}
slug="construction_core_material"
value={props.building.construction_core_material} // check
tooltip={dataFields.construction_core_material.tooltip}
options={ConstructionMaterialsOptions}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
<SelectDataEntry
title={dataFields.construction_secondary_materials.title}
slug="construction_secondary_materials"
value={props.building.construction_secondary_materials} // check
tooltip={dataFields.construction_secondary_materials.tooltip}
options={ConstructionMaterialsOptions}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
<SelectDataEntry
title={dataFields.construction_roof_covering.title}
slug="construction_roof_covering"
value={props.building.construction_roof_covering} // check
tooltip={dataFields.construction_roof_covering.tooltip}
options={RoofCoveringOptions}
mode={props.mode}
copy={props.copy}
onChange={props.onChange}
/>
</Fragment>
);
};
const ConstructionContainer = withCopyEdit(ConstructionView);
export default ConstructionContainer;

View File

@ -280,6 +280,24 @@ export const dataFields = {
//tooltip: ,
},
construction_core_material: {
category: Category.Construction,
title: "Core Material",
tooltip:"The main structural material",
},
construction_secondary_materials: {
category: Category.Construction,
title: "Secondary Construction Material/s",
tooltip:"Other construction materials",
},
construction_roof_covering: {
category: Category.Construction,
title: "Main Roof Covering",
tooltip:'Main roof covering material',
},
sust_breeam_rating: {
category: Category.Sustainability,
title: "BREEAM Rating",

View File

@ -89,6 +89,11 @@ class Header extends React.Component<HeaderProps, HeaderState> {
Downloads
</NavLink>
</li>
<li className="nav-item">
<NavLink to="/leaderboard.html" className="nav-link" onClick={this.handleNavigate}>
Leaderboard
</NavLink>
</li>
<li className="nav-item">
<NavLink to="/contact.html" className="nav-link" onClick={this.handleNavigate}>
Contact

View File

@ -75,7 +75,16 @@ const LEGEND_CONFIG = {
},
construction: {
title: 'Construction',
elements: []
elements: [
{ color: "#96613b", text: "Wood" },
{ color: "#ffffe3", text: "Stone" },
{ color: "#f5d96b", text: "Brick" },
{ color: "#beffe8", text: "Steel" },
{ color: "#fca89d", text: "Reinforced Concrete" },
{ color: "#5c8970", text: "Other Metal" },
{ color: "#b5a859", text: "Other Natural Material" },
{ color: "#c48a85", text: "Other Man-Made Material" }
]
},
team: {
title: 'Team',

View File

@ -131,6 +131,7 @@ class ColouringMap extends Component<ColouringMapProps, ColouringMapState> {
const tilesetByCat = {
age: 'date_year',
size: 'size_height',
construction: 'construction_core_material',
location: 'location',
like: 'likes',
planning: 'planning_combined',

View File

@ -0,0 +1,34 @@
table {
table-layout: fixed;
width: 60%;
margin-left: 20%;
margin-right: 20%;
border: 1px solid black;
}
table th, td {
border: 1px solid black;
text-align: left;
padding-left: 1%;
}
table tr:nth-child(odd) {
background: #f6f8fa;
}
table tr:nth-child(1) {
background: #fff;
}
#title {
text-align: center;
padding-bottom: 1%;
}
#radiogroup {
padding: 1%;
}
input[type="radio"] {
margin: 0 2px 0 10px;
}

View File

@ -0,0 +1,149 @@
import React, { Component } from 'react';
import './leaderboard.css';
interface Leader {
number_edits: string;
username: string;
}
interface LeaderboardProps {
}
interface LeaderboardState {
leaders: Leader[];
fetching: boolean;
//We need to track the state of the radio buttons to ensure their current state is shown correctly when the view is (re)rendered
number_limit: number;
time_limit: number;
}
class LeaderboardPage extends Component<LeaderboardProps, LeaderboardState> {
constructor(props) {
super(props);
this.state = {
leaders: [],
fetching: false,
number_limit: 10,
time_limit: -1
};
this.getLeaders = this.getLeaders.bind(this);
this.renderTableData = this.renderTableData.bind(this);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
if(e.target.name == 'number_limit'){
this.getLeaders(e.target.value, this.state.time_limit);
this.setState({number_limit: e.target.value});
}else {
this.getLeaders(this.state.number_limit, e.target.value);
this.setState({time_limit: e.target.value});
}
}
componentDidMount() {
this.getLeaders(this.state.number_limit, this.state.time_limit);
}
componentWillUnmount() {}
getLeaders(number_limit, time_limit) {
this.setState({
fetching: true
});
fetch(
'/api/leaderboard/leaders?number_limit=' + number_limit + '&time_limit='+time_limit
).then(
(res) => res.json()
).then((data) => {
if (data && data.leaders){
this.setState({
leaders: data.leaders,
fetching: false
});
} else {
console.error(data);
this.setState({
leaders: [],
fetching: false
});
}
}).catch((err) => {
console.error(err);
this.setState({
leaders: [],
fetching: false
});
});
}
renderTableData() {
return this.state.leaders.map((u, i) => {
const username = u.username;
const number_edits = u.number_edits;
return (
<tr key={username}>
<td>{i+1}</td>
<td>{username}</td>
<td>{number_edits}</td>
</tr>
);
});
}
render() {
return(
<div>
<form id="radiogroup">
<div id="number-radiogroup" >
<p>Select number of users to be displayed: <br/>
<input type="radio" name="number_limit" value="10" onChange={this.handleChange} checked={10 == this.state.number_limit} />10
<input type="radio" name="number_limit" value="100" onChange={this.handleChange} checked={100 == this.state.number_limit} />100
</p>
</div>
<div id="time-radiogroup" >
<p>Select time period: <br/>
<input type="radio" name="time_limit" value="-1" onChange={this.handleChange} checked={-1 == this.state.time_limit} /> All time
<input type="radio" name="time_limit" value="7" onChange={this.handleChange} checked={7 == this.state.time_limit} /> Last 7 days
<input type="radio" name="time_limit" value="30" onChange={this.handleChange} checked={30 == this.state.time_limit} /> Last 30 days
</p>
</div>
</form>
<h1 id='title'>Leader Board</h1>
<table id='leaderboard'>
<tbody>
<tr>
<th>Rank</th>
<th>Username</th>
<th>Contributions</th>
</tr>
{this.renderTableData()}
</tbody>
</table>
</div>
);
}
}
export default LeaderboardPage;

View File

@ -55,6 +55,15 @@ const BUILDING_LAYER_DEFINITIONS = {
buildings as b
WHERE g.geometry_id = b.geometry_id
) as size_height`,
construction_core_material: `(
SELECT
b.core_materials,
g.geometry_geom
FROM
geometries as g,
buildings as b
WHERE g.geometry_id = b.geometry_id
) as core_materials`,
location: `(
SELECT
(

View File

@ -1,35 +1,52 @@
## Adding new building attribute fields
# Adding new building attribute fields
This document is a checklist for adding a new building attribute to the system. It's split into two sections - actions that apply when adding any field, and additional steps to add a field that will be visualised on the map.
This document is a checklist for adding a new building attribute to the system. It's split into three sections - actions that apply when adding any field, and additional steps to add a field that will be visualised on the map.
The second section would be required when adding a new category or when changing which field should be visualised for a category.
The third section would appply to any data which can be ammended via the API.
When adding a new attribute a set of seed data should be identified, the base data set formany fields is Polly Hudsons PhD data set. This data set is required to;
- Test visualisation elements (map styling)
- Provide some data for users to relate to and encourage them to fill in the field
- Test the API and database elements.
### Adding any attribute
#### In database
## Adding any attribute
### In database
1. Add a column to the `buildings` table in the database.
2. Add any check constraints or foreign key constraints on the column, if necessary (if the foreign key constraint is used to restrict the column to a set of values, the table with the values might need to be created from scratch)
3. If a source is being collected for field. Add a column `fieldName_source` to the `sources` table.
4. If verfication is being enabled. Add a column `bieldName_verifications` to the `verfication` table.
#### In API
### In API
1. Add field name to `BUILDING_FIELD_WHITELIST` in the building service to allow saving changes to the field
2. Add any special domain logic for processing updates to the field in the `processBuildingUpdate()` function
#### In frontend
### In frontend
1. Add the field description to the `dataFields` object in `data_fields.ts`
2. Add the data entry React component of the appropriate type (text, numeric etc) to the appropriate category view component in the `building/data-containers` folder. Link to `dataFields` for static string values (field name, description etc)
#### In data extracts
### In data extracts
1. Add the field to the select list in the COPY query in `maintenance/extract_data/export_attributes.sql`
2. Add a description of the field to the `README.txt` file
### Adding an attribute which is used to colour the map
## Adding an attribute which is used to colour the map
All steps from the previous section need to be carried out first.
#### In tileserver
1. Add a SQL query for calculating the value to be visualised to `BUILDING_LAYER_DEFINITIONS` in `app/tiles/dataDefinition.ts`
### In tileserver
1. Add a SQL query for calculating the value to be visualised to `BUILDING_LAYER_DEFINITIONS` in `app/src/tiles/dataDefinition.ts`
2. Add Mapnik rendering style in `app/map_styles/polygon.xml`
#### In frontend
### In frontend
1. Update the category to field name mapping in the `tilesetByCat` object inside the `ColouringMap` React component (`map.tsx` file)
2. Add an entry for the field to the `LEGEND_CONFIG` object in `legend.tsx` file
2. Add an entry for the field to the `LEGEND_CONFIG` object in `legend.tsx` file
## Testing
Run tests on staging to confirm;
- Database changes accepted
- API is working and data is getting posted to the database
- Map styling is applied and style is appropriate way to visualise the data

View File

@ -60,9 +60,14 @@ def main(base_url, api_key, source_file, json_columns):
if building_id is None:
continue
if 'sust_dec' in line and line['sust_dec'] == '':
del line['sust_dec']
response_code, response_data = update_building(building_id, line, api_key, base_url)
if response_code != 200:
print('ERROR', building_id, response_code, response_data)
else:
print('DEBUG', building_id, response_code, response_data)
def update_building(building_id, data, api_key, base_url):
@ -82,6 +87,7 @@ def find_building(data, base_url):
if building_id is not None:
print("match_by_building_id", building_id)
return building_id
if 'toid' in data:
building_id = find_by_reference(base_url, 'toid', data['toid'])
if building_id is not None:

View File

@ -61,6 +61,11 @@ def update_building(building_id, data, api_key, base_url):
def find_building(data, base_url):
if 'building_id' in data:
building_id = data['building_id']
if building_id is not None:
print("match_by_building_id", building_id)
return building_id
if 'toid' in data:
building_id = find_by_reference(base_url, 'toid', data['toid'])
if building_id is not None:

View File

@ -0,0 +1,140 @@
"""Join shapefile data to buildings
This is effectively an example script using the HTTP API, tailored to particular collected
datasets for Camden (age data) and Fitzrovia (number of storeys).
- read through shapes
- locate building by toid
- else locate building by representative point
- update building with data
"""
import json
import os
import sys
from functools import partial
import fiona
import pyproj
import requests
from shapely.geometry import shape
from shapely.ops import transform
osgb_to_ll = partial(
pyproj.transform,
pyproj.Proj(init='epsg:27700'),
pyproj.Proj(init='epsg:4326')
)
def main(base_url, api_key, process, source_file):
"""Read from file, update buildings
"""
with fiona.open(source_file, 'r') as source:
for feature in source:
props = feature['properties']
if process == "camden":
toid, data = process_camden(props)
else:
toid, data = process_fitzrovia(props)
if data is None:
continue
building_id = find_building(toid, feature['geometry'], base_url)
if not building_id:
print("no_match", toid, "-")
continue
save_data(building_id, data, api_key, base_url)
def process_camden(props):
toid = osgb_toid(props['TOID'])
data = {
'date_year': props['Year_C'],
'date_source_detail': props['Date_sou_1']
}
return toid, data
def process_fitzrovia(props):
toid = osgb_toid(props['TOID'])
storeys = props['Storeys']
if storeys is None:
return toid, None
if props['Basement'] == 'Yes':
data = {
'size_storeys_core': int(storeys) - 1,
'size_storeys_basement': 1
}
else:
data = {
'size_storeys_core': int(storeys),
'size_storeys_basement': 0
}
return toid, data
def osgb_toid(toid):
if toid is None:
toid = ""
return "osgb" + toid.lstrip("0")
def save_data(building_id, data, api_key, base_url):
"""Save data to a building
"""
r = requests.post(
"{}/buildings/{}.json?api_key={}".format(base_url, building_id, api_key),
json=data
)
def find_building(toid, geom, base_url):
"""Find building_id by TOID or location
"""
r = requests.get(base_url + "/buildings/reference", params={
'key': 'toid',
'id': toid
})
buildings = r.json()
if buildings and len(buildings) == 1:
bid = buildings[0]['building_id']
print("match_by_toid", toid, bid)
return bid
# try location
poly = shape(geom)
point_osgb = poly.centroid
if not poly.contains(point_osgb):
point_osgb = poly.representative_point()
point_ll = transform(osgb_to_ll, point_osgb)
r = requests.get(base_url + "/buildings/locate", params={
'lng': point_ll.x,
'lat': point_ll.y
})
buildings = r.json()
if buildings and len(buildings) == 1:
bid = buildings[0]['building_id']
print("match_by_location", toid, bid)
return bid
return None
if __name__ == '__main__':
try:
url, api_key, process, filename = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
except IndexError:
print(
"Usage: {} <URL> <api_key> <camden|fitzrovia> ./path/to/camden.shp".format(
os.path.basename(__file__)
))
exit()
main(url, api_key, process, filename)

View File

@ -63,6 +63,9 @@ This is the main table, containing almost all data collected by Colouring London
- `size_floor_area_ground`: ground floor floor area in square metres
- `size_floor_area_total`: total floor area in square metres
- `size_width_frontage`: width of frontage in metres
- `construction_core_material`: main structural material
- `construction_secondary_materials`: other structural materials
- `construction_roof_covering`: main roof covering
- `sust_breeam_rating`: BREEAM rating
- `sust_dec`: DEC rating
- `sust_retrofit_date`: year of last significant retrofit

View File

@ -36,6 +36,9 @@ COPY (SELECT
sust_breeam_rating,
sust_dec,
sust_retrofit_date,
construction_core_material,
construction_secondary_materials,
construction_roof_covering,
planning_portal_link,
planning_in_conservation_area,
planning_conservation_area_name,

View File

@ -0,0 +1,35 @@
-- Remove sustainability fields, update in paralell with adding new fields
-- Last significant retrofit date YYYY
-- Need to add a constraint to sust_retrofit_date
-- Renewal technologies
-- Constraint - Front end multi select back end ENUM
-- Values:
ALTER TABLE buildings DROP COLUMN IF EXISTS sust_renewables_tech;
--Has a building had a major renovation without extenstion (captured in form)
--Boolean yes/no - links to the the DATE
ALTER TABLE buildings DROP COLUMN IF EXISTS sust_retrofitted;
-- Generating capacity of those renewables, on selection of one of the above generate correspondening front end input for this. Pair values
-- Constraint more than 0 less than?, integer only
ALTER TABLE buildings DROP COLUMN IF EXISTS sust_renewables_capax;
-- Biodiversity
-- Green roof, green wall, both
-- Constrain drop down and enum
ALTER TABLE buildings DROP COLUMN IF EXISTS sust_biodiversity;
-- Insulation, tool tip for glazing in construction to cross link
-- Which components are insulated
-- Cosntraint multi-entry and ENUM stored in josnb object
-- Values; Wall, Roof, FLOOR
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_insulation;
-- Water recycling
-- yes / no
ALTER TABLE buildings DROP COLUMN IF EXISTS sust_h2o_recyling;
-- Rain water harvesting
-- Does building store it's rainwater, helps combat flood risk
-- yes / no
ALTER TABLE buildings DROP COLUMN IF EXISTS sust_rainwater_harvest;

View File

@ -0,0 +1,66 @@
-- Remove sustainability fields, update in paralell with adding new fields
-- Last significant retrofit date YYYY
-- Need to add a constraint to sust_retrofit_date
ALTER TABLE buildings
ADD CONSTRAINT sust_retrofit_date_end CHECK (sust_retrofit_date <= DATE_PART('year', CURRENT_DATE));
--Has a building had a major renovation without extenstion (captured in form)
--Boolean yes/no - links to the the DATE
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS sust_retrofitted boolean DEFAULT 'n';
-- Renewal technologies
-- Constraint - Front end multi select back end ENUM
-- Values: Solar PV, Solar thermal, Wind, Ground sourced heat pump, Air sourced heat pump,
CREATE TYPE sust_renewables_tech
AS ENUM ('Solar photovoltaic',
'Solar thermal',
'Wind',
'Ground source heat pump',
'Air-source heat pump',
'Water source heat pump',
'Anaerobic digester');
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS sust_renewables_tech sust_renewables_tech DEFAULT NULL;
-- Generating capacity of those renewables, on selection of one of the above generate correspondening front end input for this. Pair values
-- Constraint more than 0 less than 9999
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS sust_renewables_capax int CONSTRAINT high_renewables_capx CHECK (sust_renewables_capax >= 0);
-- Biodiversity
-- Green roof, green wall, both
-- Constrain drop down and enum
CREATE TYPE sust_biodiversity
AS ENUM ('Green roof',
'Green wall',
'Green wall & roof',
'Anaerobic digester');
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS sust_biodiversity sust_biodiversity DEFAULT NULL;
-- Insulation, tool tip for glazing in construction to cross link
-- Which components are insulated
-- Cosntraint multi-entry and ENUM stored in josnb object
-- Values; Wall, Roof, FLOOR
CREATE TYPE constrctn_insulation
AS ENUM ('Cavity wall',
'External wall',
'Roof',
'Floor');
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS constrctn_insulation constrctn_insulation DEFAULT NULL;
-- Water recycling
-- yes / no
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS sust_h2o_recyling boolean DEFAULT 'n';
-- Rain water harvesting
-- Does building store it's rainwater, helps combat flood risk
-- yes / no
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS sust_rainwater_harvest boolean DEFAULT 'n';

View File

@ -0,0 +1,13 @@
-- Remove community fields
-- Ownership type, enumerate type from:
ALTER TABLE buildings DROP COLUMN IF EXISTS ownership_type;
-- Ownerhsip perception, would you describe this as a community asset?
-- Boolean yes / no
ALTER TABLE buildings DROP COLUMN IF EXISTS ownership_perception;
-- Historic ownership type / perception
-- Has this building ever been used for community or public services activities?
-- Boolean yes / no
ALTER TABLE buildings DROP COLUMN IF EXISTS ownership_historic;

View File

@ -0,0 +1,28 @@
-- Remove community fields
-- Ownership type, enumerate type from:
--
CREATE TYPE ownership_type
AS ENUM ('Private individual',
'Private company',
'Private offshore ownership',
'Publicly owned',
'Institutionally owned');
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS ownership_type ownership_type DEFAULT 'Private individual';
-- Ownerhsip perception, would you describe this as a community asset?
-- Boolean yes / no
-- Below accepts t/f, yes/no, y/n, 0/1 as valid inputs all of which
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS ownership_perception boolean DEFAULT null;
-- Historic ownership type / perception
-- Has this building ever been used for community or public services activities?
-- Boolean yes / no
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS ownership_historic boolean DEFAULT null;

View File

@ -13,18 +13,3 @@ ALTER TABLE buildings DROP COLUMN IF EXISTS current_landuse_group;
-- Land use order, singular. Client and db constrained.
ALTER TABLE buildings DROP COLUMN IF EXISTS current_landuse_order;
--===========================================
--
-- We also collect original landuse, structure & process is as current land use
-- We don't currently collect intermediate historic uses
--
--===========================================
-- Original Land use class or classes, array object, client constrained.
ALTER TABLE buildings DROP COLUMN IF EXISTS original_landuse_class;
-- Land use order, singular. Client and db constrained.
ALTER TABLE buildings DROP COLUMN IF EXISTS original_landuse_order;

View File

@ -0,0 +1,8 @@
ALTER TABLE buildings DROP COLUMN IF EXISTS core_materials;
ALTER TABLE buildings DROP COLUMN IF EXISTS secondary_materials;
ALTER TABLE buildings DROP COLUMN IF EXISTS roof_covering;
DROP TYPE IF EXISTS construction_materials;
DROP TYPE IF EXISTS roof_covering;

View File

@ -0,0 +1,38 @@
-- Launch of Constuction category
-- Fields: Core construction material, Secondary construction materials, Roof covering materials
-- Construction materials: Wood, Stone, Brick, Steel, Reinforced Concrete, Other Metal
-- Other Natural Material, Other Man-Made Material
CREATE TYPE construction_materials
AS ENUM ('Wood',
'Stone',
'Brick',
'Steel',
'Reinforced Concrete',
'Other Metal',
'Other Natural Material',
'Other Man-Made Material');
-- Roof covering materials: Slate, Clay Tile, Wood, Asphalt, Iron or Steel, Other Metal
-- Other Natural Material, Other Man-Made Material
CREATE TYPE roof_covering
AS ENUM ('Slate',
'Clay Tile',
'Wood',
'Asphalt',
'Iron or Steel',
'Other Metal',
'Other Natural Material',
'Other Man-Made Material');
-- Core Construction Material
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS core_materials construction_materials;
-- Secondary Construction Materials
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS secondary_materials construction_materials;
-- Main Roof Covering
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS roof_covering roof_covering;

View File

@ -0,0 +1,23 @@
--NOTE Some construction category fields (insulation and glazing) are migrated in sustainability.up.1-extra see #405
--Primary material (brick/clay tile, slate, steel/metal, timber, stone, glass, concrete, natural-green or thatch, asphalt or other)
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_primary_mat;
--Secondary material
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_secondary_mat;
--Primary roof material
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_primary_roof_mat;
--Secondary roof material
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_secondary_roof_mat;
--Has building been extended y/n
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_extension;
--What kind of extensions (side, rear, loft, basement)
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_extension_type;
--Glazing type, what is most common glazing type. (unless I add into sustainability for now)
ALTER TABLE buildings DROP COLUMN IF EXISTS constrctn_glazing_type;
--Also pick up roof in this for

View File

@ -0,0 +1,3 @@
-- Remove team fields, update in paralell with adding new fields
-- Award or awards (may be multiple) stored as json b object
ALTER TABLE buildings DROP COLUMN IF EXISTS team_awards;

View File

@ -0,0 +1,8 @@
--Storing as json b, create column defined as jsonb
--This contains the award name and award year
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS team_awards jsonb;
--To validate this input, the following confirms it's an valid object but not that the items in the object are validated agains those we will acccept
ALTER TABLE buildings
ADD CONSTRAINT data_is_valid CHECK (is_jsonb_valid ('{"type": "object"}', team_awards));

View File

@ -0,0 +1,16 @@
-- Building original use, collecting both class and order
ALTER TABLE buildings DROP COLUMN IF EXISTS original_landuse_class;
-- [Disabled for launch] Date of change of use
-- This needs to pair with demolition
ALTER TABLE buildings DROP COLUMN IF EXISTS date_change_building_use;
-- Original Land use class or classes, array object, client constrained.
ALTER TABLE buildings DROP COLUMN IF EXISTS original_landuse_class;
-- Land use order, singular. Client and db constrained.
ALTER TABLE buildings DROP COLUMN IF EXISTS original_landuse_order;
-- Land use order, singular. Client and db constrained.
ALTER TABLE buildings DROP COLUMN IF EXISTS original_landuse_source;

View File

@ -0,0 +1,14 @@
-- Land use order, singular. Client and db constrained with foreign key
ALTER TABLE buildings
ADD COLUMN IF NOT EXISTS original_landuse_order text,
ADD FOREIGN KEY (current_landuse_order)
REFERENCES reference_tables.buildings_landuse_order (description);
-- Land use groups, array. Derived from classes.
ALTER TABLE buildings ADD COLUMN IF NOT EXISTS original_landuse_group text ARRAY[41];
-- Land use class or classes, array object, client constrained. ARRAY[] is used to constrain array size. The array is limited to 250 based on Westfield Stratford as a single toid with many uses, this may want to be reduced down to reduce maximum size.
ALTER TABLE buildings ADD COLUMN IF NOT EXISTS original_landuse_class text ARRAY[250];
--Landuse source

View File

@ -53,3 +53,17 @@ Set or update passwords:
```bash
psql -c "ALTER USER appusername WITH PASSWORD 'longsecurerandompassword';"
```
## File naming syntax
Initial up and down migrations as `###.name.up.sql` file number should be sequential
and incremental to last migrations file number is same for up/down.
If adjusting a prior migration syntax is:
###.name.up.sql
Syntax for adding to existing migration:
0##.filename-extension-#.up.sql