Общее задание
This commit is contained in:
parent
2eb43edec5
commit
a8704609bc
16
data/observations.csv
Normal file
16
data/observations.csv
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
station_id,date,temperature,precipitation,wind_speed
|
||||||
|
ST001,2024-01-15,-15.5,0.5,2.3
|
||||||
|
ST001,2024-06-15,22.3,0.0,4.1
|
||||||
|
ST001,2024-12-20,-8.2,1.2,12.5
|
||||||
|
ST002,2024-03-10,1.2,0.8,5.5
|
||||||
|
ST002,2024-07-20,18.5,0.3,3.2
|
||||||
|
ST002,2024-10-05,8.3,2.1,11.8
|
||||||
|
ST003,2024-02-01,-25.8,0.2,1.5
|
||||||
|
ST003,2024-08-15,19.7,0.0,6.8
|
||||||
|
ST003,2024-11-30,-12.4,1.5,9.3
|
||||||
|
ST004,2024-04-25,10.2,0.1,4.7
|
||||||
|
ST004,2024-09-12,14.6,1.8,15.2
|
||||||
|
ST004,2024-05-18,16.8,0.0,3.9
|
||||||
|
ST005,2024-01-20,-10.3,0.7,8.2
|
||||||
|
ST005,2024-07-25,24.1,0.0,5.1
|
||||||
|
ST005,2024-12-10,-5.6,0.9,7.6
|
||||||
|
6
data/stations.csv
Normal file
6
data/stations.csv
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
station_id,station_name,latitude,longitude
|
||||||
|
ST001,Москва,55.7558,37.6173
|
||||||
|
ST002,Санкт-Петербург,59.9311,30.3609
|
||||||
|
ST003,Новосибирск,55.0084,82.9357
|
||||||
|
ST004,Екатеринбург,56.8389,60.6057
|
||||||
|
ST005,Нижний Новгород,56.2965,43.9361
|
||||||
|
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
35
src/data_loader.py
Normal file
35
src/data_loader.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import csv
|
||||||
|
from src.data_parser import parse_observation_line
|
||||||
|
|
||||||
|
def load_observations(file_path: str) -> list:
|
||||||
|
"""Загружает наблюдения из CSV-файла"""
|
||||||
|
try:
|
||||||
|
observations = []
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
|
reader = csv.reader(file)
|
||||||
|
next(reader) # Пропускаем заголовок
|
||||||
|
for row in reader:
|
||||||
|
line = ','.join(row)
|
||||||
|
observations.append(parse_observation_line(line))
|
||||||
|
return observations
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Ошибка: Файл {file_path} не найден")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def load_stations(file_path: str) -> dict:
|
||||||
|
"""Загружает справочник станций из CSV-файла"""
|
||||||
|
try:
|
||||||
|
stations = {}
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
|
reader = csv.DictReader(file)
|
||||||
|
for row in reader:
|
||||||
|
station_id = row['station_id']
|
||||||
|
stations[station_id] = {
|
||||||
|
"name": row['station_name'],
|
||||||
|
"latitude": float(row['latitude']),
|
||||||
|
"longitude": float(row['longitude'])
|
||||||
|
}
|
||||||
|
return stations
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Ошибка: Файл {file_path} не найден")
|
||||||
|
return {}
|
||||||
19
src/data_parser.py
Normal file
19
src/data_parser.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
def parse_observation_line(line: str) -> dict:
|
||||||
|
"""Парсит строку из CSV-файла с наблюдениями"""
|
||||||
|
parts = line.strip().split(',')
|
||||||
|
|
||||||
|
station_id = parts[0]
|
||||||
|
date = parts[1]
|
||||||
|
|
||||||
|
# Преобразуем числовые значения, пустые строки заменяем на None
|
||||||
|
temperature = float(parts[2]) if parts[2] and parts[2] != '' else None
|
||||||
|
precipitation = float(parts[3]) if parts[3] and parts[3] != '' else None
|
||||||
|
wind_speed = float(parts[4]) if parts[4] and parts[4] != '' else None
|
||||||
|
|
||||||
|
return {
|
||||||
|
"station_id": station_id,
|
||||||
|
"date": date,
|
||||||
|
"temperature": temperature,
|
||||||
|
"precipitation": precipitation,
|
||||||
|
"wind_speed": wind_speed
|
||||||
|
}
|
||||||
45
src/filters.py
Normal file
45
src/filters.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
def filter_by_date_range(observations: list, start_date: str, end_date: str) -> list:
|
||||||
|
"""Фильтрует наблюдения по диапазону дат"""
|
||||||
|
filtered = []
|
||||||
|
for obs in observations:
|
||||||
|
if start_date <= obs['date'] <= end_date:
|
||||||
|
filtered.append(obs)
|
||||||
|
return filtered
|
||||||
|
|
||||||
|
|
||||||
|
def clean_observations(observations: list) -> list:
|
||||||
|
"""Очищает наблюдения от некорректных значений"""
|
||||||
|
cleaned = []
|
||||||
|
|
||||||
|
for obs in observations:
|
||||||
|
# Проверяем температуру
|
||||||
|
temp = obs['temperature']
|
||||||
|
if temp is not None and (temp < -50 or temp > 50):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Проверяем осадки
|
||||||
|
precip = obs['precipitation']
|
||||||
|
if precip is not None and precip < 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Проверяем скорость ветра
|
||||||
|
wind = obs['wind_speed']
|
||||||
|
if wind is not None and wind < 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Если все проверки пройдены, добавляем запись
|
||||||
|
cleaned_obs = obs.copy()
|
||||||
|
cleaned.append(cleaned_obs)
|
||||||
|
|
||||||
|
return cleaned
|
||||||
|
|
||||||
|
def classify_wind_speed(speed: float) -> str:
|
||||||
|
"""Классифицирует скорость ветра"""
|
||||||
|
if speed < 1:
|
||||||
|
return "штиль"
|
||||||
|
elif 1 <= speed < 5:
|
||||||
|
return "слабый"
|
||||||
|
elif 5 <= speed <= 10:
|
||||||
|
return "умеренный"
|
||||||
|
else:
|
||||||
|
return "сильный"
|
||||||
73
src/main.py
Normal file
73
src/main.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import os
|
||||||
|
from src.data_loader import load_observations, load_stations
|
||||||
|
from src.filters import filter_by_date_range, clean_observations
|
||||||
|
from src.statistics import (calculate_daily_stats, find_extreme_stations,
|
||||||
|
add_wind_category)
|
||||||
|
from src.report_generator import generate_report
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
DATA_DIR = "../data"
|
||||||
|
REPORTS_DIR = "../reports"
|
||||||
|
START_DATE = "2024-01-01"
|
||||||
|
END_DATE = "2024-12-31"
|
||||||
|
|
||||||
|
os.makedirs(REPORTS_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
print("Загрузка данных о станциях...")
|
||||||
|
stations = load_stations(f"{DATA_DIR}/stations.csv")
|
||||||
|
|
||||||
|
print("Загрузка данных о наблюдениях...")
|
||||||
|
observations = load_observations(f"{DATA_DIR}/observations.csv")
|
||||||
|
|
||||||
|
if not observations:
|
||||||
|
print("Нет данных для обработки!")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"Фильтрация данных за период {START_DATE} - {END_DATE}...")
|
||||||
|
filtered_obs = filter_by_date_range(observations, START_DATE, END_DATE)
|
||||||
|
|
||||||
|
print("Очистка данных...")
|
||||||
|
cleaned_obs = clean_observations(filtered_obs)
|
||||||
|
|
||||||
|
print("Добавление категорий ветра...")
|
||||||
|
obs_with_wind = add_wind_category(cleaned_obs)
|
||||||
|
|
||||||
|
print("Вычисление дневной статистики...")
|
||||||
|
daily_stats = calculate_daily_stats(obs_with_wind)
|
||||||
|
|
||||||
|
print("Поиск экстремальных станций...")
|
||||||
|
top_avg_temp = find_extreme_stations(obs_with_wind, stations, "avg_temp", 3)
|
||||||
|
top_max_wind = find_extreme_stations(obs_with_wind, stations, "max_wind", 3)
|
||||||
|
|
||||||
|
print("Формирование отчета...")
|
||||||
|
output_path = f"{REPORTS_DIR}/weather_report.txt"
|
||||||
|
generate_report(obs_with_wind, stations, daily_stats, top_avg_temp, output_path)
|
||||||
|
|
||||||
|
print("\n" + "=" * 50)
|
||||||
|
print("РЕЗУЛЬТАТЫ АНАЛИЗА")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
print("\nТоп-3 станции по средней температуре:")
|
||||||
|
for i, (name, value) in enumerate(top_avg_temp, 1):
|
||||||
|
print(f" {i}. {name}: {value:.2f}°C")
|
||||||
|
|
||||||
|
print("\nТоп-3 станции по максимальной скорости ветра:")
|
||||||
|
for i, (name, value) in enumerate(top_max_wind, 1):
|
||||||
|
# Определяем категорию ветра для каждого значения
|
||||||
|
if value < 1:
|
||||||
|
category = "штиль"
|
||||||
|
elif 1 <= value < 5:
|
||||||
|
category = "слабый"
|
||||||
|
elif 5 <= value <= 10:
|
||||||
|
category = "умеренный"
|
||||||
|
else:
|
||||||
|
category = "сильный"
|
||||||
|
print(f" {i}. {name}: {value:.1f} м/с ({category})")
|
||||||
|
|
||||||
|
print(f"\nОтчет сохранен: {output_path}")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
47
src/report_generator.py
Normal file
47
src/report_generator.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
def generate_report(observations: list, stations: dict, daily_stats: dict,
|
||||||
|
extreme_stations: list, output_path: str) -> None:
|
||||||
|
"""Формирует текстовый отчет"""
|
||||||
|
|
||||||
|
# Общее количество наблюдений
|
||||||
|
total_observations = len(observations)
|
||||||
|
|
||||||
|
# Диапазон дат
|
||||||
|
dates = [obs['date'] for obs in observations]
|
||||||
|
if dates:
|
||||||
|
min_date = min(dates)
|
||||||
|
max_date = max(dates)
|
||||||
|
else:
|
||||||
|
min_date = max_date = "Нет данных"
|
||||||
|
|
||||||
|
# Список экстремальных станций
|
||||||
|
extreme_stations_str = "\n".join([f" - {name}: {value:.2f}" for name, value in extreme_stations])
|
||||||
|
|
||||||
|
# Средняя температура за весь период
|
||||||
|
temperatures = [obs['temperature'] for obs in observations if obs['temperature'] is not None]
|
||||||
|
avg_temperature = sum(temperatures) / len(temperatures) if temperatures else 0
|
||||||
|
|
||||||
|
# Количество дней с сильным ветром
|
||||||
|
strong_wind_days = sum(1 for obs in observations
|
||||||
|
if obs.get('wind_category') == "сильный")
|
||||||
|
|
||||||
|
# Формируем отчет
|
||||||
|
report = f"""ОТЧЕТ ПО ПОГОДНЫМ НАБЛЮДЕНИЯМ
|
||||||
|
{'=' * 50}
|
||||||
|
|
||||||
|
Общее количество обработанных наблюдений: {total_observations}
|
||||||
|
|
||||||
|
Диапазон дат: {min_date} - {max_date}
|
||||||
|
|
||||||
|
Список экстремальных станций (топ-3):
|
||||||
|
{extreme_stations_str}
|
||||||
|
|
||||||
|
Средняя температура за весь период: {avg_temperature:.2f}°C
|
||||||
|
|
||||||
|
Количество дней с сильным ветром: {strong_wind_days}
|
||||||
|
|
||||||
|
{'=' * 50}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Сохраняем отчет
|
||||||
|
with open(output_path, 'w', encoding='utf-8') as file:
|
||||||
|
file.write(report)
|
||||||
94
src/statistics.py
Normal file
94
src/statistics.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
from src.filters import classify_wind_speed
|
||||||
|
|
||||||
|
def calculate_daily_stats(observations: list) -> dict:
|
||||||
|
"""Вычисляет дневную статистику по наблюдениям"""
|
||||||
|
daily_data = {}
|
||||||
|
|
||||||
|
for obs in observations:
|
||||||
|
date = obs['date']
|
||||||
|
|
||||||
|
if date not in daily_data:
|
||||||
|
daily_data[date] = {
|
||||||
|
'temperatures': [],
|
||||||
|
'total_precip': 0,
|
||||||
|
'max_wind': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Добавляем температуру (игнорируем None)
|
||||||
|
if obs['temperature'] is not None:
|
||||||
|
daily_data[date]['temperatures'].append(obs['temperature'])
|
||||||
|
|
||||||
|
# Добавляем осадки
|
||||||
|
if obs['precipitation'] is not None:
|
||||||
|
daily_data[date]['total_precip'] += obs['precipitation']
|
||||||
|
|
||||||
|
# Обновляем максимальный ветер
|
||||||
|
if obs['wind_speed'] is not None:
|
||||||
|
daily_data[date]['max_wind'] = max(daily_data[date]['max_wind'], obs['wind_speed'])
|
||||||
|
|
||||||
|
# Формируем результат
|
||||||
|
result = {}
|
||||||
|
for date, data in daily_data.items():
|
||||||
|
avg_temp = sum(data['temperatures']) / len(data['temperatures']) if data['temperatures'] else None
|
||||||
|
result[date] = {
|
||||||
|
'avg_temp': avg_temp,
|
||||||
|
'total_precip': data['total_precip'],
|
||||||
|
'max_wind': data['max_wind']
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def find_extreme_stations(observations: list, stations: dict, metric: str, top_n: int = 3) -> list:
|
||||||
|
"""Находит топ-N станций по заданной метрике"""
|
||||||
|
station_metrics = {}
|
||||||
|
|
||||||
|
# Группируем наблюдения по станциям
|
||||||
|
for obs in observations:
|
||||||
|
station_id = obs['station_id']
|
||||||
|
if station_id not in station_metrics:
|
||||||
|
station_metrics[station_id] = {
|
||||||
|
'temperatures': [],
|
||||||
|
'wind_speeds': []
|
||||||
|
}
|
||||||
|
|
||||||
|
if obs['temperature'] is not None:
|
||||||
|
station_metrics[station_id]['temperatures'].append(obs['temperature'])
|
||||||
|
|
||||||
|
if obs['wind_speed'] is not None:
|
||||||
|
station_metrics[station_id]['wind_speeds'].append(obs['wind_speed'])
|
||||||
|
|
||||||
|
# Вычисляем метрики для каждой станции
|
||||||
|
results = []
|
||||||
|
for station_id, metrics in station_metrics.items():
|
||||||
|
if station_id not in stations:
|
||||||
|
continue
|
||||||
|
|
||||||
|
station_name = stations[station_id]['name']
|
||||||
|
|
||||||
|
if metric == 'avg_temp':
|
||||||
|
if metrics['temperatures']:
|
||||||
|
value = sum(metrics['temperatures']) / len(metrics['temperatures'])
|
||||||
|
results.append((station_name, value))
|
||||||
|
elif metric == 'max_wind':
|
||||||
|
if metrics['wind_speeds']:
|
||||||
|
value = max(metrics['wind_speeds'])
|
||||||
|
results.append((station_name, value))
|
||||||
|
|
||||||
|
# Сортируем и берем top_n
|
||||||
|
results.sort(key=lambda x: x[1], reverse=True)
|
||||||
|
return results[:top_n]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def add_wind_category(observations: list) -> list:
|
||||||
|
"""Добавляет категорию ветра к наблюдениям"""
|
||||||
|
new_observations = []
|
||||||
|
for obs in observations:
|
||||||
|
obs_copy = obs.copy()
|
||||||
|
if obs_copy['wind_speed'] is not None:
|
||||||
|
obs_copy['wind_category'] = classify_wind_speed(obs_copy['wind_speed'])
|
||||||
|
else:
|
||||||
|
obs_copy['wind_category'] = None
|
||||||
|
new_observations.append(obs_copy)
|
||||||
|
return new_observations
|
||||||
Loading…
Reference in New Issue
Block a user