feat(weather): wehaer value sky model instead of text cloudness

This commit is contained in:
2024-07-26 18:25:15 +03:00
parent f711b2d77b
commit 48a6cce569
6 changed files with 135 additions and 47 deletions

View File

@@ -16,6 +16,7 @@ RUN apt update && \
dpkg-reconfigure --frontend=noninteractive locales
ENV LANG=ru_RU.UTF-8
ENV LC_ALL=ru_RU.UTF-8
ENV TZ="Europe/Moscow"
COPY --from=builder /app ./
COPY gismeteo gismeteo/
COPY weather weather/

View File

@@ -10,7 +10,7 @@ class LocationValue(NamedTuple):
@classmethod
def parse(cls, source: str) -> "LocationValue":
location, name = source.split("-")
name, location = source.split("-")
return cls(int(location), name)

View File

@@ -5,6 +5,8 @@ from typing import Dict, Iterable, List, Optional
import dateparser
from bs4 import Tag
from weather.model import Cloudness, Precipitation, Sky, WindDirection
from .core import BaseWidgetParser, RowParser
ONE_DAY_PARSER = BaseWidgetParser(".widget.widget-oneday .widget-items")
@@ -40,12 +42,45 @@ class DateParser(RowParser[datetime.datetime]):
yield time
class CloudnessParser(RowParser[str]):
KEY = "cloudness"
class SkyParser(RowParser[Sky]):
KEY = "sky"
def parse_row(self, tag: Tag) -> Iterable[str]:
CLOUDNESS_MAP: Dict[str, Cloudness] = {
"ясно": Cloudness.CLEAR,
"малооблачно": Cloudness.PARTLY_CLOUDY,
"облачно": Cloudness.CLOUDY,
"пасмурно": Cloudness.MAINLY_CLOUDY,
}
PRECIPITATION_MAP: Dict[str, Precipitation] = {
"без осадков": Precipitation.NO,
"небольшой дождь": Precipitation.SMALL_RAIN,
"дождь": Precipitation.RAIN,
"ливень": Precipitation.SHOWER,
}
def parse_row(self, tag: Tag) -> Iterable[Sky]:
for item in tag.select(".widget-row[data-row=icon-tooltip] > .row-item"):
yield item.attrs["data-tooltip"]
sky_str = item.attrs["data-tooltip"]
values = {item.strip().lower() for item in sky_str.split(",")}
cloudness = Cloudness.CLEAR
precipitation = Precipitation.NO
thunder = "гроза" in values
fog = "дымка" in values
for k, v in self.CLOUDNESS_MAP.items():
if k in values:
cloudness = v
break
for k, v in self.PRECIPITATION_MAP.items():
if k in values:
precipitation = v
break
yield Sky(
cloudness=cloudness,
precipitation=precipitation,
thunder=thunder,
fog=fog,
)
class TemperatureParser(RowParser[int]):
@@ -77,14 +112,27 @@ class WindGustParser(RowParser[int]):
yield int(value.attrs["value"]) if value else 0
class WindDirectionParser(RowParser[str]):
class WindDirectionParser(RowParser[WindDirection]):
KEY = "wind_direction"
def parse_row(self, tag: Tag) -> Iterable[str]:
WIND_DIRECTION_MAP: Dict[str, WindDirection] = {
"штиль": WindDirection.CALM,
"с": WindDirection.N,
"св": WindDirection.NO,
"в": WindDirection.O,
"юв": WindDirection.SO,
"ю": WindDirection.S,
"юз": WindDirection.SW,
"з": WindDirection.W,
"сз": WindDirection.NW,
}
def parse_row(self, tag: Tag) -> Iterable[WindDirection]:
for item in tag.select(
".widget-row[data-row=wind-direction] > .row-item > .direction"
):
yield item.text
wind_direction_str = item.text.lower()
yield self.WIND_DIRECTION_MAP[wind_direction_str]
class WindPrecipitationParser(RowParser[float]):
@@ -117,7 +165,7 @@ class HumidityParser(RowParser[int]):
ROW_PARSERS: List[RowParser] = [
DateParser(),
CloudnessParser(),
SkyParser(),
TemperatureParser(),
WindSpeedParser(),
WindGustParser(),

View File

@@ -1,21 +1,60 @@
import datetime
from enum import Enum
from pydantic import BaseModel
class WeatherValue(BaseModel):
class Model(BaseModel):
class Config:
use_enum_values = True
class Cloudness(str, Enum):
CLEAR = "clear"
PARTLY_CLOUDY = "party_cloudy"
CLOUDY = "cloudy"
MAINLY_CLOUDY = "mainly_cloudy"
class Precipitation(str, Enum):
NO = "no"
SMALL_RAIN = "small_rain"
RAIN = "rain"
SHOWER = "shower"
class Sky(Model):
cloudness: Cloudness
precipitation: Precipitation
thunder: bool
fog: bool
class WindDirection(str, Enum):
CALM = "calm"
N = "N"
NO = "NO"
O = "O"
SO = "SO"
S = "S"
SW = "SW"
W = "W"
NW = "NW"
class WeatherValue(Model):
date: datetime.datetime
cloudness: str
sky: Sky
temperature: int
wind_speed: int
wind_gust: int
wind_direction: str
wind_direction: WindDirection
precipitation: float
pressure: int
humidity: int
class WeatherResponse(BaseModel):
class WeatherResponse(Model):
location: str
date: datetime.date
period: str

View File

@@ -1,38 +1,39 @@
def wind_direction_icon(wind_direction: str) -> str:
from weather.model import Cloudness, Precipitation, Sky, WindDirection
def wind_direction_icon(wind_direction: WindDirection) -> str:
return {
"С": "🡫",
"СВ": "🡯",
"В": "🡨",
"ЮВ": "🡬",
"Ю": "🡡",
"ЮЗ": "🡭",
"З": "🡪",
"СЗ": "🡦",
"штиль": "",
WindDirection.N: "🡫",
WindDirection.NO: "🡯",
WindDirection.O: "🡨",
WindDirection.SO: "🡬",
WindDirection.S: "🡡",
WindDirection.SW: "🡭",
WindDirection.W: "🡪",
WindDirection.NW: "🡦",
WindDirection.CALM: "",
}.get(wind_direction, wind_direction)
def cloudness_icon(cloudness: str) -> list[str]:
icons = []
values = {item.strip().lower() for item in cloudness.split(",")}
if "дымка" in values:
def cloudness_icon(sky: Sky) -> list[str]:
main_icon = ""
if sky.thunder:
if sky.cloudness == Cloudness.CLEAR:
main_icon = "🌩️"
if sky.precipitation == Precipitation.NO:
main_icon = ""
else:
main_icon = "⛈️"
elif sky.precipitation == Precipitation.NO:
main_icon = {
Cloudness.CLEAR: "☀️",
Cloudness.PARTLY_CLOUDY: "🌤️",
Cloudness.CLOUDY: "",
Cloudness.MAINLY_CLOUDY: "☁️",
}[sky.cloudness]
else:
main_icon = "🌧️"
icons = [main_icon]
if sky.fog:
icons.append("🌫️")
values.remove("дымка")
icons = [
{
frozenset(["ясно"]): "☀️",
frozenset(["малооблачно", "без осадков"]): "🌤️",
frozenset(["облачно", "без осадков"]): "",
frozenset(["малооблачно", "небольшой дождь"]): "🌦️",
frozenset(["пасмурно", "без осадков"]): "☁️",
frozenset(["облачно", "небольшой дождь"]): "🌧️",
frozenset(["облачно", "дождь"]): "🌧️",
frozenset(["облачно", "небольшой дождь", "гроза"]): "⛈️",
frozenset(["малооблачно", "дождь"]): "🌦️",
frozenset(["пасмурно", "небольшой дождь"]): "🌧️",
frozenset(["облачно", "дождь", "гроза"]): "⛈️",
frozenset(["пасмурно", "дождь"]): "🌧️",
frozenset(["пасмурно", "дождь", "гроза"]): "⛈️",
}.get(frozenset(values), "|".join(values))
] + icons
return icons

View File

@@ -44,7 +44,7 @@
<tr>
{% for value in response.values %}
<td class="cloudness">
{% for icon in value.cloudness | cloudness_icon %}
{% for icon in value.sky | cloudness_icon %}
<div class="icon">{{icon}}</div>
{% endfor %}
</td>
@@ -76,7 +76,6 @@
{% for value in response.values %}
<td class="wind">
<span class="icon">{{value.wind_direction | wind_direction_icon}}</span>
<span class="direction">{{value.wind_direction}}</span>
</td>
{% endfor %}
</tr>