2026-08-18 21:26:34 +03:00
|
|
|
\set ON_ERROR_STOP on
|
|
|
|
|
|
|
|
|
|
BEGIN;
|
|
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS weather (
|
|
|
|
|
dateTime TIMESTAMP NOT NULL,
|
|
|
|
|
city VARCHAR(255) NOT NULL,
|
|
|
|
|
tempMax DOUBLE PRECISION,
|
|
|
|
|
tempMin DOUBLE PRECISION,
|
|
|
|
|
tempAvg DOUBLE PRECISION,
|
|
|
|
|
precipitation DOUBLE PRECISION,
|
|
|
|
|
windAvg DOUBLE PRECISION,
|
|
|
|
|
windMax DOUBLE PRECISION,
|
|
|
|
|
visibilityMin DOUBLE PRECISION,
|
|
|
|
|
visibilityAvg DOUBLE PRECISION,
|
|
|
|
|
snowAvg DOUBLE PRECISION,
|
|
|
|
|
atmPressure DOUBLE PRECISION,
|
|
|
|
|
dewPoint DOUBLE PRECISION,
|
|
|
|
|
humidity DOUBLE PRECISION,
|
|
|
|
|
sunDuration DOUBLE PRECISION,
|
|
|
|
|
phenomena TEXT[],
|
|
|
|
|
UNIQUE(city, dateTime)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
WITH
|
|
|
|
|
settings AS (
|
|
|
|
|
SELECT
|
|
|
|
|
date_trunc('hour', CURRENT_TIMESTAMP)::timestamp AS end_at,
|
|
|
|
|
(date_trunc('hour', CURRENT_TIMESTAMP) - INTERVAL '18 months')::timestamp AS start_at
|
|
|
|
|
),
|
|
|
|
|
cities(city, climate_offset) AS (
|
|
|
|
|
VALUES
|
|
|
|
|
('Ainaži', -0.8), ('Alūksne', -2.3), ('Bauska', 0.7), ('Dagda', -1.7),
|
|
|
|
|
('Daugavgrīva', 0.6), ('Daugavpils', -0.9), ('Dobele', 0.8), ('Gulbene', -2.0),
|
|
|
|
|
('Jelgava', 0.9), ('Kalnciems', 0.7), ('Kolka', 0.3), ('Kuldīga', 0.8),
|
|
|
|
|
('Lielpēči', 0.2), ('Liepāja', 1.1), ('Madona', -1.8), ('Mērsrags', 0.2),
|
|
|
|
|
('Pāvilosta', 0.9), ('Piedruja', -1.4), ('Priekuļi', -1.6), ('Rēzekne', -1.2),
|
|
|
|
|
('Rīga', 1.3), ('Rucava', 1.0), ('Rūjiena', -1.0), ('Saldus', 0.3),
|
|
|
|
|
('Sigulda', -0.8), ('Sīļi', -1.3), ('Skrīveri', -0.5), ('Skulte', 0.0),
|
2026-08-19 17:29:03 +03:00
|
|
|
('Stende', -0.3), ('Valmiera', -1.2), ('Ventspils', 0.9), ('Vičaki', 0.2), ('Zīlāni', -1.1),
|
2026-08-18 21:26:34 +03:00
|
|
|
('Zosēni', -2.1)
|
|
|
|
|
),
|
|
|
|
|
hours AS (
|
|
|
|
|
SELECT generate_series(settings.start_at, settings.end_at, INTERVAL '1 hour') AS observed_at
|
|
|
|
|
FROM settings
|
|
|
|
|
),
|
|
|
|
|
base AS (
|
|
|
|
|
SELECT
|
|
|
|
|
hours.observed_at,
|
|
|
|
|
cities.city,
|
|
|
|
|
cities.climate_offset,
|
|
|
|
|
abs(hashtext(cities.city || hours.observed_at::text)) AS sample_hash,
|
|
|
|
|
7.5
|
|
|
|
|
+ 12.5 * sin(2 * pi() * (extract(doy FROM hours.observed_at) - 172) / 365.25)
|
|
|
|
|
+ 2.8 * sin(2 * pi() * (extract(hour FROM hours.observed_at) - 9) / 24)
|
|
|
|
|
+ cities.climate_offset
|
|
|
|
|
+ 1.8 * sin(extract(epoch FROM hours.observed_at) / 173000 + cities.climate_offset) AS temperature
|
|
|
|
|
FROM hours
|
|
|
|
|
CROSS JOIN cities
|
|
|
|
|
),
|
|
|
|
|
weather_values AS (
|
|
|
|
|
SELECT
|
|
|
|
|
*,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN sample_hash % 100 < 13
|
|
|
|
|
THEN round(((sample_hash % 190) / 10.0 + 0.2)::numeric, 1)::double precision
|
|
|
|
|
ELSE 0.0
|
|
|
|
|
END AS rain,
|
|
|
|
|
round((1.2 + (sample_hash % 65) / 10.0)::numeric, 1)::double precision AS wind,
|
|
|
|
|
round((58 + (sample_hash % 34) + 8 * cos(2 * pi() * extract(doy FROM observed_at) / 365.25))::numeric, 1)::double precision AS relative_humidity
|
|
|
|
|
FROM base
|
|
|
|
|
),
|
|
|
|
|
final_values AS (
|
|
|
|
|
SELECT
|
|
|
|
|
*,
|
|
|
|
|
GREATEST(0.0, LEAST(100.0, relative_humidity)) AS bounded_humidity,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN temperature < 1.0 AND rain > 0
|
|
|
|
|
THEN round((rain * 0.8 + (sample_hash % 25) / 10.0)::numeric, 1)::double precision
|
|
|
|
|
WHEN temperature < -2.0
|
|
|
|
|
THEN round(((sample_hash % 80) / 10.0)::numeric, 1)::double precision
|
|
|
|
|
ELSE 0.0
|
|
|
|
|
END AS snow,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN extract(hour FROM observed_at) BETWEEN 7 AND 18 AND rain = 0
|
|
|
|
|
THEN round((35 + sample_hash % 26)::numeric, 1)::double precision
|
|
|
|
|
ELSE 0.0
|
|
|
|
|
END AS sunshine_minutes
|
|
|
|
|
FROM weather_values
|
|
|
|
|
)
|
|
|
|
|
INSERT INTO weather (
|
|
|
|
|
dateTime, city, tempMax, tempMin, tempAvg, precipitation, windAvg, windMax,
|
|
|
|
|
visibilityMin, visibilityAvg, snowAvg, atmPressure, dewPoint, humidity,
|
|
|
|
|
sunDuration, phenomena
|
|
|
|
|
)
|
|
|
|
|
SELECT
|
|
|
|
|
observed_at,
|
|
|
|
|
city,
|
|
|
|
|
CASE WHEN sample_hash % 997 = 0 THEN NULL ELSE round((temperature + 1.8 + (sample_hash % 12) / 10.0)::numeric, 1)::double precision END,
|
|
|
|
|
CASE WHEN sample_hash % 991 = 0 THEN NULL ELSE round((temperature - 1.6 - (sample_hash % 10) / 10.0)::numeric, 1)::double precision END,
|
|
|
|
|
round(temperature::numeric, 1)::double precision,
|
|
|
|
|
rain,
|
|
|
|
|
wind,
|
|
|
|
|
round((wind + 1.5 + (sample_hash % 70) / 10.0)::numeric, 1)::double precision,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN rain > 12 THEN round((0.8 + sample_hash % 20 / 10.0)::numeric, 1)::double precision
|
|
|
|
|
WHEN bounded_humidity > 88 THEN round((1.5 + sample_hash % 40 / 10.0)::numeric, 1)::double precision
|
|
|
|
|
ELSE round((8 + sample_hash % 80 / 10.0)::numeric, 1)::double precision
|
|
|
|
|
END,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN rain > 12 THEN round((3 + sample_hash % 40 / 10.0)::numeric, 1)::double precision
|
|
|
|
|
ELSE round((12 + sample_hash % 90 / 10.0)::numeric, 1)::double precision
|
|
|
|
|
END,
|
|
|
|
|
snow,
|
|
|
|
|
round((1000 + sample_hash % 310 / 10.0 + 5 * sin(extract(epoch FROM observed_at) / 250000))::numeric, 1)::double precision,
|
|
|
|
|
round((temperature - (100 - bounded_humidity) / 5.0)::numeric, 1)::double precision,
|
|
|
|
|
CASE WHEN sample_hash % 983 = 0 THEN NULL ELSE round(bounded_humidity::numeric, 1)::double precision END,
|
|
|
|
|
sunshine_minutes,
|
|
|
|
|
CASE
|
|
|
|
|
WHEN snow > 0.5 THEN ARRAY['snow']::text[]
|
|
|
|
|
WHEN rain > 12 THEN ARRAY['heavy rain', 'overcast']::text[]
|
|
|
|
|
WHEN rain > 0 THEN ARRAY['rain']::text[]
|
|
|
|
|
WHEN bounded_humidity > 88 THEN ARRAY['fog']::text[]
|
|
|
|
|
WHEN wind > 6.5 THEN ARRAY['windy']::text[]
|
|
|
|
|
WHEN sunshine_minutes > 0 THEN ARRAY['clear']::text[]
|
|
|
|
|
ELSE ARRAY['cloudy']::text[]
|
|
|
|
|
END
|
|
|
|
|
FROM final_values
|
|
|
|
|
ON CONFLICT (city, dateTime) DO UPDATE SET
|
|
|
|
|
tempMax = EXCLUDED.tempMax,
|
|
|
|
|
tempMin = EXCLUDED.tempMin,
|
|
|
|
|
tempAvg = EXCLUDED.tempAvg,
|
|
|
|
|
precipitation = EXCLUDED.precipitation,
|
|
|
|
|
windAvg = EXCLUDED.windAvg,
|
|
|
|
|
windMax = EXCLUDED.windMax,
|
|
|
|
|
visibilityMin = EXCLUDED.visibilityMin,
|
|
|
|
|
visibilityAvg = EXCLUDED.visibilityAvg,
|
|
|
|
|
snowAvg = EXCLUDED.snowAvg,
|
|
|
|
|
atmPressure = EXCLUDED.atmPressure,
|
|
|
|
|
dewPoint = EXCLUDED.dewPoint,
|
|
|
|
|
humidity = EXCLUDED.humidity,
|
|
|
|
|
sunDuration = EXCLUDED.sunDuration,
|
|
|
|
|
phenomena = EXCLUDED.phenomena;
|
|
|
|
|
|
|
|
|
|
ANALYZE weather;
|
|
|
|
|
|
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
|
|
SELECT
|
|
|
|
|
count(*) AS synthetic_rows,
|
|
|
|
|
count(DISTINCT city) AS stations,
|
|
|
|
|
min(dateTime) AS first_observation,
|
|
|
|
|
max(dateTime) AS last_observation
|
|
|
|
|
FROM weather;
|