feat(CRIT-010): add oil_prices API endpoint with zone-to-county mapping
Add GET /oil-prices/county/:county_id public endpoint to serve scraped oil price data by county. Includes oil_prices table definition in schema and a backfill script to populate county_id on existing records. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
63
db/backfill_county_id.sql
Normal file
63
db/backfill_county_id.sql
Normal file
@@ -0,0 +1,63 @@
|
||||
-- Backfill county_id for existing oil_prices records based on state+zone mapping
|
||||
-- Run this ONCE after adding the county_id column to oil_prices
|
||||
|
||||
-- First, add the column if it doesn't exist
|
||||
ALTER TABLE oil_prices ADD COLUMN IF NOT EXISTS county_id INTEGER;
|
||||
|
||||
-- Connecticut
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'New London' AND state = 'CT') WHERE LOWER(state) = 'connecticut' AND zone = 1 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Windham' AND state = 'CT') WHERE LOWER(state) = 'connecticut' AND zone = 2 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'New Haven' AND state = 'CT') WHERE LOWER(state) = 'connecticut' AND zone = 3 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Middlesex' AND state = 'CT') WHERE LOWER(state) = 'connecticut' AND zone = 4 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'New Haven' AND state = 'CT') WHERE LOWER(state) = 'connecticut' AND zone = 5 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Hartford' AND state = 'CT') WHERE LOWER(state) = 'connecticut' AND zone = 6 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Litchfield' AND state = 'CT') WHERE LOWER(state) = 'connecticut' AND zone = 7 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Fairfield' AND state = 'CT') WHERE LOWER(state) = 'connecticut' AND zone = 8 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Tolland' AND state = 'CT') WHERE LOWER(state) = 'connecticut' AND zone = 9 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Litchfield' AND state = 'CT') WHERE LOWER(state) = 'connecticut' AND zone = 10 AND county_id IS NULL;
|
||||
|
||||
-- Massachusetts
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Suffolk' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 1 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Middlesex' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 2 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Norfolk' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 3 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Plymouth' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 4 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Middlesex' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 5 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Bristol' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 6 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Barnstable' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 7 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Essex' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 8 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Essex' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 9 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Worcester' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 10 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Worcester' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 11 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Hampshire' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 12 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Hampden' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 13 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Franklin' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 14 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Berkshire' AND state = 'MA') WHERE LOWER(state) = 'massachusetts' AND zone = 15 AND county_id IS NULL;
|
||||
|
||||
-- New Hampshire
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Coos' AND state = 'NH') WHERE LOWER(state) = 'newhampshire' AND zone = 1 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Strafford' AND state = 'NH') WHERE LOWER(state) = 'newhampshire' AND zone = 2 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Merrimack' AND state = 'NH') WHERE LOWER(state) = 'newhampshire' AND zone = 3 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Grafton' AND state = 'NH') WHERE LOWER(state) = 'newhampshire' AND zone = 4 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Cheshire' AND state = 'NH') WHERE LOWER(state) = 'newhampshire' AND zone = 5 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Hillsborough' AND state = 'NH') WHERE LOWER(state) = 'newhampshire' AND zone = 6 AND county_id IS NULL;
|
||||
|
||||
-- Rhode Island
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Newport' AND state = 'RI') WHERE LOWER(state) = 'rhodeisland' AND zone = 1 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Providence' AND state = 'RI') WHERE LOWER(state) = 'rhodeisland' AND zone = 2 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Washington' AND state = 'RI') WHERE LOWER(state) = 'rhodeisland' AND zone = 3 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Kent' AND state = 'RI') WHERE LOWER(state) = 'rhodeisland' AND zone = 4 AND county_id IS NULL;
|
||||
|
||||
-- Maine
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Cumberland' AND state = 'ME') WHERE LOWER(state) = 'maine' AND zone = 1 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Kennebec' AND state = 'ME') WHERE LOWER(state) = 'maine' AND zone = 2 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Androscoggin' AND state = 'ME') WHERE LOWER(state) = 'maine' AND zone = 3 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'York' AND state = 'ME') WHERE LOWER(state) = 'maine' AND zone = 4 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Knox' AND state = 'ME') WHERE LOWER(state) = 'maine' AND zone = 5 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Penobscot' AND state = 'ME') WHERE LOWER(state) = 'maine' AND zone = 6 AND county_id IS NULL;
|
||||
UPDATE oil_prices SET county_id = (SELECT id FROM county WHERE name = 'Washington' AND state = 'ME') WHERE LOWER(state) = 'maine' AND zone = 7 AND county_id IS NULL;
|
||||
|
||||
-- Verify
|
||||
SELECT state, zone, county_id, COUNT(*) as record_count
|
||||
FROM oil_prices
|
||||
GROUP BY state, zone, county_id
|
||||
ORDER BY state, zone;
|
||||
16
schema.sql
16
schema.sql
@@ -58,6 +58,22 @@ CREATE TABLE listings (
|
||||
last_edited TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Oil prices (scraped from NewEnglandOil.com / MaineOil.com)
|
||||
CREATE TABLE oil_prices (
|
||||
id SERIAL PRIMARY KEY,
|
||||
state VARCHAR(100),
|
||||
zone INTEGER,
|
||||
name VARCHAR(255),
|
||||
price DOUBLE PRECISION,
|
||||
date VARCHAR(20),
|
||||
scrapetimestamp TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||
company_id INTEGER,
|
||||
county_id INTEGER
|
||||
);
|
||||
|
||||
-- If the table already exists, add county_id
|
||||
-- ALTER TABLE oil_prices ADD COLUMN county_id INTEGER;
|
||||
|
||||
-- If the table already exists, add the new columns
|
||||
-- ALTER TABLE listings ADD COLUMN price_per_gallon_cash DOUBLE PRECISION;
|
||||
-- ALTER TABLE listings ADD COLUMN note TEXT;
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::auth::auth::{auth_middleware, login, register, logout};
|
||||
use crate::data::data::get_user;
|
||||
use crate::state::data::{get_counties_by_state, get_county_by_id};
|
||||
use crate::listing::data::{get_listings, get_listing_by_id, get_listings_by_county, create_listing, update_listing, delete_listing};
|
||||
use crate::oil_prices::data::get_oil_prices_by_county;
|
||||
use axum::middleware;
|
||||
use sqlx::PgPool;
|
||||
use std::sync::Arc;
|
||||
@@ -18,6 +19,7 @@ mod data;
|
||||
mod state;
|
||||
mod company;
|
||||
mod listing;
|
||||
mod oil_prices;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
@@ -82,7 +84,8 @@ async fn main() {
|
||||
.route("/categories", axum::routing::get(crate::company::category::get_all_categories))
|
||||
.route("/state/:state_abbr", axum::routing::get(get_counties_by_state))
|
||||
.route("/state/:state_abbr/:county_id", axum::routing::get(get_county_by_id))
|
||||
.route("/listings/county/:county_id", axum::routing::get(get_listings_by_county));
|
||||
.route("/listings/county/:county_id", axum::routing::get(get_listings_by_county))
|
||||
.route("/oil-prices/county/:county_id", axum::routing::get(get_oil_prices_by_county));
|
||||
|
||||
let app = public_routes
|
||||
.merge(protected_routes)
|
||||
|
||||
34
src/oil_prices/data.rs
Normal file
34
src/oil_prices/data.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
Json,
|
||||
};
|
||||
use crate::auth::structs::AppState;
|
||||
use crate::oil_prices::structs::OilPrice;
|
||||
use serde_json::json;
|
||||
|
||||
pub async fn get_oil_prices_by_county(
|
||||
State(app_state): State<AppState>,
|
||||
Path(county_id): Path<i32>,
|
||||
) -> Result<Json<Vec<OilPrice>>, (StatusCode, Json<serde_json::Value>)> {
|
||||
tracing::info!(county_id = county_id, "Fetching oil prices by county");
|
||||
match sqlx::query_as::<_, OilPrice>(
|
||||
"SELECT id, state, zone, name, price, date, scrapetimestamp, county_id FROM oil_prices WHERE county_id = $1 ORDER BY price ASC"
|
||||
)
|
||||
.bind(county_id)
|
||||
.fetch_all(&*app_state.db)
|
||||
.await
|
||||
{
|
||||
Ok(prices) => {
|
||||
tracing::info!(county_id = county_id, count = prices.len(), "Oil prices retrieved");
|
||||
Ok(Json(prices))
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!(county_id = county_id, error = %e, "Failed to fetch oil prices");
|
||||
Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({"error": format!("Failed to fetch oil prices: {}", e)}))
|
||||
))
|
||||
},
|
||||
}
|
||||
}
|
||||
2
src/oil_prices/mod.rs
Normal file
2
src/oil_prices/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod structs;
|
||||
pub mod data;
|
||||
15
src/oil_prices/structs.rs
Normal file
15
src/oil_prices/structs.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use chrono::NaiveDateTime;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
||||
pub struct OilPrice {
|
||||
pub id: i32,
|
||||
pub state: Option<String>,
|
||||
pub zone: Option<i32>,
|
||||
pub name: Option<String>,
|
||||
pub price: Option<f64>,
|
||||
pub date: Option<String>,
|
||||
pub scrapetimestamp: Option<NaiveDateTime>,
|
||||
pub county_id: Option<i32>,
|
||||
}
|
||||
Reference in New Issue
Block a user