diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d33a35..908f976 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,3 +80,49 @@ jobs: - name: Build run: npm run build + + lint-rust: + name: Lint Rust + runs-on: ubuntu-latest + defaults: + run: + working-directory: server-rs + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + + - name: Cache cargo + uses: Swatinem/rust-cache@v2 + with: + workspaces: server-rs + + - name: Run clippy + run: cargo clippy -- -D warnings + + - name: Check formatting + run: cargo fmt --check + + test-rust: + name: Test Rust + runs-on: ubuntu-latest + needs: [lint-rust] + defaults: + run: + working-directory: server-rs + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo + uses: Swatinem/rust-cache@v2 + with: + workspaces: server-rs + + - name: Run tests + run: cargo test diff --git a/.vscode/settings.json b/.vscode/settings.json index 3c9d529..702822d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,8 +3,10 @@ "*.venv": true, "**/__pycache__": true, "**/node_modules": true, - "**/.ruff_cache":true, - "**/.pytest_cache":true, - "**/target":true + "**/.ruff_cache": true, + "**/.pytest_cache": true, + "**/target": true, + "frontend/dist": true, + "**/.task": true } -} +} \ No newline at end of file diff --git a/Taskfile.yml b/Taskfile.yml index 677d189..1e55c2d 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -42,7 +42,7 @@ tasks: dir: og-screenshot env: CACHE_DIR: /tmp/og-cache - NARROWIT_URL: http://localhost:3030 + NARROWIT_URL: http://localhost:3000 cmds: - npm install - npx playwright install --with-deps chromium @@ -50,7 +50,7 @@ tasks: - npm start dev:frontend: - desc: Run frontend dev server on port 3030 (proxies /api to :8001) + desc: Run frontend dev server on port 3000 (proxies /api to :8001) dir: frontend deps: - install diff --git a/frontend/src/lib/consts.ts b/frontend/src/lib/consts.ts index 36b35af..6e5fd89 100644 --- a/frontend/src/lib/consts.ts +++ b/frontend/src/lib/consts.ts @@ -35,8 +35,9 @@ export const POSTCODE_ZOOM_THRESHOLD = 15; export const ZOOM_TO_RESOLUTION_THRESHOLDS = [ { maxZoom: 7.5, resolution: 5 }, { maxZoom: 9.5, resolution: 6 }, - { maxZoom: 10.5, resolution: 8 }, - { maxZoom: 12, resolution: 9 }, + { maxZoom: 10.5, resolution: 7 }, + { maxZoom: 11.5, resolution: 8 }, + { maxZoom: 13, resolution: 9 }, { maxZoom: Infinity, resolution: 10 }, ] as const; @@ -76,33 +77,66 @@ export const TWEMOJI_BASE = 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/ export const OSM_ATTRIBUTION = '© OpenStreetMap'; // ============================================================================= -// Crime Category Breakdowns +// Stacked Chart Groups // ============================================================================= -/** Component crimes that make up each aggregate crime metric */ -export const CRIME_BREAKDOWNS: Record = { - 'Serious crime (avg/yr)': [ - 'Violence and sexual offences (avg/yr)', - 'Robbery (avg/yr)', - 'Burglary (avg/yr)', - 'Possession of weapons (avg/yr)', +export interface StackedChartConfig { + /** Display label for the chart */ + label: string; + /** If set, use this feature's stats for the total and info popup. Otherwise sum components. */ + feature?: string; + /** Suffix shown after the total value (e.g. "avg/yr") */ + unit?: string; + /** Feature names that make up the segments */ + components: string[]; +} + +/** + * Groups whose features should be collapsed into stacked bar charts. + * Keyed by feature group name. Each entry defines one stacked chart. + */ +export const STACKED_GROUPS: Record = { + Crime: [ + { + label: 'Serious crime', + feature: 'Serious crime (avg/yr)', + unit: 'avg/yr', + components: [ + 'Violence and sexual offences (avg/yr)', + 'Robbery (avg/yr)', + 'Burglary (avg/yr)', + 'Possession of weapons (avg/yr)', + ], + }, + { + label: 'Minor crime', + feature: 'Minor crime (avg/yr)', + unit: 'avg/yr', + components: [ + 'Anti-social behaviour (avg/yr)', + 'Criminal damage and arson (avg/yr)', + 'Shoplifting (avg/yr)', + 'Bicycle theft (avg/yr)', + 'Theft from the person (avg/yr)', + 'Other theft (avg/yr)', + 'Vehicle crime (avg/yr)', + 'Public order (avg/yr)', + 'Drugs (avg/yr)', + 'Other crime (avg/yr)', + ], + }, ], - 'Minor crime (avg/yr)': [ - 'Anti-social behaviour (avg/yr)', - 'Criminal damage and arson (avg/yr)', - 'Shoplifting (avg/yr)', - 'Bicycle theft (avg/yr)', - 'Theft from the person (avg/yr)', - 'Other theft (avg/yr)', - 'Vehicle crime (avg/yr)', - 'Public order (avg/yr)', - 'Drugs (avg/yr)', - 'Other crime (avg/yr)', + Demographics: [ + { + label: 'Ethnic composition', + unit: '%', + components: ['% White', '% Asian', '% Black', '% Mixed', '% Other'], + }, ], }; -/** Colors for crime breakdown segments (designed for 10 distinct categories) */ -export const CRIME_SEGMENT_COLORS = [ +/** Colors for stacked bar segments */ +export const SEGMENT_COLORS = [ '#ef4444', // red-500 '#f97316', // orange-500 '#eab308', // yellow-500