Общее задание

This commit is contained in:
Александр Костюхин 2026-04-04 08:30:53 +03:00
parent 2eb43edec5
commit a8704609bc
9 changed files with 335 additions and 0 deletions

16
data/observations.csv Normal file
View 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
1 station_id date temperature precipitation wind_speed
2 ST001 2024-01-15 -15.5 0.5 2.3
3 ST001 2024-06-15 22.3 0.0 4.1
4 ST001 2024-12-20 -8.2 1.2 12.5
5 ST002 2024-03-10 1.2 0.8 5.5
6 ST002 2024-07-20 18.5 0.3 3.2
7 ST002 2024-10-05 8.3 2.1 11.8
8 ST003 2024-02-01 -25.8 0.2 1.5
9 ST003 2024-08-15 19.7 0.0 6.8
10 ST003 2024-11-30 -12.4 1.5 9.3
11 ST004 2024-04-25 10.2 0.1 4.7
12 ST004 2024-09-12 14.6 1.8 15.2
13 ST004 2024-05-18 16.8 0.0 3.9
14 ST005 2024-01-20 -10.3 0.7 8.2
15 ST005 2024-07-25 24.1 0.0 5.1
16 ST005 2024-12-10 -5.6 0.9 7.6

6
data/stations.csv Normal file
View 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
1 station_id station_name latitude longitude
2 ST001 Москва 55.7558 37.6173
3 ST002 Санкт-Петербург 59.9311 30.3609
4 ST003 Новосибирск 55.0084 82.9357
5 ST004 Екатеринбург 56.8389 60.6057
6 ST005 Нижний Новгород 56.2965 43.9361

0
src/__init__.py Normal file
View File

35
src/data_loader.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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