├── .gitattributes
├── .gitignore
├── .streamlit
└── config.toml
├── Dockerfile
├── LICENSE.md
├── README.md
├── app.py
├── app
├── __init__.py
├── build.py
├── forms.py
├── plot.py
└── utils.py
├── examples
├── addis-ababa-ethiopia-temperature-mean-2022-ref-1961-1990.png
├── atlantic-ocean-temperature-mean-2023-ref-1961-1990.png
├── bad-neuenahr-ahrweiler-germany-precipitation-rolling-2021-ref-1961-1990.png
├── beijing-china-temperature-mean-2022-ref-1961-1990.png
├── berlin-germany-temperature-mean-2023-ref-1961-1990.png
├── buenos-aires-argentina-temperature-mean-2022-ref-1961-1990.png
├── cape-town-south-africa-temperature-mean-2022-ref-1961-1990.png
├── caracas-venezuela-precipitation-cum-2014-ref-1961-1990.png
├── caracas-venezuela-temperature-mean-2022-ref-1961-1990.png
├── duisburg-germany-temperature-max-2019-ref-1961-1990.png
├── hawaii-county-united-states-temperature-mean-2022-ref-1961-1990.png
├── helsinki-finland-temperature-mean-2022-ref-1961-1990.png
├── kampala-uganda-temperature-mean-2022-ref-1961-1990.png
├── key-west-united-states-temperature-max-2023-ref-1961-1990.png
├── kyiv-ukraine-temperature-mean-2022-ref-1961-1990.png
├── madrid-spain-temperature-mean-2023-ref-1961-1990.png
├── monterrey-mexico-temperature-mean-2023-ref-1961-1990.png
├── mumbai-india-precipitation-cum-2022-ref-1961-1990.png
├── mumbai-india-precipitation-rolling-2021-ref-1961-1990.png
├── mumbai-india-temperature-mean-2022-ref-1961-1990.png
├── pacific-ocean-temperature-mean-2023-ref-1961-1990.png
├── prague-czechia-precipitation-rolling-2022-ref-1961-1990.png
├── riyadh-saudi-arabia-temperature-mean-2022-ref-1961-1990.png
├── rome-italy-temperature-max-2023-ref-1961-1990.png
├── san-francisco-united-states-temperature-mean-2022-ref-1961-1990.png
├── seville-spain-precipitation-cum-2023-ref-1961-1990.png
└── sydney-australia-temperature-mean-2022-ref-1961-1990.png
├── header.png
├── meteo_hist
├── __init__.py
├── base.py
├── exceptions.py
└── interactive.py
├── notebooks
└── meteo_hist_example.ipynb
├── requirements.txt
├── sample_data
├── raw
│ ├── precipitation_cum-caracas-venezuela.csv
│ ├── precipitation_cum-oaxaca-city-mexico.csv
│ ├── temperature_max-caracas-venezuela.csv
│ ├── temperature_max-oaxaca-city-mexico.csv
│ ├── temperature_mean-addis-ababa-ethiopia.csv
│ └── temperature_mean-berlin-germany.csv
└── transformed
│ ├── precipitation_cum-caracas-venezuela_transformed.csv
│ ├── precipitation_cum-oaxaca-city-mexico_transformed.csv
│ ├── temperature_max-caracas-venezuela_transformed.csv
│ ├── temperature_max-oaxaca-city-mexico_transformed.csv
│ ├── temperature_mean-addis-ababa-ethiopia_transformed.csv
│ └── temperature_mean-berlin-germany_transformed.csv
├── style.css
└── tests
├── __init__.py
└── test_base.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Tell linguist to count Jupyter Notebooks as Python
2 | *.ipynb linguist-language=Python
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/#use-with-ide
110 | .pdm.toml
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Cython debug symbols
153 | cython_debug/
154 |
155 | # PyCharm
156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 | # and can be added to the global gitignore or merged into this file. For a more nuclear
159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 | #.idea/
161 |
162 | .directory
163 | tmp/
164 | output/*.png
165 | .vscode/
166 |
--------------------------------------------------------------------------------
/.streamlit/config.toml:
--------------------------------------------------------------------------------
1 | [global]
2 |
3 | # By default, Streamlit checks if the Python watchdog module is available and, if not, prints a warning asking for you to install it. The watchdog module is not required, but highly recommended. It improves Streamlit's ability to detect changes to files in your filesystem.
4 | # If you'd like to turn off this warning, set this to True.
5 | # Default: false
6 | # disableWatchdogWarning = false
7 |
8 | # By default, Streamlit displays a warning when a user sets both a widget default value in the function defining the widget and a widget value via the widget's key in `st.session_state`.
9 | # If you'd like to turn off this warning, set this to True.
10 | # Default: false
11 | # disableWidgetStateDuplicationWarning = false
12 |
13 | # If True, will show a warning when you run a Streamlit-enabled script via "python my_script.py".
14 | # Default: true
15 | # showWarningOnDirectExecution = true
16 |
17 | # DataFrame serialization.
18 | # Acceptable values: - 'legacy': Serialize DataFrames using Streamlit's custom format. Slow but battle-tested. - 'arrow': Serialize DataFrames using Apache Arrow. Much faster and versatile.
19 | # Default: "arrow"
20 | # dataFrameSerialization = "arrow"
21 |
22 |
23 | [logger]
24 |
25 | # Level of logging: 'error', 'warning', 'info', or 'debug'.
26 | # Default: 'info'
27 | # level = "info"
28 |
29 | # String format for logging messages. If logger.datetimeFormat is set, logger messages will default to `%(asctime)s.%(msecs)03d %(message)s`. See [Python's documentation](https://docs.python.org/2.6/library/logging.html#formatter-objects) for available attributes.
30 | # Default: "%(asctime)s %(message)s"
31 | # messageFormat = "%(asctime)s %(message)s"
32 |
33 |
34 | [client]
35 |
36 | # Whether to enable st.cache.
37 | # Default: true
38 | caching = true
39 |
40 | # If false, makes your Streamlit script not draw to a Streamlit app.
41 | # Default: true
42 | # displayEnabled = true
43 |
44 | # Controls whether uncaught app exceptions and deprecation warnings are displayed in the browser. By default, this is set to True and Streamlit displays app exceptions and associated tracebacks, and deprecation warnings, in the browser.
45 | # If set to False, an exception or deprecation warning will result in a generic message being shown in the browser, and exceptions, tracebacks, and deprecation warnings will be printed to the console only.
46 | # Default: true
47 | # showErrorDetails = true
48 |
49 | # Change the visibility of items in the toolbar, options menu, and settings dialog (top right of the app).
50 | # Allowed values: * "auto" : Show the developer options if the app is accessed through localhost and hide them otherwise. * "developer" : Show the developer options. * "viewer" : Hide the developer options. * "minimal" : Show only options set externally (e.g. through Streamlit Community Cloud) or through st.set_page_config. If there are no options left, hide the menu.
51 | # Default: "auto"
52 | toolbarMode = "minimal"
53 |
54 |
55 | [runner]
56 |
57 | # Allows you to type a variable or string by itself in a single line of Python code to write it to the app.
58 | # Default: true
59 | # magicEnabled = true
60 |
61 | # Install a Python tracer to allow you to stop or pause your script at any point and introspect it. As a side-effect, this slows down your script's execution.
62 | # Default: false
63 | # installTracer = false
64 |
65 | # Sets the MPLBACKEND environment variable to Agg inside Streamlit to prevent Python crashing.
66 | # Default: true
67 | # fixMatplotlib = true
68 |
69 | # Run the Python Garbage Collector after each script execution. This can help avoid excess memory use in Streamlit apps, but could introduce delay in rerunning the app script for high-memory-use applications.
70 | # Default: true
71 | # postScriptGC = true
72 |
73 | # Handle script rerun requests immediately, rather than waiting for script execution to reach a yield point. This makes Streamlit much more responsive to user interaction, but it can lead to race conditions in apps that mutate session_state data outside of explicit session_state assignment statements.
74 | # Default: true
75 | # fastReruns = true
76 |
77 | # Raise an exception after adding unserializable data to Session State. Some execution environments may require serializing all data in Session State, so it may be useful to detect incompatibility during development, or when the execution environment will stop supporting it in the future.
78 | # Default: false
79 | # enforceSerializableSessionState = false
80 |
81 |
82 | [server]
83 |
84 | # List of folders that should not be watched for changes. This impacts both "Run on Save" and @st.cache.
85 | # Relative paths will be taken as relative to the current working directory.
86 | # Example: ['/home/user1/env', 'relative/path/to/folder']
87 | # Default: []
88 | # folderWatchBlacklist = []
89 |
90 | # Change the type of file watcher used by Streamlit, or turn it off completely.
91 | # Allowed values: * "auto" : Streamlit will attempt to use the watchdog module, and falls back to polling if watchdog is not available. * "watchdog" : Force Streamlit to use the watchdog module. * "poll" : Force Streamlit to always use polling. * "none" : Streamlit will not watch files.
92 | # Default: "auto"
93 | # fileWatcherType = "auto"
94 |
95 | # Symmetric key used to produce signed cookies. If deploying on multiple replicas, this should be set to the same value across all replicas to ensure they all share the same secret.
96 | # Default: randomly generated secret key.
97 | # cookieSecret = "a753f1b3e04f77d51197fb1a9183909473f430ddbb69dfffe68b4cf24f44701b"
98 |
99 | # If false, will attempt to open a browser window on start.
100 | # Default: false unless (1) we are on a Linux box where DISPLAY is unset, or (2) we are running in the Streamlit Atom plugin.
101 | # headless = false
102 |
103 | # Automatically rerun script when the file is modified on disk.
104 | # Default: false
105 | # runOnSave = false
106 |
107 | # The address where the server will listen for client and browser connections. Use this if you want to bind the server to a specific address. If set, the server will only be accessible from this address, and not from any aliases (like localhost).
108 | # Default: (unset)
109 | # address =
110 |
111 | # The port where the server will listen for browser connections.
112 | # Default: 8501
113 | # port = 8501
114 |
115 | # The base path for the URL where Streamlit should be served from.
116 | # Default: ""
117 | # baseUrlPath = ""
118 |
119 | # Enables support for Cross-Origin Resource Sharing (CORS) protection, for added security.
120 | # Due to conflicts between CORS and XSRF, if `server.enableXsrfProtection` is on and `server.enableCORS` is off at the same time, we will prioritize `server.enableXsrfProtection`.
121 | # Default: true
122 | # enableCORS = true
123 |
124 | # Enables support for Cross-Site Request Forgery (XSRF) protection, for added security.
125 | # Due to conflicts between CORS and XSRF, if `server.enableXsrfProtection` is on and `server.enableCORS` is off at the same time, we will prioritize `server.enableXsrfProtection`.
126 | # Default: true
127 | # enableXsrfProtection = true
128 |
129 | # Max size, in megabytes, for files uploaded with the file_uploader.
130 | # Default: 200
131 | # maxUploadSize = 200
132 |
133 | # Max size, in megabytes, of messages that can be sent via the WebSocket connection.
134 | # Default: 200
135 | # maxMessageSize = 200
136 |
137 | # Enables support for websocket compression.
138 | # Default: false
139 | # enableWebsocketCompression = false
140 |
141 | # Enable serving files from a `static` directory in the running app's directory.
142 | # Default: false
143 | # enableStaticServing = false
144 |
145 | # Server certificate file for connecting via HTTPS. Must be set at the same time as "server.sslKeyFile".
146 | # ['DO NOT USE THIS OPTION IN A PRODUCTION ENVIRONMENT. It has not gone through security audits or performance tests. For the production environment, we recommend performing SSL termination by the load balancer or the reverse proxy.']
147 | # sslCertFile =
148 |
149 | # Cryptographic key file for connecting via HTTPS. Must be set at the same time as "server.sslCertFile".
150 | # ['DO NOT USE THIS OPTION IN A PRODUCTION ENVIRONMENT. It has not gone through security audits or performance tests. For the production environment, we recommend performing SSL termination by the load balancer or the reverse proxy.']
151 | # sslKeyFile =
152 |
153 |
154 | [browser]
155 |
156 | # Internet address where users should point their browsers in order to connect to the app. Can be IP address or DNS name and path.
157 | # This is used to: - Set the correct URL for CORS and XSRF protection purposes. - Show the URL on the terminal - Open the browser
158 | # Default: "localhost"
159 | # serverAddress = "localhost"
160 |
161 | # Whether to send usage statistics to Streamlit.
162 | # Default: true
163 | gatherUsageStats = false
164 |
165 | # Port where users should point their browsers in order to connect to the app.
166 | # This is used to: - Set the correct URL for CORS and XSRF protection purposes. - Show the URL on the terminal - Open the browser
167 | # Default: whatever value is set in server.port.
168 | # serverPort = 8501
169 |
170 |
171 | [mapbox]
172 |
173 | # Configure Streamlit to use a custom Mapbox token for elements like st.pydeck_chart and st.map. To get a token for yourself, create an account at https://mapbox.com. It's free (for moderate usage levels)!
174 | # Default: ""
175 | # token = ""
176 |
177 |
178 | [deprecation]
179 |
180 | # Set to false to disable the deprecation warning for the file uploader encoding.
181 | # Default: true
182 | # showfileUploaderEncoding = true
183 |
184 | # Set to false to disable the deprecation warning for using the global pyplot instance.
185 | # Default: true
186 | # showPyplotGlobalUse = true
187 |
188 |
189 | [theme]
190 |
191 | # The preset Streamlit theme that your custom theme inherits from. One of "light" or "dark".
192 | base = "light"
193 |
194 | # Primary accent color for interactive elements.
195 | # primaryColor =
196 |
197 | # Background color for the main content area.
198 | # backgroundColor =
199 |
200 | # Background color used for the sidebar and most interactive widgets.
201 | # secondaryBackgroundColor =
202 |
203 | # Color used for almost all text.
204 | # textColor =
205 |
206 | # Font family for all text in the app, except code blocks. One of "sans serif", "serif", or "monospace".
207 | # font =
208 |
209 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.12-slim
2 |
3 | WORKDIR /app
4 |
5 | # Install dependencies
6 | RUN apt-get update && \
7 | apt-get install -y \
8 | wget \
9 | unzip \
10 | curl \
11 | fontconfig && \
12 | apt-get clean && \
13 | rm -rf /var/lib/apt/lists/*
14 |
15 | # Install Lato font
16 | RUN mkdir -p /usr/share/fonts/truetype/lato && \
17 | curl -L -o /usr/share/fonts/truetype/lato/Lato-Bold.ttf "https://github.com/google/fonts/raw/main/ofl/lato/Lato-Bold.ttf" && \
18 | curl -L -o /usr/share/fonts/truetype/lato/Lato-BoldItalic.ttf "https://github.com/google/fonts/raw/main/ofl/lato/Lato-BoldItalic.ttf" && \
19 | curl -L -o /usr/share/fonts/truetype/lato/Lato-Italic.ttf "https://github.com/google/fonts/raw/main/ofl/lato/Lato-Italic.ttf" && \
20 | curl -L -o /usr/share/fonts/truetype/lato/Lato-Regular.ttf "https://github.com/google/fonts/raw/main/ofl/lato/Lato-Regular.ttf" && \
21 | fc-cache -f -v
22 |
23 | # Install Python dependencies
24 | COPY requirements.txt .
25 | RUN pip install -r requirements.txt
26 |
27 | # Copy app files
28 | COPY *.py .
29 | COPY *.css .
30 | COPY .streamlit/config.toml .streamlit/config.toml
31 | COPY ./app/ app/
32 | COPY ./meteo_hist/ meteo_hist/
33 | COPY ./examples/ examples/
34 |
35 | # Create log directory
36 | RUN mkdir -p /app/logs && chmod 777 /app/logs
37 |
38 | EXPOSE 8501
39 |
40 | HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
41 |
42 | ENTRYPOINT ["streamlit", "run", "app.py", "--server.port=8501", "--server.address=0.0.0.0"]
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
2 |
3 | Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.
4 |
5 | **Using Creative Commons Public Licenses**
6 |
7 | Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses.
8 |
9 | * __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors).
10 |
11 | * __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees).
12 |
13 | ## Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License
14 |
15 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
16 |
17 | ### Section 1 – Definitions.
18 |
19 | a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image.
20 |
21 | b. __Adapter's License__ means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License.
22 |
23 | c. __BY-NC-SA Compatible License__ means a license listed at [creativecommons.org/compatiblelicenses](http://creativecommons.org/compatiblelicenses), approved by Creative Commons as essentially the equivalent of this Public License.
24 |
25 | d. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights.
26 |
27 | e. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements.
28 |
29 | f. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material.
30 |
31 | g. __License Elements__ means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution, NonCommercial, and ShareAlike.
32 |
33 | h. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
34 |
35 | i. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
36 |
37 | j. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License.
38 |
39 | k. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange.
40 |
41 | l. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them.
42 |
43 | m. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world.
44 |
45 | n. __You__ means the individual or entity exercising the Licensed Rights under this Public License. __Your__ has a corresponding meaning.
46 |
47 | ### Section 2 – Scope.
48 |
49 | a. ___License grant.___
50 |
51 | 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
52 |
53 | A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and
54 |
55 | B. produce, reproduce, and Share Adapted Material for NonCommercial purposes only.
56 |
57 | 2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions.
58 |
59 | 3. __Term.__ The term of this Public License is specified in Section 6(a).
60 |
61 | 4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material.
62 |
63 | 5. __Downstream recipients.__
64 |
65 | A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License.
66 |
67 | B. __Additional offer from the Licensor – Adapted Material.__ Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply.
68 |
69 | C. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material.
70 |
71 | 6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i).
72 |
73 | b. ___Other rights.___
74 |
75 | 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise.
76 |
77 | 2. Patent and trademark rights are not licensed under this Public License.
78 |
79 | 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes.
80 |
81 | ### Section 3 – License Conditions.
82 |
83 | Your exercise of the Licensed Rights is expressly made subject to the following conditions.
84 |
85 | a. ___Attribution.___
86 |
87 | 1. If You Share the Licensed Material (including in modified form), You must:
88 |
89 | A. retain the following if it is supplied by the Licensor with the Licensed Material:
90 |
91 | i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated);
92 |
93 | ii. a copyright notice;
94 |
95 | iii. a notice that refers to this Public License;
96 |
97 | iv. a notice that refers to the disclaimer of warranties;
98 |
99 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable;
100 |
101 | B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and
102 |
103 | C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License.
104 |
105 | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information.
106 |
107 | 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable.
108 |
109 | b. ___ShareAlike.___
110 |
111 | In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply.
112 |
113 | 1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-NC-SA Compatible License.
114 |
115 | 2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material.
116 |
117 | 3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply.
118 |
119 | ### Section 4 – Sui Generis Database Rights.
120 |
121 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material:
122 |
123 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only;
124 |
125 | b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and
126 |
127 | c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database.
128 |
129 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights.
130 |
131 | ### Section 5 – Disclaimer of Warranties and Limitation of Liability.
132 |
133 | a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__
134 |
135 | b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__
136 |
137 | c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability.
138 |
139 | ### Section 6 – Term and Termination.
140 |
141 | a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically.
142 |
143 | b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates:
144 |
145 | 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or
146 |
147 | 2. upon express reinstatement by the Licensor.
148 |
149 | For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License.
150 |
151 | c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License.
152 |
153 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.
154 |
155 | ### Section 7 – Other Terms and Conditions.
156 |
157 | a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed.
158 |
159 | b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License.
160 |
161 | ### Section 8 – Interpretation.
162 |
163 | a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License.
164 |
165 | b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions.
166 |
167 | c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor.
168 |
169 | d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority.
170 |
171 | > Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses.
172 | >
173 | > Creative Commons may be contacted at creativecommons.org
174 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # MeteoHist - Historical Meteo Graphs
4 |
5 | ## A Streamlit app to create interactive temperature and precipitation graphs for places around the world.
6 |
7 | This app allows to create temperature and precipitation (rain, showers, and snowfall) graphs that compare the values of a given location in a given year to the values of a **reference period** at the same place.
8 |
9 | The reference period **defaults to 1961-1990** which [according](https://public.wmo.int/en/media/news/it%E2%80%99s-warmer-average-what-average) to the World Meteorological Organization (WMO) is currently the **best "long-term climate change assessment"**. Other reference periods of 30 years each can be selected, too.
10 |
11 | The **peaks** on the graph show how the displayed year's values deviate from the mean of the reference period. For temperature graphs, this means that the more and the higher the red peaks, the more "hotter days than usual" have been observed. The blue peaks indicate days colder than the historical mean. Precipitation graphs show blue peaks on top which means "more precipitation than normal" and in red "less than normal".
12 |
13 | The interactive plot is created using Python's **Plotly** library. In a first version with static images, **Matplotlib** came to use.
14 |
15 | By default, mean values of the reference period are **smoothed** using [Locally Weighted Scatterplot Smoothing (LOWESS)](https://www.statsmodels.org/devel/generated/statsmodels.nonparametric.smoothers_lowess.lowess.html). The value can be adjusted under "advanced settings" in the app.
16 |
17 | ### Interactive version
18 |
19 | In the latest version (first published on 17 August 2023), the graphs are displayed interactively on larger screens. That means you can hover over the graph and get the exact values displayed for every day. You can also zoom in to see parts of the plot.
20 |
21 | ### Data
22 |
23 | To create the graph, data from the open-source weather API [**Open-Meteo**](https://open-meteo.com/en/docs/historical-weather-api) is used. According to them, "the Historical Weather API is based on **reanalysis datasets** and uses a **combination of weather station, aircraft, buoy, radar, and satellite observations** to create a comprehensive record of past weather conditions. These datasets are able to **fill in gaps by using mathematical models** to estimate the values of various weather variables. As a result, reanalysis datasets are able to provide detailed historical weather information for **locations that may not have had weather stations nearby**, such as rural areas or the open ocean."
24 |
25 | The **Reanalysis Models** are based on [ERA5](https://cds.climate.copernicus.eu/cdsapp#!/dataset/reanalysis-era5-single-levels?tab=overview), [ERA5-Land](https://cds.climate.copernicus.eu/cdsapp#!/dataset/reanalysis-era5-land?tab=overview), and [CERRA](https://cds.climate.copernicus.eu/cdsapp#!/dataset/reanalysis-cerra-single-levels?tab=overview) from the [**European Union's Copernicus Programme**](https://www.copernicus.eu/en).
26 |
27 | To get location data (lat/lon) for the input location, [**Openstreetmap's Nominatim**](https://nominatim.openstreetmap.org/) is used.
28 |
29 | ### Metrics
30 |
31 | Available metrics are:
32 |
33 | - **Mean Temperature:** Mean daily air temperature at 2 meters above ground (24 hour aggregation from hourly values)
34 | - **Minimum Temperature:** Minimum daily air temperature at 2 meters above ground (24 hour aggregation from hourly values)
35 | - **Maximum Temperature:** Maximum daily air temperature at 2 meters above ground (24 hour aggregation from hourly values)
36 | - **Precipitation (Rolling Average):** 30-day rolling/moving average of the sum of daily precipitation (including rain, showers and snowfall)
37 | - **Precipitation (Cumulated):** Cumulated sum of daily precipitation (including rain, showers, and snowfall)
38 |
39 | ### Settings
40 |
41 | - **Location to display:** Name of the location you want to display. A search at Openstreetmap's Nominatim will be performed to find the location and get latitude and longitude.
42 | - **Year to show:** Year to be compared to reference period.
43 | - **Reference period:** The reference period is used to calculate the historical average of the daily values. The average is then used to compare the daily values of the selected year. 1961-1990 (default) is currently considered the best "long-term climate change assessment" by the World Meteorological Organization (WMO).
44 | - **Peaks to be annotated:** Number of maximum and minimum peaks to be annotated (default: 1). If peaks are too close together, the next highest/lowest peak is selected to avoid overlapping.
45 | - **Unit system:** Whether to use Metric System (°C, mm - default) or Imperial System (°F, In).
46 | - **Smoothing:** Degree of smoothing to apply to the historical data. 0 means no smoothing. The higher the value, the more smoothing is applied. Smoothing is done using LOWESS (Locally Weighted Scatterplot Smoothing).
47 | - **Peak method:** Method to determine the peaks. Either the difference to the historical mean (default) or the difference to the 05/95 percentiles. The percentile method focuses more on extreme events, while the mean method focuses more on the difference to the historical average.
48 | - **Emphasize peaks:** If checked, peaks that leave the gray area between the 5 and 95 percentiles will be highlighted more.
49 |
50 | ### Examples
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | ### License
74 |
75 | The app and the plots it produces are published under a [**Creative Commons license (CC by-sa-nc 4.0)**](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en).
76 |
77 | ### Try it
78 |
79 | You can try the app at [https://yotka.org/meteo-hist/](https://yotka.org/meteo-hist/)
80 |
81 | To use the app on your machine, there are two simple ways:
82 |
83 | **1. Set up a Python environment, clone the repository, and run app.py using streamlit:**
84 |
85 | ```bash
86 | git clone https://github.com/yotkadata/meteo_hist/
87 | cd meteo_hist/
88 | pip install -r requirements.txt
89 | streamlit run app.py
90 | ```
91 |
92 | This should open a page in your default browser at http://localhost:8501 that shows the app.
93 |
94 | **2. Set up Docker and run it in a container (you can change the name and the tag, of course):**
95 |
96 | ```bash
97 | docker build -t meteo_hist:latest github.com/yotkadata/meteo_hist
98 | docker run -d --name meteo_hist -p 8501:8501 meteo_hist:latest
99 | ```
100 |
101 | Then open http://localhost:8501 or http://0.0.0.0:8501/ in your browser to see the app.
102 |
103 | To save the generated files outside the Docker container, you can add a binding to a folder on your hard drive when you start the container:
104 | (replace `/home/user/path/output/` with the path to the folder to be used).
105 |
106 | ```bash
107 | docker run -d --name meteo_hist -p 8501:8501 -v /home/user/path/output/:/app/output meteo_hist:latest
108 | ```
109 |
110 | ### User Query Tracking
111 |
112 | The app includes an anonymous query tracking feature that logs information about how the app is used. This helps improve the application by understanding which locations, metrics, and settings are most valuable to users.
113 |
114 | #### What is tracked:
115 |
116 | - Timestamp of the query
117 | - Location coordinates and resolved location name
118 | - Selected metric (temperature/precipitation type)
119 | - Year and reference period being viewed
120 | - Selected visualization settings
121 |
122 | No personal data or identifying information is collected.
123 |
124 | #### Log file location:
125 |
126 | - When running locally: Logs are stored in a `logs` directory in the project root
127 | - In Docker: Logs are stored in `/app/logs` inside the container
128 |
129 | #### Accessing logs from Docker:
130 |
131 | Mount a volume to access logs from your Docker container:
132 |
133 | ```bash
134 | docker run -d --name meteo_hist -p 8501:8501 \
135 | -v /home/user/path/output/:/app/output \
136 | -v /home/user/path/logs/:/app/logs \
137 | meteo_hist:latest
138 | ```
139 |
140 | #### Log format:
141 |
142 | Logs are stored as JSON entries in a `user_queries.log` file for easy parsing and analysis:
143 |
144 | ```json
145 | {
146 | "timestamp": "2025-05-04T20:25:23.808898",
147 | "location": "Caracas, Venezuela",
148 | "coords": [10.506093, -66.914601],
149 | "metric": "precipitation_cum",
150 | "year": 2025,
151 | "reference_period": [1961, 1990],
152 | "settings": {
153 | "highlight_max": 3,
154 | "highlight_min": 2,
155 | "peak_alpha": false,
156 | "peak_method": "percentile",
157 | "peak_distance": 10,
158 | "smooth": { "apply": false, "frac": 0.083 },
159 | "system": "imperial"
160 | }
161 | }
162 | ```
163 |
164 | #### Configuration:
165 |
166 | You can customize the log directory by setting the `LOG_DIR` environment variable:
167 |
168 | ```bash
169 | LOG_DIR=/custom/path streamlit run app.py
170 | ```
171 |
172 | Or in Docker:
173 |
174 | ```bash
175 | docker run -d --name meteo_hist -p 8501:8501 -e LOG_DIR=/custom/logs meteo_hist:latest
176 | ```
177 |
178 | ### Using the class without the web interface
179 |
180 | It is also possible to use the Python class directly, without the web app. See the `notebooks` directory for examples.
181 |
182 | ### Using Open-Meteo API keys
183 |
184 | The Open-Meteo API doesn't require an API key, but limits the number of API calls without one. To use an API key provided by Open-Meteo, simply add the `OPEN_METEO_API_KEY` variable to a file called `.env` in the base directory. Example (replace [my_api_key] with your key):
185 |
186 | ```
187 | OPEN_METEO_API_KEY=[my_api_key]
188 | ```
189 |
190 | ### Thanks
191 |
192 | - This app was inspired by [plots](https://twitter.com/dr_xeo/status/1656933695511511043) made by [Dr. Dominic Royé](https://github.com/dominicroye) - thanks for the idea and the exchange about it.
193 |
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | """
2 | Streamlit app.
3 | """
4 |
5 | import time
6 |
7 | import streamlit as st
8 | from streamlit_js_eval import streamlit_js_eval
9 |
10 | from app import build_content, build_menu, get_form_defaults
11 |
12 |
13 | def main() -> None:
14 | """
15 | Main function.
16 | """
17 | # Set page title
18 | st.set_page_config(page_title="Historical Meteo Graphs", layout="wide")
19 |
20 | # Get screen width using window.innerWidth because screen.width
21 | # does not react to window size changes
22 | st.session_state["screen_width"] = streamlit_js_eval(
23 | js_expressions="window.innerWidth", key="SCR"
24 | )
25 |
26 | # Wait until screen width is set
27 | while (
28 | "screen_width" not in st.session_state
29 | or st.session_state["screen_width"] is None
30 | ):
31 | time.sleep(0.1)
32 |
33 | # Include custom CSS
34 | with open("style.css", encoding="utf-8") as css:
35 | st.markdown(f"", unsafe_allow_html=True)
36 |
37 | # Define default values for the form
38 | if "form_defaults" not in st.session_state:
39 | st.session_state["form_defaults"] = get_form_defaults()
40 |
41 | # Set base URL
42 | if "base_url" not in st.session_state:
43 | st.session_state["base_url"] = "https://yotka.org/meteo-hist/"
44 |
45 | # If screen size is large enough, use two columns
46 | if st.session_state["screen_width"] > 1200:
47 | col1, col2 = st.columns([1, 3])
48 | else:
49 | col1, col2 = st.container(), st.container()
50 |
51 | with col1:
52 | # Set page title
53 | st.markdown(
54 | "Historical Meteo Graphs",
55 | unsafe_allow_html=True,
56 | )
57 |
58 | # Create a placeholder for messages
59 | message_box = st.empty()
60 | build_menu()
61 |
62 | with col2:
63 | plot_placeholder = st.empty()
64 | build_content(plot_placeholder, message_box)
65 |
66 |
67 | if __name__ == "__main__":
68 | main()
69 |
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Streamlit app utility functions
3 | """
4 | from .utils import (
5 | build_location_by_coords,
6 | build_location_by_name,
7 | create_share_url,
8 | get_base_url,
9 | get_form_defaults,
10 | get_query_params,
11 | )
12 | from .forms import build_form, process_form
13 | from .plot import adjust_layout, create_graph, display_context_info
14 | from .build import build_content, build_menu
15 |
--------------------------------------------------------------------------------
/app/build.py:
--------------------------------------------------------------------------------
1 | """
2 | Streamlit app
3 | Functions related to building the page.
4 | """
5 |
6 | import base64
7 | import time
8 |
9 | import extra_streamlit_components as stx
10 | import folium
11 | import streamlit as st
12 | from folium import Icon
13 | from pydantic.v1.utils import deep_update
14 | from streamlit_js_eval import streamlit_js_eval
15 |
16 | from app import (
17 | build_form,
18 | create_graph,
19 | display_context_info,
20 | get_form_defaults,
21 | get_query_params,
22 | process_form,
23 | )
24 | from meteo_hist import MeteoHist
25 |
26 |
27 | def build_menu() -> None:
28 | """
29 | Create the column holding themenu.
30 | """
31 |
32 | # Get query parameters
33 | query_params = get_query_params()
34 |
35 | if len(query_params) > 0:
36 | st.session_state["form_defaults"] = deep_update(
37 | st.session_state["form_defaults"], query_params
38 | )
39 |
40 | default_tab = (
41 | query_params["method"]
42 | if "method" in query_params.keys()
43 | else st.session_state["form_defaults"]["method"]
44 | )
45 |
46 | # Create tab bar to select method for location input
47 | active_tab = stx.tab_bar(
48 | data=[
49 | stx.TabBarItemData(
50 | id="by_name",
51 | title="By name",
52 | description="Location by name",
53 | ),
54 | stx.TabBarItemData(
55 | id="by_coords",
56 | title="By lat/lon",
57 | description="Location by coordinates",
58 | ),
59 | ],
60 | default=default_tab,
61 | )
62 |
63 | if active_tab != st.session_state["form_defaults"]["method"]:
64 | # Reset form defaults once method is changed
65 | st.session_state["form_defaults"] = get_form_defaults()
66 | # Remove all query parameters from URL
67 | st.query_params.clear()
68 |
69 | # Build form
70 | st.session_state["input_values"] = build_form(
71 | method=active_tab, params=query_params
72 | )
73 |
74 | # Create button to show random graph
75 | st.session_state["random_graph"] = st.button("Show random")
76 |
77 | st.markdown(
78 | """
79 | yotka.org |
80 |
81 | github.com
82 | """,
83 | unsafe_allow_html=True,
84 | )
85 |
86 |
87 | def build_content(plot_placeholder, message_box) -> None:
88 | """
89 | Create the column holding the content.
90 | """
91 |
92 | # Save viewport width to session state
93 | st.session_state["viewport_width"] = streamlit_js_eval(
94 | js_expressions="window.innerWidth", key="ViewportWidth"
95 | )
96 |
97 | # Wait for viewport width to be set
98 | while (
99 | "viewport_width" not in st.session_state
100 | or st.session_state["viewport_width"] is None
101 | ):
102 | time.sleep(0.1)
103 |
104 | # Show a random graph on start (but not when the user clicks the "Create" button)
105 | if (
106 | "last_generated" not in st.session_state
107 | and st.session_state["input_values"] is None
108 | ):
109 | if "start_img" not in st.session_state:
110 | st.session_state["start_img"] = MeteoHist.show_random()
111 |
112 | if st.session_state["start_img"] is not None:
113 | plot_placeholder.image(st.session_state["start_img"])
114 |
115 | if st.session_state["input_values"] is not None:
116 | # Process form values
117 | input_processed = process_form(st.session_state["input_values"], message_box)
118 |
119 | # Make sure lat/lon values are set
120 | if isinstance(input_processed, dict) and not [
121 | x for x in (input_processed["lat"], input_processed["lon"]) if x is None
122 | ]:
123 | # Create figure for the graph
124 | plot_object, file_path = create_graph(input_processed, plot_placeholder)
125 |
126 | # Display some info about the data
127 | display_context_info(plot_object)
128 |
129 | # Display a download link
130 | try:
131 | with open(file_path, "rb") as file:
132 | img_b64 = base64.b64encode(file.read()).decode()
133 |
134 | st.markdown(
135 | f'Download file',
136 | unsafe_allow_html=True,
137 | )
138 | except FileNotFoundError:
139 | st.write("File not found.")
140 |
141 | st.write("")
142 |
143 | with st.expander("Share graph"):
144 | st.write("To share this graph, you can use the following URL:")
145 | st.write(f"{st.session_state['share_url']}", unsafe_allow_html=True)
146 |
147 | with st.expander("Show map"):
148 | with st.spinner("Creating map..."):
149 | # Show a map
150 | folium_map = folium.Map(
151 | location=[input_processed["lat"], input_processed["lon"]],
152 | zoom_start=4,
153 | )
154 |
155 | # Create a marker with a custom icon
156 | folium.Marker(
157 | location=[input_processed["lat"], input_processed["lon"]],
158 | popup=input_processed["location_name"],
159 | icon=Icon(icon="cloud"),
160 | ).add_to(folium_map)
161 |
162 | # Data from https://xyzservices.readthedocs.io/en/latest/introduction.html
163 | folium.TileLayer(
164 | tiles="https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
165 | attr="(C) OpenStreetMap contributors (C) CARTO",
166 | name="CartoDB.Positron",
167 | max_zoom=20,
168 | subdomains="abcd",
169 | ).add_to(folium_map)
170 |
171 | # Convert the map to an HTML string
172 | html_string = folium_map._repr_html_()
173 |
174 | # Display the map
175 | st.components.v1.html(html_string, width=500, height=500)
176 |
177 | if st.session_state["random_graph"]:
178 | st.write("Random graph from the list of graphs created before.")
179 | with plot_placeholder:
180 | img = MeteoHist.show_random()
181 | if img:
182 | st.session_state["start_img"] = img
183 | st.image(img)
184 | else:
185 | st.error("No graph found.")
186 |
--------------------------------------------------------------------------------
/app/forms.py:
--------------------------------------------------------------------------------
1 | """
2 | Streamlit app
3 | Functions related to form building and processing.
4 | """
5 |
6 | import datetime as dt
7 |
8 | import streamlit as st
9 | from pydantic.v1.utils import deep_update
10 |
11 | from app import build_location_by_coords, build_location_by_name, create_share_url
12 |
13 |
14 | def build_form(method: str = "by_name", params: dict = None) -> dict:
15 | """
16 | Build form for input values.
17 | """
18 | params_set = False
19 |
20 | defaults = st.session_state["form_defaults"]
21 |
22 | if isinstance(params, dict):
23 | if len(params) > 0:
24 | defaults = deep_update(defaults, params)
25 | params_set = True
26 |
27 | form_values = {"method": method}
28 |
29 | with st.form("settings_form"):
30 | if method == "by_coords":
31 | # Create input widgets for lat, lon, and display_name
32 | lat_col, lon_col = st.columns([1, 1])
33 |
34 | with lat_col:
35 | form_values["lat"] = st.text_input("Latitude:", value=defaults["lat"])
36 |
37 | with lon_col:
38 | form_values["lon"] = st.text_input("Longitude:", value=defaults["lon"])
39 |
40 | form_values["display_name"] = st.text_input(
41 | "Display name:",
42 | value=defaults["display_name"],
43 | help="""In case no display name can be found for the
44 | given lat/lon coordinates, this name will be used""",
45 | )
46 | if len(form_values["display_name"]) == 0:
47 | form_values["display_name"] = None
48 |
49 | if method == "by_name":
50 | # Create a text input widget for location name
51 | form_values["location"] = st.text_input(
52 | "Location to display:",
53 | help="""Write the name of the location you want to display.
54 | A search at Openstreetmap's Nominatim will be performed to
55 | find the location and get latitude and longitude. If the
56 | location cannot be found, please try again with a more
57 | specific name.""",
58 | )
59 |
60 | year_col, ref_col = st.columns([1, 1])
61 |
62 | with year_col:
63 | # Create input widget for year
64 | years = list(range(dt.datetime.now().year, 1939, -1))
65 | form_values["year"] = st.selectbox(
66 | "Year to show:", years, index=years.index(defaults["year"])
67 | )
68 |
69 | with ref_col:
70 | # Selectbox for reference period
71 | periods_tuples = [
72 | (1991, 2020),
73 | (1981, 2010),
74 | (1971, 2000),
75 | (1961, 1990),
76 | (1951, 1980),
77 | (1941, 1970),
78 | ]
79 |
80 | # Create str representation of tuples
81 | periods = [f"{period[0]}-{period[1]}" for period in periods_tuples]
82 |
83 | select_ref_period = st.selectbox(
84 | "Reference period:",
85 | periods,
86 | index=periods_tuples.index(defaults["ref_period"]),
87 | help="""
88 | The reference period is used to calculate the historical average of
89 | the daily values. The average is then used to compare the daily
90 | values of the selected year. 1961-1990 is currently considered
91 | the best "long-term climate change assessment" by the World Meteorological
92 | Organization (WMO).
93 | """,
94 | )
95 |
96 | # Use tuples as saved values
97 | if select_ref_period:
98 | form_values["ref_period"] = periods_tuples[
99 | periods.index(select_ref_period)
100 | ]
101 |
102 | # Selector for metric
103 | metrics = [
104 | {
105 | "name": "temperature_mean",
106 | "data": "temperature_2m_mean",
107 | },
108 | {
109 | "name": "temperature_min",
110 | "data": "temperature_2m_min",
111 | },
112 | {
113 | "name": "temperature_max",
114 | "data": "temperature_2m_max",
115 | },
116 | {
117 | "name": "precipitation_rolling",
118 | "data": "precipitation_sum",
119 | },
120 | {
121 | "name": "precipitation_cum",
122 | "data": "precipitation_sum",
123 | },
124 | ]
125 | metrics_names = [
126 | "Mean temperature",
127 | "Minimum temperature",
128 | "Maximum temperature",
129 | "Precipitation (Rolling average)",
130 | "Precipitation (Cumulated)",
131 | ]
132 |
133 | selected_metric = st.selectbox(
134 | "Metric:",
135 | metrics_names,
136 | index=next(
137 | i
138 | for i, metric_dict in enumerate(metrics)
139 | if metric_dict["name"] == defaults["metric"]
140 | ),
141 | )
142 | form_values["metric"] = metrics[metrics_names.index(selected_metric)]
143 |
144 | # Number of max peaks to annotate
145 | form_values["highlight_max"] = st.slider(
146 | "Maximum peaks to be annotated:",
147 | min_value=0,
148 | max_value=5,
149 | value=defaults["highlight_max"],
150 | help="""
151 | Number of peaks above the mean to be annotated. If peaks are close together,
152 | the text might overlap. In this case, reduce the number of peaks.
153 | """,
154 | )
155 |
156 | # Number of min peaks to annotate
157 | form_values["highlight_min"] = st.slider(
158 | "Minimum peaks to be annotated:",
159 | min_value=0,
160 | max_value=5,
161 | value=defaults["highlight_min"],
162 | help="""
163 | Number of peaks below the mean to be annotated. If peaks are close together,
164 | the text might overlap. In this case, reduce the number of peaks.
165 | """,
166 | )
167 |
168 | with st.expander("Advanced settings"):
169 | # Selection for unit system
170 | system = ["metric", "imperial"]
171 | system_names = ["Metric (°C, mm)", "Imperial (°F, In)"]
172 |
173 | selected_system = st.selectbox(
174 | "Unit system:",
175 | system_names,
176 | index=system.index(defaults["system"]),
177 | )
178 | form_values["system"] = system[system_names.index(selected_system)]
179 |
180 | # Slider to apply LOWESS smoothing
181 | form_values["smooth"] = st.slider(
182 | "Smoothing:",
183 | min_value=0,
184 | max_value=3,
185 | value=defaults["smooth"],
186 | help="""Degree of smoothing to apply to the historical data.
187 | 0 means no smoothing. The higher the value, the more smoothing
188 | is applied. Smoothing is done using LOWESS (Locally Weighted
189 | Scatterplot Smoothing).""",
190 | )
191 |
192 | # Select method to calculate peaks
193 | peak_methods = ["mean", "percentile"]
194 | peak_methods_names = ["Historical mean", "5/95 percentile"]
195 |
196 | peak_method = st.radio(
197 | "Peak method - Difference to:",
198 | peak_methods_names,
199 | index=peak_methods.index(defaults["peak_method"]),
200 | help="""
201 | Method to determine the peaks. Either the difference to the historical
202 | mean or the difference to the 5/95 percentile respectively. The percentile
203 | method focuses more on extreme events, while the mean method focuses more
204 | on the difference to the historical average.
205 | """,
206 | )
207 | form_values["peak_method"] = peak_methods[
208 | peak_methods_names.index(peak_method)
209 | ]
210 |
211 | # Checkbox to decide if peaks should be emphasized
212 | form_values["peak_alpha"] = st.checkbox(
213 | "Emphasize peaks",
214 | value=defaults["peak_alpha"],
215 | help="""
216 | If checked, peaks that leave the gray area between the 5 and 95
217 | percentile will be highlighted more.
218 | """,
219 | )
220 |
221 | # Checkbox to decide if months should have alternating background
222 | form_values["alternate_months"] = st.checkbox(
223 | "Alternate months",
224 | value=defaults["alternate_months"],
225 | help="""
226 | If checked, the background color of months is alternated.
227 | """,
228 | )
229 |
230 | # Create button to start the analysis
231 | form_values["create_graph"] = st.form_submit_button("Create")
232 |
233 | if form_values["create_graph"] or params_set:
234 | return form_values
235 |
236 | return None
237 |
238 |
239 | def process_form(form_values: dict, message_box) -> dict:
240 | """
241 | Process form values.
242 | """
243 | # Sanity checks
244 | if form_values is None:
245 | message_box.error("Please fill out the form.")
246 | return None
247 |
248 | if form_values["method"] == "by_name":
249 | if len(form_values["location"]) == 0:
250 | message_box.error("Please enter a location name.")
251 | return None
252 |
253 | lat, lon, location = build_location_by_name(
254 | form_values["location"], message_box
255 | )
256 | form_values["lat"] = lat
257 | form_values["lon"] = lon
258 | form_values["location_name"] = location
259 |
260 | if form_values["method"] == "by_coords":
261 | if len(form_values["lat"]) == 0 or len(form_values["lon"]) == 0:
262 | message_box.error("Please enter latitude and longitude.")
263 | return None
264 |
265 | try:
266 | form_values["lat"] = float(form_values["lat"])
267 | form_values["lon"] = float(form_values["lon"])
268 | except ValueError:
269 | message_box.error("Latitude and longitude must be numbers.")
270 | return None
271 |
272 | if form_values["lat"] < -90 or form_values["lat"] > 90:
273 | message_box.error("Latitude must be between -90 and 90.")
274 | return None
275 |
276 | if form_values["lon"] < -180 or form_values["lon"] > 180:
277 | message_box.error("Longitude must be between -180 and 180.")
278 | return None
279 |
280 | form_values["location_name"] = build_location_by_coords(
281 | form_values["lat"],
282 | form_values["lon"],
283 | form_values["display_name"],
284 | message_box,
285 | )
286 | if form_values["location_name"] is None:
287 | return None
288 |
289 | # Set share URL
290 | st.session_state["share_url"] = create_share_url(form_values)
291 |
292 | # Calculate values for smoothing
293 | if form_values["smooth"] == 0:
294 | form_values["smooth"] = {"apply": False}
295 | else:
296 | frac_values = {1: 1 / 52, 2: 1 / 24, 3: 1 / 12}
297 | form_values["smooth"] = {
298 | "apply": True,
299 | # Get frac value based on smoothing value in form
300 | # 1->1/52, 2->1/24, 3->1/12 (lower values mean less smoothing)
301 | "frac": frac_values[form_values["smooth"]],
302 | }
303 |
304 | # Setting for alternating background colors for months
305 | form_values["alternate_months"] = {"apply": form_values["alternate_months"]}
306 |
307 | return form_values
308 |
--------------------------------------------------------------------------------
/app/plot.py:
--------------------------------------------------------------------------------
1 | """
2 | Streamlit app
3 | Functions related to plotting and displaying data.
4 | """
5 |
6 | from copy import deepcopy
7 |
8 | import plotly.graph_objects as go
9 | import streamlit as st
10 |
11 | from meteo_hist import MeteoHist, MeteoHistInteractive, OpenMeteoAPIException
12 |
13 |
14 | def create_graph(inputs: dict, plot_placeholder) -> MeteoHist:
15 | """
16 | Create the graph.
17 | """
18 | with st.spinner("Creating graph..."):
19 | with plot_placeholder:
20 | # Don't save plot to file here, first show it
21 | inputs["save_file"] = False
22 |
23 | # Calculate width and height based on viewport width
24 | width = (
25 | st.session_state["viewport_width"]
26 | if st.session_state["viewport_width"] is not None
27 | else 1200
28 | )
29 | height = width * 3 / 5
30 |
31 | # Determine if we need to create a new plot object or update an existing one
32 | if "last_settings" in st.session_state:
33 | reload_keys = ["lat", "lon", "year", "ref_period", "metric", "system"]
34 | reload = any(
35 | inputs[key] != st.session_state["last_settings"][key]
36 | for key in reload_keys
37 | )
38 | else:
39 | reload = True
40 |
41 | if "plot" in st.session_state and not reload:
42 | # If plot object is already in session state, use it
43 | plot = st.session_state["plot"]
44 | plot.update_settings(inputs)
45 |
46 | else:
47 | try:
48 | # Instantiate the plot object
49 | plot = MeteoHistInteractive(
50 | coords=(inputs["lat"], inputs["lon"]),
51 | year=inputs["year"],
52 | reference_period=inputs["ref_period"],
53 | metric=inputs["metric"]["name"],
54 | settings=inputs,
55 | )
56 | except OpenMeteoAPIException as exc:
57 | st.error(
58 | f"There was an error retrieving the data from open-meteo.com. Error message:\n\n {exc}"
59 | )
60 | st.stop()
61 |
62 | # Save plot object and settings to session state
63 | st.session_state["plot"] = plot
64 | st.session_state["last_settings"] = inputs
65 |
66 | # Create figure
67 | figure, file_path = plot.create_plot()
68 |
69 | # Create a copy of the figure to display in Streamlit
70 | figure_display = deepcopy(figure)
71 |
72 | # Adjust font sizes etc. for display in Streamlit
73 | figure_display = adjust_layout(figure_display, width, height)
74 |
75 | # Display an interactive plot for large screens
76 | if st.session_state["screen_width"] >= 1200:
77 | st.plotly_chart(figure_display, theme=None, width=width, height=height)
78 |
79 | # Save the plot as a file
80 | file_path = plot.save_plot_to_file()
81 |
82 | # Display the plot as an image for small screens
83 | if st.session_state["screen_width"] < 1200:
84 | st.image(file_path)
85 |
86 | # Save the file path to session state
87 | st.session_state["last_generated"] = file_path
88 |
89 | return plot, file_path
90 |
91 |
92 | def adjust_layout(fig: go.Figure, width: int, height: int) -> go.Figure:
93 | """
94 | Adjust layout of plotly figure just for display in Streamlit.
95 | (This is a hacky workaround for the fact that Streamlit and Plotly
96 | have some deficiencies when it comes to responsive design.)
97 | """
98 | # Calculate factor based on viewport width
99 | factor = st.session_state["viewport_width"] / 1000
100 |
101 | # Adjust font sizes accordingly
102 |
103 | font_size = int(fig["layout"]["font"]["size"] * factor)
104 | font_size_title = int(fig["layout"]["title"]["font"]["size"] * factor)
105 | font_size_datainfo = int(12 * factor)
106 | margin_b = int(fig["layout"]["margin"]["b"] * factor)
107 | margin_l = int(fig["layout"]["margin"]["l"] * factor)
108 | margin_r = int(fig["layout"]["margin"]["r"] * factor)
109 | margin_t = int(fig["layout"]["margin"]["t"] * factor)
110 | margin_pad = int(fig["layout"]["margin"]["pad"] * factor)
111 |
112 | # Set layout options just for the plot in Streamlit
113 | layout_options = {
114 | "width": width,
115 | "height": height,
116 | "font_size": font_size,
117 | "title_font_size": font_size_title,
118 | "font": {"family": "Lato, Arial, sans-serif"},
119 | "title": {"font": {"family": "Lato, Arial, sans-serif"}},
120 | "margin": {
121 | "b": margin_b,
122 | "l": margin_l,
123 | "r": margin_r,
124 | "t": margin_t,
125 | "pad": margin_pad,
126 | },
127 | }
128 | fig.update_layout(layout_options)
129 |
130 | # Adjust position and font size of annotations
131 | for annotation_name in ["Data source", "Data info"]:
132 | fig.update_annotations(
133 | selector={"name": annotation_name},
134 | font_size=font_size_datainfo,
135 | font={"family": "Lato, Arial, sans-serif"},
136 | )
137 |
138 | return fig
139 |
140 |
141 | def display_context_info(graph: MeteoHist) -> None:
142 | """
143 | Display context information about the graph.
144 | """
145 | if graph.coords is not None:
146 | url = (
147 | f"https://www.openstreetmap.org/"
148 | f"?mlat={graph.coords[0]}&mlon={graph.coords[1]}"
149 | f"#map=6/{graph.coords[0]}/{graph.coords[1]}&layers=H"
150 | )
151 |
152 | st.markdown(
153 | f"""""",
157 | unsafe_allow_html=True,
158 | )
159 |
160 | st.markdown(
161 | f"""
162 | Last available date: {graph.last_date}
163 |
""",
164 | unsafe_allow_html=True,
165 | )
166 |
--------------------------------------------------------------------------------
/app/utils.py:
--------------------------------------------------------------------------------
1 | """
2 | Streamlit app utility functions
3 | """
4 |
5 | import datetime as dt
6 | import urllib.parse
7 |
8 | import streamlit as st
9 |
10 | from meteo_hist import MeteoHist
11 |
12 |
13 | def get_form_defaults() -> dict:
14 | """
15 | Get default values for the form.
16 | """
17 | form_defaults = {
18 | "method": "by_name",
19 | "lat": "",
20 | "lon": "",
21 | "display_name": "",
22 | "year": dt.datetime.now().year,
23 | "ref_period": (1961, 1990),
24 | "highlight_max": 1,
25 | "highlight_min": 1,
26 | "metric": "temperature_mean",
27 | "plot_type": "interactive",
28 | "system": "metric",
29 | "smooth": 3,
30 | "peak_method": "mean",
31 | "peak_alpha": True,
32 | "alternate_months": True,
33 | }
34 |
35 | return form_defaults
36 |
37 |
38 | def get_base_url() -> str:
39 | """
40 | Get base URL from current session.
41 | """
42 |
43 | if "base_url" in st.session_state:
44 | return st.session_state["base_url"]
45 |
46 | # If not set manually, get base URL
47 | session = st.runtime.get_instance()._session_mgr.list_active_sessions()[0]
48 | base_url = urllib.parse.urlunparse(
49 | [session.client.request.protocol, session.client.request.host, "", "", "", ""]
50 | )
51 |
52 | return base_url
53 |
54 |
55 | def get_query_params() -> dict:
56 | """
57 | Get query parameters from URL.
58 | """
59 |
60 | query = st.query_params
61 |
62 | allowed_params = [
63 | "lat",
64 | "lon",
65 | "display_name",
66 | "year",
67 | "ref_period",
68 | "highlight_max",
69 | "highlight_min",
70 | "metric",
71 | "system",
72 | "smooth",
73 | "peak_method",
74 | "peak_alpha",
75 | "alternate_months",
76 | ]
77 |
78 | # Filter out values not allowed or empty
79 | params = {
80 | key: query[key]
81 | for key in allowed_params
82 | if key in query.keys() and query[key] != ""
83 | }
84 |
85 | # Convert parameters and apply sanity checks
86 | remove_keys = []
87 |
88 | for key, value in params.items():
89 | # Check keys with float values
90 | if key in ["lat", "lon"] and isinstance(value, str):
91 | try:
92 | params[key] = float(value)
93 | if key == "lat" and (params[key] < -90 or params[key] > 90):
94 | remove_keys.append(key)
95 | if key == "lon" and (params[key] < -180 or params[key] > 180):
96 | remove_keys.append(key)
97 | except ValueError:
98 | remove_keys.append(key)
99 |
100 | # Check keys with int values
101 | elif key in ["year", "highlight_max", "highlight_min", "smooth"] and isinstance(
102 | value, str
103 | ):
104 | try:
105 | params[key] = int(value)
106 | if key == "year" and (
107 | params[key] < 1940 or params[key] > dt.datetime.now().year
108 | ):
109 | remove_keys.append(key)
110 | if (
111 | key in ["highlight_max", "highlight_min"]
112 | and not 0 <= params[key] <= 5
113 | ):
114 | remove_keys.append(key)
115 | if key == "smooth" and not 0 <= params[key] <= 3:
116 | remove_keys.append(key)
117 | except ValueError:
118 | remove_keys.append(key)
119 |
120 | # Check ref_period
121 | elif key == "ref_period" and isinstance(value, str):
122 | params[key] = (
123 | int("".join(value).split("-", maxsplit=1)[0]),
124 | int("".join(value).split("-", maxsplit=1)[1]),
125 | )
126 | if not (
127 | params[key][0] in [1941, 1951, 1961, 1971, 1981, 1991]
128 | and params[key][1] - params[key][0] == 29
129 | ):
130 | remove_keys.append(key)
131 |
132 | # Check boolean values
133 | elif key in ["peak_alpha", "alternate_months"] and isinstance(value, str):
134 | params[key] = value != "false"
135 |
136 | # Check unit system
137 | elif key == "system" and isinstance(value, str) and value != "imperial":
138 | remove_keys.append(key)
139 |
140 | # Check metric
141 | elif (
142 | key == "metric"
143 | and isinstance(value, str)
144 | and value
145 | not in [
146 | "temperature_min",
147 | "temperature_max",
148 | "precipitation_rolling",
149 | "precipitation_cum",
150 | ]
151 | ):
152 | remove_keys.append(key)
153 |
154 | # Remove invalid keys
155 | params = {key: value for key, value in params.items() if key not in remove_keys}
156 |
157 | if "lat" in params.keys() and "lon" in params.keys():
158 | params["method"] = "by_coords"
159 | return params
160 |
161 | return {}
162 |
163 |
164 | def create_share_url(params: dict) -> str:
165 | """
166 | Create URL with settings parameters to share graph.
167 | """
168 | params = params.copy()
169 |
170 | allowed_keys = [
171 | "lat",
172 | "lon",
173 | "display_name",
174 | "year",
175 | "ref_period",
176 | "highlight_max",
177 | "highlight_min",
178 | "metric",
179 | "system",
180 | "smooth",
181 | "peak_method",
182 | "peak_alpha",
183 | "alternate_months",
184 | ]
185 |
186 | # Change metric to str
187 | params["metric"] = params["metric"]["name"]
188 |
189 | # Filter out values not allowed, defaults, and empty values
190 | params = {
191 | key: params[key]
192 | for key in allowed_keys
193 | if key in params.keys()
194 | and params[key] != get_form_defaults()[key]
195 | and params[key] is not None
196 | }
197 |
198 | # Change ref_period to str
199 | if "ref_period" in params.keys():
200 | params["ref_period"] = f"{params['ref_period'][0]}-{params['ref_period'][1]}"
201 |
202 | # Change boolean values to lowercase
203 | if "peak_alpha" in params.keys():
204 | params["peak_alpha"] = str(params["peak_alpha"]).lower()
205 | if "alternate_months" in params.keys():
206 | params["alternate_months"] = str(params["alternate_months"]).lower()
207 |
208 | return f"{get_base_url()}?{urllib.parse.urlencode(params)}"
209 |
210 |
211 | def build_location_by_name(location: str, message_box) -> tuple[float, float, str]:
212 | """
213 | Build location by name.
214 | """
215 | with st.spinner("Searching for latitude and longitude..."):
216 | # Get the latitude and longitude
217 | location = MeteoHist.get_lat_lon(location)
218 |
219 | if len(location) == 0:
220 | message_box.error("Location not found. Please try again.")
221 | return None, None, None
222 |
223 | lat = location[0]["lat"]
224 | lon = location[0]["lon"]
225 | location_name = location[0]["location_name"]
226 |
227 | return lat, lon, location_name
228 |
229 |
230 | def build_location_by_coords(
231 | lat: float, lon: float, display_name: str, message_box
232 | ) -> str:
233 | """
234 | Build location by coordinates.
235 | """
236 | with st.spinner("Searching for location name..."):
237 | # Get the location name
238 | location = MeteoHist.get_location((lat, lon))
239 |
240 | if location is None and display_name is None:
241 | location = None
242 | message_box.error("Location not found. Please provide a display name.")
243 | return None
244 |
245 | if location is None and display_name is not None:
246 | location = display_name
247 | message_box.info("Location not found. Using display name.")
248 |
249 | return location
250 |
--------------------------------------------------------------------------------
/examples/addis-ababa-ethiopia-temperature-mean-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/addis-ababa-ethiopia-temperature-mean-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/atlantic-ocean-temperature-mean-2023-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/atlantic-ocean-temperature-mean-2023-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/bad-neuenahr-ahrweiler-germany-precipitation-rolling-2021-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/bad-neuenahr-ahrweiler-germany-precipitation-rolling-2021-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/beijing-china-temperature-mean-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/beijing-china-temperature-mean-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/berlin-germany-temperature-mean-2023-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/berlin-germany-temperature-mean-2023-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/buenos-aires-argentina-temperature-mean-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/buenos-aires-argentina-temperature-mean-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/cape-town-south-africa-temperature-mean-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/cape-town-south-africa-temperature-mean-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/caracas-venezuela-precipitation-cum-2014-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/caracas-venezuela-precipitation-cum-2014-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/caracas-venezuela-temperature-mean-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/caracas-venezuela-temperature-mean-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/duisburg-germany-temperature-max-2019-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/duisburg-germany-temperature-max-2019-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/hawaii-county-united-states-temperature-mean-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/hawaii-county-united-states-temperature-mean-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/helsinki-finland-temperature-mean-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/helsinki-finland-temperature-mean-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/kampala-uganda-temperature-mean-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/kampala-uganda-temperature-mean-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/key-west-united-states-temperature-max-2023-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/key-west-united-states-temperature-max-2023-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/kyiv-ukraine-temperature-mean-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/kyiv-ukraine-temperature-mean-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/madrid-spain-temperature-mean-2023-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/madrid-spain-temperature-mean-2023-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/monterrey-mexico-temperature-mean-2023-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/monterrey-mexico-temperature-mean-2023-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/mumbai-india-precipitation-cum-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/mumbai-india-precipitation-cum-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/mumbai-india-precipitation-rolling-2021-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/mumbai-india-precipitation-rolling-2021-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/mumbai-india-temperature-mean-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/mumbai-india-temperature-mean-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/pacific-ocean-temperature-mean-2023-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/pacific-ocean-temperature-mean-2023-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/prague-czechia-precipitation-rolling-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/prague-czechia-precipitation-rolling-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/riyadh-saudi-arabia-temperature-mean-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/riyadh-saudi-arabia-temperature-mean-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/rome-italy-temperature-max-2023-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/rome-italy-temperature-max-2023-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/san-francisco-united-states-temperature-mean-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/san-francisco-united-states-temperature-mean-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/seville-spain-precipitation-cum-2023-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/seville-spain-precipitation-cum-2023-ref-1961-1990.png
--------------------------------------------------------------------------------
/examples/sydney-australia-temperature-mean-2022-ref-1961-1990.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/examples/sydney-australia-temperature-mean-2022-ref-1961-1990.png
--------------------------------------------------------------------------------
/header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/header.png
--------------------------------------------------------------------------------
/meteo_hist/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | MeteoHist is a Python package to visualize historical weather data from OpenMeteo API.
3 | """
4 |
5 | from .exceptions import OpenMeteoAPIException, APICallFailed
6 | from .base import MeteoHist
7 | from .interactive import MeteoHistInteractive
8 |
--------------------------------------------------------------------------------
/meteo_hist/exceptions.py:
--------------------------------------------------------------------------------
1 | """
2 | Classes to define exceptions for the meteo_hist package.
3 | """
4 |
5 |
6 | class OpenMeteoAPIException(Exception):
7 | "Raised when an error occurs during data retrieval from OpenMeteo API."
8 |
9 |
10 | class APICallFailed(Exception):
11 | "Raised when an API call fails."
12 |
--------------------------------------------------------------------------------
/meteo_hist/interactive.py:
--------------------------------------------------------------------------------
1 | """
2 | Class to generate an interactive version of the plot using Plotly.
3 | """
4 |
5 | import datetime as dt
6 |
7 | import numpy as np
8 | import pandas as pd
9 | import plotly.graph_objects as go
10 | from plotly.express.colors import sample_colorscale
11 |
12 | from meteo_hist import MeteoHist, OpenMeteoAPIException
13 |
14 |
15 | class MeteoHistInteractive(MeteoHist):
16 | """
17 | Class to create an interactive plot of a year's meteo values compared to historical values.
18 | Inherits from MeteoHist, which does the data processing.
19 | """
20 |
21 | def __init__(
22 | self,
23 | coords: tuple[float, float],
24 | year: int = None,
25 | reference_period: tuple[int, int] = (1961, 1990),
26 | metric: str = "temperature_mean",
27 | settings: dict = None,
28 | data: pd.DataFrame = None,
29 | layout_options: dict = None,
30 | ):
31 | """
32 | Parameters
33 | ----------
34 | coords: tuple of floats
35 | Latitude and longitude of the location.
36 | year: int
37 | Year to plot.
38 | reference_period: tuple of ints
39 | Reference period to compare the data, by default (1961, 1990).
40 | metric: str
41 | Metric to plot. Allowed values: temperature_mean (default), temperature_min,
42 | temperature_max, precipitation_rolling, precipitation_cum.
43 | settings: dict, optional
44 | Settings dictionary, by default None.
45 | data: pd.DataFrame, optional
46 | Dataframe with metric data, by default None.
47 | layout_options: dict, optional
48 | Dictionary with layout options for the plot, by default None.
49 | """
50 | # Call the base class constructor using super()
51 | super().__init__(coords, year, reference_period, metric, settings, data)
52 | self.layout_options = layout_options
53 | self.fig = None
54 |
55 | def get_colorscale(self) -> np.ndarray:
56 | """
57 | Get the colorscale for the plot as a combination of two colormaps.
58 | Values above/below the mean are separately normalized to 0-1 and then
59 | mapped to a colormap.
60 | """
61 | # Get difference between year's value and mean of reference period
62 | diff = self.data[f"{self.year}_diff"].copy().to_numpy()
63 |
64 | # Create masks for above and below mean
65 | mask_above = diff > 0
66 | mask_below = diff < 0
67 |
68 | # Get absolute value of difference
69 | diff = abs(diff)
70 |
71 | # Create array of zeros with same shape as diff
72 | diff_norm = np.zeros_like(diff)
73 |
74 | # Create array of white colors with same shape as diff
75 | colors = np.full_like(diff, "rgb(255, 255, 255)", dtype="object")
76 |
77 | def normalize_and_get_colors(
78 | diff: np.ndarray, diff_norm: np.ndarray, mask: np.ndarray, colormap: str
79 | ) -> np.ndarray:
80 | """
81 | Normalize values and map to a colormap.
82 |
83 | Parameters
84 | ----------
85 | diff: np.ndarray
86 | Array with difference values.
87 | diff_norm: np.ndarray
88 | Array with normalized difference values.
89 | mask: np.ndarray
90 | Mask to apply to the arrays.
91 | colormap: str
92 | Name of the colormap to use.
93 | """
94 | if len(diff[mask]) == 0:
95 | return None
96 |
97 | if len(diff[mask]) == 1:
98 | # Only one value, assign it a default value of 1
99 | diff_norm[mask] = np.array([1])
100 | else:
101 | # Normalize to 0-1
102 | max_val = np.nanmax(diff[mask])
103 | min_val = np.nanmin(diff[mask])
104 | diff_norm[mask] = (diff[mask] - min_val) / (max_val - min_val)
105 |
106 | return sample_colorscale(
107 | self.settings["metric"]["colors"][colormap], diff_norm[mask]
108 | )
109 |
110 | # Apply normalization and get colors for above and below mean
111 | colors[mask_above] = normalize_and_get_colors(
112 | diff, diff_norm, mask_above, "cmap_above"
113 | )
114 | colors[mask_below] = normalize_and_get_colors(
115 | diff, diff_norm, mask_below, "cmap_below"
116 | )
117 |
118 | return colors
119 |
120 | def get_opacity(self) -> np.ndarray:
121 | """
122 | Get the opacity for the plot
123 | """
124 | # Define array of ones with same shape as yearly values
125 | opacity = np.ones_like(self.data[f"{self.year}"])
126 |
127 | if self.settings["peak_alpha"]:
128 | # Create mask for values between p05 and p95
129 | mask_between = (self.data[f"{self.year}"] >= self.data["p05"]) & (
130 | self.data[f"{self.year}"] <= self.data["p95"]
131 | )
132 |
133 | # Set opacity to 0.6 for values between p05 and p95
134 | opacity[mask_between] = 0.6
135 |
136 | return opacity
137 |
138 | def add_alternating_bg(self, fig: go.Figure) -> go.Figure:
139 | """
140 | Add alternating background color for months.
141 |
142 | Parameters
143 | ----------
144 | fig: go.Figure
145 | Plotly figure object.
146 | """
147 |
148 | # Define dict with first and last day of each month, ignoring leap days
149 | months_with_days = {
150 | month: (
151 | dt.datetime(self.year, month, 1),
152 | dt.datetime(
153 | self.year,
154 | month,
155 | 28 if month == 2 else 30 if month in [4, 6, 9, 11] else 31,
156 | ),
157 | )
158 | for month in range(1, 13)
159 | }
160 |
161 | for month, days in months_with_days.items():
162 | # Define background color
163 | bg_color = (
164 | self.settings["alternate_months"]["even_color"]
165 | if (month % 2) == 0
166 | else self.settings["alternate_months"]["odd_color"]
167 | )
168 |
169 | # Define background opacity
170 | bg_opacity = (
171 | self.settings["alternate_months"]["even_alpha"]
172 | if (month % 2) == 0
173 | else self.settings["alternate_months"]["odd_alpha"]
174 | )
175 |
176 | fig.add_shape(
177 | type="rect",
178 | yref="paper",
179 | x0=days[0],
180 | x1=days[1],
181 | y0=0,
182 | y1=1,
183 | fillcolor=bg_color,
184 | opacity=bg_opacity,
185 | layer="below",
186 | line_width=0,
187 | )
188 |
189 | return fig
190 |
191 | def plot_percentile_area(self, fig: go.Figure) -> go.Figure:
192 | """
193 | Add filled area between p05 and p95 to plot.
194 |
195 | Parameters
196 | ----------
197 | fig: go.Figure
198 | Plotly figure object.
199 | """
200 |
201 | fig.add_traces(
202 | [
203 | # p95 trace used as upper bound
204 | go.Scatter(
205 | x=self.data["date"],
206 | y=self.data["p95"],
207 | name="Percentile area upper bound (p95)",
208 | # Make line invisible
209 | line_color="rgba(0,0,0,0)",
210 | showlegend=False,
211 | hoverinfo="skip",
212 | ),
213 | # Fill area between p05 and p95
214 | go.Scatter(
215 | x=self.data["date"],
216 | y=self.data["p05"],
217 | name="Area between p05 and p95",
218 | fill="tonexty",
219 | fillcolor=self.settings["fill_percentiles"],
220 | # Make line invisible
221 | line_color="rgba(0,0,0,0)",
222 | showlegend=False,
223 | hoverinfo="skip",
224 | ),
225 | ]
226 | )
227 |
228 | return fig
229 |
230 | def plot_percentile_lines(self, fig: go.Figure) -> go.Figure:
231 | """
232 | Add percentile lines to plot.
233 |
234 | Parameters
235 | ----------
236 | fig: go.Figure
237 | Plotly figure object.
238 | """
239 |
240 | fig.add_traces(
241 | [
242 | # p95 trace
243 | go.Scatter(
244 | x=self.data["date"],
245 | y=self.data["p95"],
246 | name="P95",
247 | line={"color": "#000", "width": 1, "dash": "dot"},
248 | showlegend=False,
249 | hovertemplate=(
250 | "%{y:.1f}"
251 | f"{super().get_units()}"
252 | f"95th percentile {self.reference_period[0]}-"
253 | f"{self.reference_period[1]}"
254 | ),
255 | ),
256 | # p05 trace
257 | go.Scatter(
258 | x=self.data["date"],
259 | y=self.data["p05"],
260 | name="P05",
261 | line={"color": "#000", "width": 1, "dash": "dot"},
262 | showlegend=False,
263 | hovertemplate=(
264 | "%{y:.1f}"
265 | f"{super().get_units()}"
266 | f"5th percentile {self.reference_period[0]}-"
267 | f"{self.reference_period[1]}"
268 | ),
269 | ),
270 | ]
271 | )
272 |
273 | return fig
274 |
275 | def plot_mean(self, fig: go.Figure) -> go.Figure:
276 | """
277 | Plot the the long-term mean.
278 |
279 | Parameters
280 | ----------
281 | fig: go.Figure
282 | Plotly figure object.
283 | """
284 |
285 | fig.add_trace(
286 | go.Scatter(
287 | x=self.data["date"],
288 | y=self.data["mean"],
289 | name="Mean",
290 | line={"color": "#000", "width": 2.5},
291 | showlegend=False,
292 | hovertemplate=(
293 | "%{y:.1f}"
294 | f"{super().get_units()}"
295 | f"Mean {self.reference_period[0]}-"
296 | f"{self.reference_period[1]}"
297 | ),
298 | ),
299 | )
300 |
301 | return fig
302 |
303 | def plot_diff(self, fig: go.Figure, chart_type: str = "area") -> go.Figure:
304 | """
305 | Plot the difference between the year's value and the long-term mean.
306 |
307 | Parameters
308 | ----------
309 | fig: go.Figure
310 | Plotly figure object.
311 | chart_type: str
312 | Type of chart to display, either "area" or "bar".
313 | """
314 |
315 | opacity = self.get_opacity()
316 | colors = self.get_colorscale()
317 |
318 | # Display a simpler and faster plot if chart_type is "bar"
319 | if chart_type == "bar":
320 | fig.add_trace(
321 | go.Bar(
322 | x=self.data["date"],
323 | y=self.data[f"{self.year}_diff"],
324 | base=self.data["mean"],
325 | name=f"{self.year} value",
326 | marker={"color": colors, "line_width": 0, "opacity": opacity},
327 | showlegend=False,
328 | hovertemplate=("%{y:.1f}" f"{super().get_units()}"),
329 | )
330 | )
331 |
332 | return fig
333 |
334 | # Invisible trace just to show the correct hover info
335 | fig.add_trace(
336 | go.Scatter(
337 | x=self.data["date"],
338 | y=self.data[f"{self.year}"],
339 | showlegend=False,
340 | mode="markers",
341 | name="Hoverinfo current date",
342 | hovertemplate=("%{y:.1f}" f"{super().get_units()}" f""),
343 | marker={
344 | "color": colors, # This color will be shown on hover
345 | "opacity": 0, # Hide the marker
346 | },
347 | )
348 | )
349 |
350 | # For each day, add a filled area between the mean and the year's value
351 | for i in range(len(self.data) - 1):
352 | # Define x and y values to draw a polygon between mean and values of today and tomorrow
353 | date_today = self.data["date"].iloc[i]
354 | date_tomorrow = self.data["date"].iloc[i + 1]
355 | mean_today = self.data["mean"].iloc[i]
356 | mean_tomorrow = self.data["mean"].iloc[i + 1]
357 | value_today = self.data[f"{self.year}"].iloc[i]
358 | value_tomorrow = self.data[f"{self.year}"].iloc[i + 1]
359 |
360 | # If one day is above and the other below the mean, set the value to the mean
361 | if (value_today > mean_today) ^ (value_tomorrow > mean_tomorrow):
362 | value_tomorrow = mean_tomorrow
363 |
364 | fig.add_trace(
365 | go.Scatter(
366 | name=f"Daily value {self.data['date'].iloc[i].strftime('%d.%m.%Y')}",
367 | x=[date_today, date_today, date_tomorrow, date_tomorrow],
368 | y=[mean_today, value_today, value_tomorrow, mean_tomorrow],
369 | line_width=0,
370 | fill="toself",
371 | fillcolor=colors[i],
372 | showlegend=False,
373 | mode="lines",
374 | opacity=opacity[i],
375 | hoverinfo="skip",
376 | )
377 | )
378 |
379 | return fig
380 |
381 | def annotate_peaks(self, fig: go.Figure, how: str = "max") -> go.Figure:
382 | """
383 | Annotate maximum or minimum values.
384 |
385 | Parameters
386 | ----------
387 | fig: go.Figure
388 | Plotly figure object.
389 | how: str
390 | Annotation type, either "max" or "min".
391 | """
392 | if how not in ["max", "min"]:
393 | return fig
394 |
395 | conf_options = {
396 | "max": {
397 | "text": "Maximum",
398 | "setting": "highlight_max",
399 | "ref": "p95",
400 | "asc": False,
401 | "prefix": "+",
402 | "yanchor": "bottom",
403 | "yshift": 10,
404 | },
405 | "min": {
406 | "text": "Minimum",
407 | "setting": "highlight_min",
408 | "ref": "p05",
409 | "asc": True,
410 | "prefix": "",
411 | "yanchor": "top",
412 | "yshift": -10,
413 | },
414 | }
415 | conf = conf_options[how]
416 |
417 | # Create a copy of the dataframe to sort
418 | df_sorted = self.data.copy()
419 |
420 | if self.settings["peak_method"] != "percentile":
421 | # By default, sort by difference between year's value and mean
422 | sort_column = f"{self.year}_diff"
423 | else:
424 | # If peak method is percentile, sort by difference between year's value and p95/p05
425 | sort_column = f"{self.year}_diff_minmax"
426 | df_sorted[sort_column] = df_sorted[f"{self.year}"] - df_sorted[conf["ref"]]
427 |
428 | df_sorted = df_sorted.sort_values(sort_column, ascending=conf["asc"])
429 |
430 | # Remove values that are too close together (min_distance)
431 | for i in range(self.settings[conf["setting"]]):
432 | current = df_sorted["dayofyear"].iloc[i]
433 | min_distance = self.settings["peak_distance"]
434 | range_around_current = [
435 | day
436 | for day in range(current - min_distance, current + min_distance + 1)
437 | if day != current
438 | ]
439 | # Filter out values that are too close to the current one
440 | df_sorted = df_sorted[~df_sorted["dayofyear"].isin(range_around_current)]
441 |
442 | for i in range(self.settings[conf["setting"]]):
443 | # Add text
444 | fig.add_annotation(
445 | x=df_sorted["date"].iloc[i],
446 | y=df_sorted[f"{self.year}"].iloc[i],
447 | text=(
448 | f"{conf['prefix']}{df_sorted[f'{self.year}_diff'].values[i]:.1f}"
449 | f"{super().get_units()}"
450 | ),
451 | showarrow=False,
452 | xanchor="center",
453 | yanchor=conf["yanchor"],
454 | yshift=conf["yshift"],
455 | )
456 | # Add circles
457 | fig.add_trace(
458 | go.Scatter(
459 | x=[df_sorted["date"].iloc[i]],
460 | y=[df_sorted[f"{self.year}"].iloc[i]],
461 | mode="markers",
462 | name=f"{conf['text']} {i+1}",
463 | marker={
464 | "color": "rgba(255,255,255,0)",
465 | "size": 10,
466 | "line": {"color": "#000", "width": 1},
467 | },
468 | showlegend=False,
469 | hoverinfo="skip",
470 | )
471 | )
472 |
473 | return fig
474 |
475 | def add_annotations(self, fig: go.Figure) -> go.Figure:
476 | """
477 | Add annotations to the plot to explain the data.
478 |
479 | Parameters
480 | ----------
481 | fig: go.Figure
482 | Plotly figure object.
483 | """
484 | y_min, y_max = self.get_y_limits()
485 |
486 | # Annotations for the mean line
487 |
488 | if self.settings["metric"]["name"] == "precipitation_cum":
489 | # Position arrow on the mean line in mid April
490 | arrow_x = dt.datetime.strptime(f"{self.year}-04-15", "%Y-%m-%d")
491 | arrow_y = self.data[self.data["date"] == arrow_x]["mean"].values[0]
492 |
493 | # Position text center mid March at 1/6 of the distance
494 | # between maximum value for February to April and y axis maximum
495 | text_x = dt.datetime.strptime(f"{self.year}-03-15", "%Y-%m-%d")
496 | max_value = super().get_min_max((46, 105))
497 | text_y = max_value + (y_max - max_value) / 6
498 |
499 | elif self.settings["metric"]["name"] == "precipitation_rolling":
500 | # Position arrow on the mean line in mid March
501 | arrow_x = dt.datetime.strptime(f"{self.year}-03-15", "%Y-%m-%d")
502 | arrow_y = self.data[self.data["date"] == arrow_x]["mean"].values[0]
503 |
504 | # Position text center in February at 1/6 of the distance
505 | # between maximum value for January to February and y axis maximum
506 | text_x = dt.datetime.strptime(f"{self.year}-02-01", "%Y-%m-%d")
507 | max_value = super().get_min_max((1, 90))
508 | text_y = max_value + (y_max - max_value) / 6
509 |
510 | else:
511 | # Position arrow on the mean line in March
512 | arrow_x = dt.datetime.strptime(f"{self.year}-03-15", "%Y-%m-%d")
513 | arrow_y = self.data[self.data["date"] == arrow_x]["mean"].values[0]
514 |
515 | # Position text center in mid April at 1/3 of the distance
516 | # between minimum value for March to May and y axis minimum
517 | text_x = dt.datetime.strptime(f"{self.year}-04-15", "%Y-%m-%d")
518 | min_value = super().get_min_max((74, 135), which="min")
519 | text_y = min_value - (min_value - y_min) / 3
520 |
521 | fig.add_annotation(
522 | x=arrow_x,
523 | y=arrow_y,
524 | xref="x",
525 | yref="y",
526 | ax=text_x,
527 | ay=text_y,
528 | axref="x",
529 | ayref="y",
530 | text=(
531 | f"{self.settings['metric']['description']}
"
532 | f"{self.reference_period[0]}-{self.reference_period[1]}"
533 | ),
534 | showarrow=True,
535 | xanchor="center",
536 | yanchor="middle",
537 | arrowwidth=2,
538 | arrowcolor="#000",
539 | name="Reference period mean",
540 | )
541 |
542 | # Annotations for the area between p05 and p95
543 |
544 | if self.settings["metric"]["name"] == "precipitation_cum":
545 | # Position arrow 1/6 into the p05/p95 area in mid September
546 | arrow_x = dt.datetime.strptime(f"{self.year}-09-15", "%Y-%m-%d")
547 | idx = self.data[self.data["date"] == arrow_x].index[0]
548 | mean, p05 = self.data.iloc[idx]["mean"], self.data.iloc[idx]["p05"]
549 | arrow_y = p05 + (mean - p05) / 6
550 |
551 | # Position text center mid October at 1/3 of the distance
552 | # between minimum value for September to November and y axis minimum
553 | text_x = dt.datetime.strptime(f"{self.year}-10-15", "%Y-%m-%d")
554 | min_value = super().get_min_max((258, 319), which="min")
555 | text_y = min_value - (min_value - y_min) / 3
556 |
557 | elif self.settings["metric"]["name"] == "precipitation_rolling":
558 | # Position arrow 1/6 into the p05/p95 area in mid September
559 | arrow_x = dt.datetime.strptime(f"{self.year}-09-15", "%Y-%m-%d")
560 | idx = self.data[self.data["date"] == arrow_x].index[0]
561 | mean, p95 = self.data.iloc[idx]["mean"], self.data.iloc[idx]["p95"]
562 | arrow_y = p95 - (p95 - mean) / 6
563 |
564 | # Position text center mid October at 1/3 of the distance
565 | # between maximum value for September to November and y axis maximum
566 | text_x = dt.datetime.strptime(f"{self.year}-10-15", "%Y-%m-%d")
567 | max_value = super().get_min_max((258, 319))
568 | text_y = max_value + (y_max - max_value) / 3
569 |
570 | else:
571 | # Position arrow 1/6 into the p05/p95 area in mid October
572 | arrow_x = dt.datetime.strptime(f"{self.year}-10-15", "%Y-%m-%d")
573 | idx = self.data[self.data["date"] == arrow_x].index[0]
574 | mean, p05 = self.data.iloc[idx]["mean"], self.data.iloc[idx]["p05"]
575 | arrow_y = p05 + (mean - p05) / 6
576 |
577 | # Position text center mid September at 1/3 of the distance
578 | # between minimum value for August to October and y axis minimum
579 | text_x = dt.datetime.strptime(f"{self.year}-09-15", "%Y-%m-%d")
580 | min_value = super().get_min_max((227, 288), which="min")
581 | text_y = min_value - (min_value - y_min) / 3
582 |
583 | fig.add_annotation(
584 | x=arrow_x,
585 | y=arrow_y,
586 | xref="x",
587 | yref="y",
588 | ax=text_x,
589 | ay=text_y,
590 | axref="x",
591 | ayref="y",
592 | text="90% of reference period
values fall within the gray area",
593 | showarrow=True,
594 | xanchor="center",
595 | yanchor="middle",
596 | arrowwidth=2,
597 | arrowcolor="#000",
598 | name="Reference period mean",
599 | )
600 |
601 | # Annotations for percentile lines
602 | for percentile in ["p05", "p95"]:
603 | fig.add_annotation(
604 | x=self.data["date"].iloc[-1],
605 | y=self.data[percentile].iloc[-1],
606 | text=percentile.upper(),
607 | showarrow=False,
608 | xanchor="left",
609 | yanchor="middle",
610 | )
611 |
612 | return fig
613 |
614 | def add_data_source(self, fig: go.Figure) -> go.Figure:
615 | """
616 | Add data source to the plot.
617 |
618 | Parameters
619 | ----------
620 | fig: go.Figure
621 | Plotly figure object.
622 | """
623 | fig.add_annotation(
624 | xref="paper",
625 | yref="paper",
626 | name="Data source",
627 | x=1,
628 | y=-0.14,
629 | xanchor="right",
630 | showarrow=False,
631 | text="Data: open-meteo.com, OSM, "
632 | "License: CC by-sa-nc 4.0 "
633 | "Graph: Jan Kühn, https://yotka.org",
634 | opacity=0.5,
635 | font_size=12,
636 | )
637 |
638 | return fig
639 |
640 | def add_data_info(self, fig: go.Figure) -> go.Figure:
641 | """
642 | Add coordinates and last avalable date to the plot.
643 |
644 | Parameters
645 | ----------
646 | fig: go.Figure
647 | Plotly figure object.
648 | """
649 | if self.coords[0] is None or self.coords[1] is None:
650 | return fig
651 |
652 | last_date_text = (
653 | f" (last date included: {self.last_date})"
654 | if self.year == dt.datetime.now().year
655 | else ""
656 | )
657 |
658 | fig.add_annotation(
659 | xref="paper",
660 | yref="paper",
661 | name="Data info",
662 | x=0,
663 | y=-0.14,
664 | xanchor="left",
665 | showarrow=False,
666 | text=f"lat: {self.coords[0]}, lon: {self.coords[1]}{last_date_text}",
667 | opacity=0.5,
668 | font_size=12,
669 | )
670 |
671 | return fig
672 |
673 | def layout(self, fig: go.Figure) -> go.Figure:
674 | """
675 | Update layout options.
676 |
677 | Parameters
678 | ----------
679 | fig: go.Figure
680 | Plotly figure object.
681 | """
682 |
683 | fig.update_layout(
684 | title={
685 | "text": (
686 | f"{self.settings['metric']['title']} in {self.settings['location_name']} "
687 | f"{self.year}
{self.settings['metric']['subtitle']} "
688 | f"({self.reference_period[0]}-{self.reference_period[1]})"
689 | ),
690 | "font": {"family": "Lato, Arial, sans-serif", "size": 32, "color": "#1f1f1f"},
691 | "x": 0.98,
692 | "y": 0.93,
693 | "xanchor": "right",
694 | "yanchor": "top",
695 | },
696 | template="plotly_white",
697 | paper_bgcolor="#fff",
698 | plot_bgcolor="#fff",
699 | margin={"b": 70, "l": 60, "r": 20, "t": 100, "pad": 10},
700 | hovermode="x",
701 | bargap=0,
702 | width=1000,
703 | height=600,
704 | font={"family": "Lato, Arial, sans-serif", "size": 14, "color": "#1f1f1f"},
705 | xaxis={
706 | "dtick": "M1", # Tick every month
707 | "hoverformat": "%e %B",
708 | "range": [f"{self.year-1}-12-20", f"{self.year+1}-01-10"],
709 | "showgrid": False,
710 | "tickformat": "%b", # Month name
711 | "ticklabelmode": "period", # Center tick labels
712 | },
713 | yaxis={
714 | "range": super().get_y_limits(),
715 | "showgrid": True,
716 | "ticksuffix": super().get_units(),
717 | },
718 | )
719 |
720 | # Update layout with user defined options
721 | if isinstance(self.layout_options, dict):
722 | fig.update_layout(self.layout_options)
723 |
724 | return fig
725 |
726 | def create_plot(self) -> tuple[go.Figure, str]:
727 | """
728 | Creates the plot.
729 | """
730 |
731 | # Create a new Figure object
732 | fig = go.Figure()
733 |
734 | # Plot percentile area
735 | fig = self.plot_percentile_area(fig)
736 |
737 | # Plot daily values
738 | fig = self.plot_diff(fig, chart_type="area")
739 |
740 | # Plot percentile lines
741 | fig = self.plot_percentile_lines(fig)
742 |
743 | # Plot the historical value for each day of the year
744 | fig = self.plot_mean(fig)
745 |
746 | # Add alternating background colors
747 | if self.settings["alternate_months"]["apply"]:
748 | fig = self.add_alternating_bg(fig)
749 |
750 | # Add annotations to explain the data
751 | fig = self.add_annotations(fig)
752 |
753 | # Annotate maximum values
754 | if self.settings["highlight_max"] > 0:
755 | fig = self.annotate_peaks(fig, how="max")
756 |
757 | # Annotate minimum values
758 | if self.settings["highlight_min"] > 0:
759 | fig = self.annotate_peaks(fig, how="min")
760 |
761 | # Add lat/lon and last date info
762 | fig = self.add_data_info(fig)
763 |
764 | # Data source and attribution
765 | fig = self.add_data_source(fig)
766 |
767 | # Update layout
768 | fig = self.layout(fig)
769 |
770 | # Save figure object as class attribute
771 | self.fig = fig
772 |
773 | # Save the plot to a file if requested
774 | file_path = self.save_plot_to_file() if self.settings["save_file"] else None
775 |
776 | return fig, file_path
777 |
778 | def save_plot_to_file(self) -> None:
779 | """
780 | Save the plot to a file.
781 | """
782 | file_path = super().create_file_path()
783 |
784 | if not isinstance(self.fig, go.Figure):
785 | self.fig = self.create_plot()[0]
786 |
787 | # Save the plot
788 | self.fig.write_image(
789 | file_path,
790 | width=1000,
791 | height=600,
792 | scale=2,
793 | )
794 |
795 | # Clean up the output directory
796 | super().clean_output_dir()
797 |
798 | return file_path
799 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | extra_streamlit_components==0.1.71
2 | folium==0.16.0
3 | geopy==2.4.1
4 | kaleido==0.2.1
5 | nbformat==5.10.4
6 | numpy==2.1.1
7 | pandas==2.2.2
8 | plotly==5.22.0
9 | pydantic==2.9.1
10 | pytest==8.2.2
11 | python-dotenv==1.0.1
12 | Requests==2.32.3
13 | statsmodels==0.14.2
14 | streamlit==1.41.1
15 | streamlit_js_eval==0.1.5
16 | tenacity==8.3.0
17 | Unidecode==1.3.8
18 |
--------------------------------------------------------------------------------
/sample_data/transformed/temperature_max-caracas-venezuela_transformed.csv:
--------------------------------------------------------------------------------
1 | dayofyear,min,p05,p40,mean,p60,p95,max,2024,2024_diff,date
2 | 1,21.9,23.073110572669627,24.96,24.984310193760177,25.68,26.842898325838586,27.5,24.2,-0.7843101937601773,2024-01-01
3 | 2,22.5,23.07651143996058,24.560000000000002,25.014809101408574,25.7,26.919036629061342,27.7,25.2,0.18519089859142568,2024-01-02
4 | 3,22.8,23.079863253315967,25.0,25.045574680862906,25.48,26.99497943611871,27.0,26.2,1.1544253191370935,2024-01-03
5 | 4,22.5,23.083267209466527,24.8,25.076577852347757,25.54,27.07058135207473,27.1,27.6,2.5234221476522443,2024-01-04
6 | 5,22.7,23.0868189918814,24.560000000000002,25.10777010018597,25.4,27.145740409891207,27.8,25.0,-0.10777010018597011,2024-01-05
7 | 6,21.7,23.09057655673206,24.7,25.13908063027652,25.34,27.220415243840442,27.2,25.1,-0.0390806302765192,2024-01-06
8 | 7,23.4,23.09455951793291,24.560000000000002,25.170427196101997,25.34,27.29458852368138,27.7,25.6,0.42957280389800445,2024-01-07
9 | 8,22.4,23.098751914884858,24.46,25.2017396957097,25.46,27.368279671900055,28.0,26.5,1.2982603042902987,2024-01-08
10 | 9,22.2,23.103059355407535,24.720000000000002,25.23294761038461,25.4,27.441505258644,27.7,28.6,3.3670523896153917,2024-01-09
11 | 10,22.4,23.107350921801117,24.76,25.264001422548947,25.4,27.51430670984422,28.4,27.2,1.935998577451052,2024-01-10
12 | 11,23.6,23.111496164117515,25.0,25.29489702021437,25.5,27.58675503306733,27.5,26.1,0.8051029797856302,2024-01-11
13 | 12,22.6,23.11527044784649,25.2,25.325705732749043,25.86,27.65897078731096,28.2,26.9,1.5742942672509557,2024-01-12
14 | 13,22.5,23.118199394854955,25.02,25.356577468994708,25.34,27.731207721251895,28.6,26.3,0.943422531005293,2024-01-13
15 | 14,22.1,23.119543925628534,25.2,25.387851039762936,25.54,27.803862114035855,28.4,25.9,0.512148960237063,2024-01-14
16 | 15,22.3,23.11835970331239,24.8,25.420460807881437,25.82,27.877087196947986,28.3,26.5,1.0795391921185633,2024-01-15
17 | 16,23.1,23.11926102711754,25.2,25.454083676060208,25.74,27.944932147809904,28.3,26.7,1.2459163239397917,2024-01-16
18 | 17,23.2,23.12282873294594,24.8,25.487145178723658,25.72,28.009710632746398,28.9,28.3,2.8128548212763427,2024-01-17
19 | 18,22.4,23.129970773991378,24.76,25.518788159848953,25.599999999999998,28.068225258715305,28.8,28.6,3.081211840151049,2024-01-18
20 | 19,22.7,23.140124676352123,25.400000000000002,25.548401374271236,26.04,28.117836564934905,28.5,29.0,3.4515986257287636,2024-01-19
21 | 20,23.0,23.152425523146206,25.220000000000002,25.575497081168493,25.98,28.157087455063166,28.4,28.6,3.0245029188315087,2024-01-20
22 | 21,23.0,23.16625769361779,25.220000000000002,25.59953229041154,26.439999999999998,28.185536467468477,29.0,28.5,2.9004677095884617,2024-01-21
23 | 22,22.0,23.180562980751198,24.86,25.620153538962644,25.72,28.203928757557964,28.6,27.8,2.179846461037357,2024-01-22
24 | 23,22.2,23.194050918678034,24.96,25.63723084252973,25.52,28.213354426142725,28.8,28.7,3.062769157470271,2024-01-23
25 | 24,22.1,23.206941428739864,25.26,25.651499076572314,25.76,28.215877233898553,29.1,27.7,2.048500923427685,2024-01-24
26 | 25,22.7,23.219868383988196,24.9,25.664608806359226,26.2,28.21544395874516,29.7,28.1,2.4353911936407755,2024-01-25
27 | 26,22.3,23.232610503601702,25.54,25.67846923235289,26.48,28.217157134090723,29.7,27.8,2.121530767647112,2024-01-26
28 | 27,22.9,23.244256875505503,25.78,25.694454846344,26.34,28.225083829489908,30.6,27.4,1.7055451536559971,2024-01-27
29 | 28,22.9,23.255195708708108,25.42,25.713349114339607,26.04,28.24134331293514,29.2,27.8,2.0866508856603936,2024-01-28
30 | 29,22.4,23.265572579692,24.86,25.735255132415094,26.06,28.26557808457406,29.7,28.2,2.464744867584905,2024-01-29
31 | 30,23.0,23.275054005213445,24.9,25.75949472142536,25.92,28.29497225396036,29.6,27.5,1.7405052785746413,2024-01-30
32 | 31,23.1,23.283332688456767,25.4,25.785501288971638,25.939999999999998,28.325598961868206,29.2,26.7,0.9144987110283616,2024-01-31
33 | 32,21.8,23.290117433559615,25.1,25.812892167775075,25.88,28.354902636038723,28.2,27.5,1.6871078322249247,2024-02-01
34 | 33,23.0,23.29556548124125,24.92,25.84181739179244,25.8,28.38303767710625,28.0,26.2,0.3581826082075601,2024-02-02
35 | 34,23.0,23.30069743551201,25.46,25.872508785846566,25.779999999999998,28.411361959993997,29.9,27.6,1.727491214153435,2024-02-03
36 | 35,22.7,23.305974858652043,25.1,25.90477216917578,26.1,28.442227093479023,27.7,26.5,0.5952278308242214,2024-02-04
37 | 36,22.8,23.31202066367674,25.26,25.93869289555351,26.26,28.477430972031588,28.7,30.3,4.361307104446492,2024-02-05
38 | 37,22.4,23.320408125100972,25.060000000000002,25.974810682338692,26.5,28.519069232831068,29.4,28.8,2.8251893176613088,2024-02-06
39 | 38,22.8,23.333447650410893,25.7,26.014678675376768,26.4,28.57009557114894,29.9,26.5,0.4853213246232322,2024-02-07
40 | 39,21.8,23.352832783535842,25.62,26.060082867188957,26.64,28.632467644382547,29.5,22.0,-4.060082867188957,2024-02-08
41 | 40,21.8,23.3789961395202,26.0,26.111546881628573,26.6,28.705201965680292,29.6,20.3,-5.811546881628573,2024-02-09
42 | 41,22.3,23.411470762981192,26.060000000000002,26.168032577960236,26.82,28.78589453344587,29.8,25.6,-0.5680325779602349,2024-02-10
43 | 42,20.9,23.448428427943195,26.060000000000002,26.227009705808303,27.08,28.872125468323397,29.1,26.3,0.07299029419169756,2024-02-11
44 | 43,21.5,23.487946409881697,25.66,26.285205120788156,26.74,28.960015661208814,29.2,27.1,0.8147948792118456,2024-02-12
45 | 44,22.2,23.528128093093287,26.26,26.3400627193887,26.939999999999998,29.044693943579645,29.7,28.2,1.8599372806112981,2024-02-13
46 | 45,22.8,23.568304607855012,25.86,26.390645125751895,26.54,29.121584857097965,30.9,28.0,1.6093548742481047,2024-02-14
47 | 46,22.3,23.60923251794104,25.92,26.437627713913443,26.58,29.188585365665872,29.8,29.6,3.1623722860865584,2024-02-15
48 | 47,22.6,23.652619165394185,25.9,26.482329344210395,27.0,29.245935659808907,29.6,30.2,3.717670655789604,2024-02-16
49 | 48,23.1,23.700263584253808,26.66,26.525688972319962,27.52,29.294955223398187,30.7,29.9,3.3743110276800365,2024-02-17
50 | 49,23.7,23.7535104990142,26.62,26.568064589610128,27.54,29.3374487078909,29.6,30.7,4.131935410389872,2024-02-18
51 | 50,24.0,23.812872929779278,26.36,26.60964247149857,27.0,29.375290806094778,30.3,29.4,2.7903575285014277,2024-02-19
52 | 51,24.2,23.87744515049705,26.6,26.65067329917456,27.4,29.410619055087338,30.2,28.0,1.34932670082544,2024-02-20
53 | 52,24.5,23.945941254431755,26.48,26.691584405947836,27.24,29.445123067970794,30.3,24.9,-1.7915844059478374,2024-02-21
54 | 53,23.6,24.01677361144515,26.52,26.732878861336903,27.04,29.480262254275843,31.4,25.1,-1.632878861336902,2024-02-22
55 | 54,22.9,24.088022697192304,26.66,26.774553970507974,27.8,29.516196171874594,30.4,24.7,-2.0745539705079743,2024-02-23
56 | 55,23.4,24.156498333732653,26.02,26.815785420896866,27.279999999999998,29.551741740871606,29.5,26.5,-0.3157854208968658,2024-02-24
57 | 56,21.5,24.219162558133622,26.42,26.85549432801737,27.439999999999998,29.585263316641562,29.4,25.6,-1.2554943280173703,2024-02-25
58 | 57,23.2,24.273435546327956,26.46,26.89266142524444,26.84,29.615894519673056,30.4,25.9,-0.9926614252444423,2024-02-26
59 | 58,23.6,24.318644038845893,26.36,26.927298598090857,27.1,29.64416983384933,29.7,25.1,-1.8272985980908558,2024-02-27
60 | 59,23.8,24.356197847558654,26.26,26.96069587216252,27.42,29.671317368968353,30.9,25.1,-1.8606958721625197,2024-02-28
61 | 60,24.8,24.388313951243596,27.0,26.99463742768154,27.34,29.698877062398218,30.0,24.2,-2.7946374276815398,2024-03-01
62 | 61,23.3,24.417390732257005,26.7,27.030714046186173,27.52,29.72800203917167,31.1,23.8,-3.230714046186172,2024-03-02
63 | 62,21.2,24.44567021221126,26.5,27.069280978983773,27.5,29.760123774973945,30.2,24.6,-2.4692809789837717,2024-03-03
64 | 63,22.7,24.47532146939781,26.74,27.109549606302846,27.42,29.796869365127677,30.3,26.0,-1.1095496063028456,2024-03-04
65 | 64,23.1,24.50710447805628,26.82,27.15038937559729,27.3,29.83955077427944,30.3,27.3,0.14961062440271178,2024-03-05
66 | 65,22.2,24.54023720653123,26.9,27.190930768247615,27.58,29.88905155026167,29.8,28.5,1.3090692317523853,2024-03-06
67 | 66,22.3,24.573585645134784,26.720000000000002,27.231184358291944,27.34,29.94515159326948,30.6,29.7,2.468815641708055,2024-03-07
68 | 67,23.3,24.605239838621777,26.66,27.271466798211115,27.36,30.00721962415066,30.1,30.3,3.0285332017888855,2024-03-08
69 | 68,23.8,24.63281053217813,26.86,27.311955121201056,27.58,30.074131139242365,31.0,28.9,1.588044878798943,2024-03-09
70 | 69,23.3,24.654910987010734,27.04,27.35276903965566,27.58,30.144701261743535,31.6,29.9,2.54723096034434,2024-03-10
71 | 70,24.2,24.67180072222972,27.560000000000002,27.39421926644078,28.08,30.217641965232318,31.2,29.0,1.6057807335592216,2024-03-11
72 | 71,22.1,24.6850225065572,27.48,27.436742651227018,28.24,30.29161503715877,30.1,28.9,1.4632573487729807,2024-03-12
73 | 72,23.4,24.696297022562568,27.560000000000002,27.480039606839046,27.98,30.36464593646215,30.3,29.0,1.5199603931609538,2024-03-13
74 | 73,24.1,24.706677278318715,27.14,27.523255318388834,28.04,30.43486587676403,31.4,28.8,1.2767446816111665,2024-03-14
75 | 74,23.9,24.71579026625192,26.9,27.565094138788236,27.7,30.500807237518337,31.2,29.4,1.8349058612117624,2024-03-15
76 | 75,24.0,24.722364885237017,27.220000000000002,27.60413728812062,27.74,30.562274973289565,31.7,28.5,0.8958627118793814,2024-03-16
77 | 76,24.6,24.726303568056395,26.4,27.639855893913673,27.22,30.62030226744067,31.9,29.9,2.260144106086326,2024-03-17
78 | 77,23.8,24.729117709411703,26.8,27.672706192579252,28.1,30.675743730740884,30.9,31.8,4.127293807420749,2024-03-18
79 | 78,24.4,24.731096515648865,26.96,27.702911047940773,27.98,30.728418509288417,31.8,31.8,4.097088952059227,2024-03-19
80 | 79,23.5,24.73230839965597,26.96,27.730588049097715,28.7,30.777608511258165,32.1,31.4,3.6694119509022833,2024-03-20
81 | 80,24.1,24.733859798834384,27.76,27.755994843094243,28.62,30.822092936748334,31.4,27.9,0.14400515690575588,2024-03-21
82 | 81,23.9,24.73629567156351,27.46,27.779544374288182,28.8,30.861078317274693,31.6,28.2,0.42045562571181705,2024-03-22
83 | 82,23.7,24.740051352850116,27.66,27.802066194715664,29.34,30.893384726451508,31.3,28.6,0.7979338052843374,2024-03-23
84 | 83,24.0,24.745608860947875,28.400000000000002,27.824612762901292,29.14,30.918770999668862,32.0,29.2,1.3753872370987068,2024-03-24
85 | 84,24.2,24.753433649030832,28.24,27.847965364078163,28.74,30.937898704535215,31.4,28.3,0.45203463592183724,2024-03-25
86 | 85,24.1,24.76371441934081,27.1,27.87191451168406,27.979999999999997,30.95217951170962,32.0,27.7,-0.17191451168406147,2024-03-26
87 | 86,23.2,24.77688113828918,27.560000000000002,27.895292285105164,28.84,30.96310792050758,31.8,27.2,-0.6952922851051646,2024-03-27
88 | 87,24.0,24.79335223561162,27.46,27.916770632567506,28.68,30.97156631353286,32.0,26.9,-1.016770632567507,2024-03-28
89 | 88,23.2,24.812356563116385,28.0,27.935092611966038,28.439999999999998,30.97745583090191,31.9,25.7,-2.2350926119660386,2024-03-29
90 | 89,24.3,24.832242167431083,27.46,27.949668825068933,28.14,30.980615989030355,32.6,26.5,-1.4496688250689331,2024-03-30
91 | 90,24.6,24.85114550242294,27.86,27.960930686482303,28.34,30.981115661316515,33.1,26.1,-1.8609306864823019,2024-03-31
92 | 91,22.6,24.86756659485323,27.220000000000002,27.969707129398653,28.54,30.97943038286667,32.9,25.6,-2.369707129398652,2024-04-01
93 | 92,24.0,24.88122054082553,27.42,27.977302024878203,28.779999999999998,30.976672963364436,31.1,25.7,-2.277302024878203,2024-04-02
94 | 93,22.0,24.893954212600836,27.32,27.98524268280798,28.62,30.973507946952193,30.9,26.4,-1.58524268280798,2024-04-03
95 | 94,22.7,24.906390215369395,27.82,27.994356375769616,28.7,30.970466726038833,31.7,29.3,1.3056436242303846,2024-04-04
96 | 95,24.4,24.91803835505515,27.36,28.00421312147358,28.62,30.96787128243041,31.1,29.3,1.295786878526421,2024-04-05
97 | 96,25.5,24.928823998662697,27.66,28.014258850308945,28.639999999999997,30.96598488188875,31.4,30.4,2.3857411496910537,2024-04-06
98 | 97,24.1,24.9378660559413,27.76,28.023649188477243,28.34,30.964155913170092,31.9,30.9,2.876350811522755,2024-04-07
99 | 98,24.7,24.943670810009046,27.64,28.03157703939457,28.38,30.961473445083037,32.6,31.5,3.46842296060543,2024-04-08
100 | 99,25.1,24.943577218858252,27.48,28.036799402851706,28.14,30.95668011403702,31.9,29.7,1.6632005971482933,2024-04-09
101 | 100,25.1,24.935104477404657,27.54,28.038097571946384,28.2,30.9487679798347,31.8,28.7,0.6619024280536152,2024-04-10
102 | 101,24.6,24.917259817343034,27.720000000000002,28.035123618273364,28.56,30.937364521504044,31.9,28.4,0.36487638172663495,2024-04-11
103 | 102,24.6,24.889630814010644,28.02,28.027321311697815,28.68,30.921764253612256,31.1,28.6,0.5726786883021866,2024-04-12
104 | 103,23.7,24.853366523221048,27.86,28.014436495188953,28.58,30.902371655128363,31.2,28.8,0.7855635048110479,2024-04-13
105 | 104,24.1,24.810221900056177,27.6,27.996571797025535,28.439999999999998,30.878998396813568,31.2,26.8,-1.196571797025534,2024-04-14
106 | 105,24.0,24.763106595513328,27.82,27.97399619678712,28.88,30.85037046957663,32.4,27.9,-0.073996196787121,2024-04-15
107 | 106,24.3,24.714239789747793,27.42,27.947177595804455,28.88,30.81530019181313,32.2,30.2,2.2528224041955447,2024-04-16
108 | 107,24.0,24.6650157690241,27.96,27.916997183697443,28.8,30.773770573543246,32.4,27.6,-0.3169971836974419,2024-04-17
109 | 108,21.4,24.616636742301477,27.46,27.88374509091748,28.18,30.726612673545876,31.7,27.0,-0.8837450909174791,2024-04-18
110 | 109,23.7,24.57028356819205,27.88,27.8474557960714,29.02,30.67546666775027,32.2,26.0,-1.8474557960714009,2024-04-19
111 | 110,24.1,24.525620188278804,27.38,27.80839546128174,28.22,30.622477600421767,31.1,25.4,-2.408395461281742,2024-04-20
112 | 111,24.5,24.481704522636996,27.4,27.767321553460913,28.939999999999998,30.5693872665292,31.2,26.3,-1.4673215534609128,2024-04-21
113 | 112,22.0,24.44008745923872,27.32,27.72500958227325,28.46,30.517316556749954,30.6,28.2,0.474990417726751,2024-04-22
114 | 113,22.6,24.403375949704753,27.46,27.682213265984846,28.02,30.466595378328595,30.5,27.1,-0.5822132659848442,2024-04-23
115 | 114,21.8,24.372628771975037,26.76,27.639372534575802,28.0,30.417605556885583,30.6,27.9,0.26062746542419646,2024-04-24
116 | 115,22.5,24.347979689071757,27.2,27.597106323991706,28.04,30.370875280441687,30.8,27.3,-0.2971063239917058,2024-04-25
117 | 116,24.9,24.32849572974068,27.68,27.556260122089466,28.04,30.32693223658552,31.2,27.2,-0.35626012208946634,2024-04-26
118 | 117,23.6,24.31452861895233,27.16,27.517823911351712,28.24,30.28634587994716,31.3,26.2,-1.317823911351713,2024-04-27
119 | 118,24.7,24.307525712678572,27.16,27.482312889697806,27.76,30.249177863260403,30.9,24.9,-2.582312889697807,2024-04-28
120 | 119,21.5,24.30910167360922,27.16,27.449953698277263,28.08,30.21388509343265,30.4,25.6,-1.849953698277261,2024-04-29
121 | 120,21.4,24.318026131843897,26.82,27.420297910494202,28.08,30.178607201675135,30.2,26.2,-1.220297910494203,2024-04-30
122 | 121,23.4,24.332197920573602,27.02,27.392424139125144,27.66,30.14198204734521,30.2,29.2,1.8075758608748558,2024-05-01
123 | 122,23.8,24.351192452553917,26.6,27.365608870797445,27.48,30.10366577364226,30.3,28.6,1.2343911292025567,2024-05-02
124 | 123,23.0,24.37482757244883,26.36,27.339308096252353,27.96,30.063222817512695,30.8,28.9,1.5606919037476459,2024-05-03
125 | 124,22.8,24.401606342212634,27.0,27.313336377044322,28.08,30.020060485216852,31.5,28.7,1.3866636229556768,2024-05-04
126 | 125,24.1,24.43015983979666,26.62,27.28774807658707,27.6,29.973559033811163,30.3,27.7,0.4122519234129278,2024-05-05
127 | 126,23.9,24.459286818385742,26.82,27.26259946675954,27.54,29.924020696172335,30.9,27.3,0.03740053324046144,2024-05-06
128 | 127,24.9,24.487886339695464,27.060000000000002,27.237675232848233,27.939999999999998,29.87266017036199,30.4,27.8,0.5623247671517682,2024-05-07
129 | 128,23.6,24.515312653101475,27.1,27.213313562403602,27.96,29.821618549469754,30.5,28.5,1.2866864375963978,2024-05-08
130 | 129,21.0,24.5426130391804,26.76,27.19030802287065,27.68,29.772806256834038,31.2,27.9,0.7096919771293493,2024-05-09
131 | 130,23.7,24.569698195909726,27.3,27.16893467104952,27.7,29.726693687872345,30.3,27.5,0.3310653289504799,2024-05-10
132 | 131,25.0,24.596014863621008,27.1,27.1487149330113,27.779999999999998,29.682045857191294,30.7,28.3,1.1512850669887023,2024-05-11
133 | 132,23.3,24.620528377443154,26.8,27.128526447051485,27.34,29.63668232935942,30.1,27.5,0.37147355294851536,2024-05-12
134 | 133,23.6,24.642665394666125,26.48,27.107183029804997,27.279999999999998,29.58906220794669,31.0,29.9,2.792816970195002,2024-05-13
135 | 134,22.1,24.66073260962852,26.7,27.083914144112594,27.34,29.5395890952643,29.4,29.7,2.6160858558874054,2024-05-14
136 | 135,23.4,24.67320263858214,26.9,27.058834965043314,27.38,29.489476174289056,29.6,27.4,0.3411650349566848,2024-05-15
137 | 136,22.5,24.678823055394513,26.86,27.032312688034864,27.14,29.439537882868365,29.9,27.2,0.16768731196513542,2024-05-16
138 | 137,22.7,24.678191892379928,26.36,27.005282943090165,27.14,29.3906828425568,29.4,27.6,0.5947170569098361,2024-05-17
139 | 138,24.7,24.673897341251934,26.7,26.979040517586974,26.939999999999998,29.343501947494786,29.0,27.7,0.7209594824130257,2024-05-18
140 | 139,24.2,24.66932416094969,27.12,26.95463696619492,27.58,29.29775383756272,30.1,29.3,2.345363033805082,2024-05-19
141 | 140,22.7,24.666435977743113,26.060000000000002,26.932259360509846,27.24,29.254196118912688,30.2,27.8,0.8677406394901546,2024-05-20
142 | 141,23.7,24.665170022302725,26.220000000000002,26.911623634637387,26.88,29.214168783044723,30.7,26.9,-0.011623634637388136,2024-05-21
143 | 142,22.3,24.664843359930018,26.76,26.892409068253034,27.2,29.17882199732285,30.2,28.7,1.8075909317469652,2024-05-22
144 | 143,24.1,24.663914627408264,26.88,26.874053163201662,27.279999999999998,29.14787242223809,29.5,26.7,-0.17405316320166264,2024-05-23
145 | 144,24.1,24.660725340578704,26.46,26.855672877882498,27.34,29.11985314989508,28.7,27.8,0.9443271221175031,2024-05-24
146 | 145,24.2,24.654716818197418,26.5,26.836468031591927,27.14,29.092167706904004,30.3,28.0,1.1635319684080727,2024-05-25
147 | 146,22.8,24.646178803579954,26.5,26.81573610106355,26.92,29.06209607062431,30.3,26.1,-0.7157361010635483,2024-05-26
148 | 147,21.8,24.634447401345096,26.58,26.7929289550417,27.279999999999998,29.027359312506572,30.7,28.2,1.4070710449583004,2024-05-27
149 | 148,22.4,24.619999973628563,27.0,26.76815847041535,27.64,28.987119556040966,30.0,28.6,1.8318415295846506,2024-05-28
150 | 149,21.7,24.605006618065385,26.36,26.742031849330573,27.24,28.941157689897835,29.3,28.2,1.4579681506694264,2024-05-29
151 | 150,22.2,24.59040071372869,26.6,26.7151901455802,27.14,28.889553311532723,29.6,26.1,-0.6151901455801969,2024-05-30
152 | 151,25.3,24.57548870447796,26.6,26.6877975443275,27.4,28.83295638784206,30.7,,,2024-05-31
153 | 152,23.1,24.560205763497315,26.52,26.660008255021353,27.1,28.77245206951114,29.1,,,2024-06-01
154 | 153,24.1,24.545043618325142,26.16,26.63220241262337,27.0,28.709671934319363,29.2,,,2024-06-02
155 | 154,24.1,24.52882150185037,26.0,26.604300698810814,26.88,28.646587344656087,29.1,,,2024-06-03
156 | 155,23.9,24.510376264837298,26.6,26.575846794710422,27.1,28.58408302952849,29.6,,,2024-06-04
157 | 156,23.5,24.489782578346244,26.46,26.546872884440205,26.939999999999998,28.521893304304598,29.1,,,2024-06-05
158 | 157,23.8,24.467841146144327,26.16,26.517337197967315,26.7,28.4590225507626,28.5,,,2024-06-06
159 | 158,23.2,24.445474440537122,26.28,26.48684137150556,26.8,28.394563354016654,30.2,,,2024-06-07
160 | 159,24.3,24.42464688251093,25.9,26.455505569400593,26.7,28.328512240170635,30.4,,,2024-06-08
161 | 160,23.1,24.407550490581496,25.9,26.424242961325877,26.48,28.26166954481757,30.4,,,2024-06-09
162 | 161,24.0,24.395346301426706,26.560000000000002,26.39414577275802,27.1,28.19506971285796,29.4,,,2024-06-10
163 | 162,24.2,24.38855114730833,26.0,26.366406260300565,26.3,28.13028504411793,29.1,,,2024-06-11
164 | 163,23.4,24.387817723758406,26.4,26.341725945367045,26.939999999999998,28.068598748324806,27.9,,,2024-06-12
165 | 164,22.6,24.39289827086457,26.42,26.31988440138088,26.8,28.010647672651253,28.0,,,2024-06-13
166 | 165,23.5,24.401737239137,26.0,26.300156045520367,26.54,27.95670164309736,28.2,,,2024-06-14
167 | 166,24.1,24.413027868937167,26.5,26.282061778044294,26.84,27.906490617428254,28.2,,,2024-06-15
168 | 167,23.0,24.427209277900698,26.2,26.265693303971226,26.439999999999998,27.860005009577183,28.4,,,2024-06-16
169 | 168,23.3,24.44536519451091,25.9,26.251234732014474,26.5,27.817552021040697,27.8,,,2024-06-17
170 | 169,24.9,24.466941908373112,25.8,26.238593447324526,26.3,27.77824967814976,28.4,,,2024-06-18
171 | 170,24.1,24.490088104317437,26.1,26.227370620173648,26.439999999999998,27.74075431366951,28.2,,,2024-06-19
172 | 171,23.5,24.512796662964565,25.76,26.21722814807445,26.279999999999998,27.704341132874873,27.5,,,2024-06-20
173 | 172,23.3,24.534713062331186,26.0,26.208268639970626,26.4,27.669574383757745,27.5,,,2024-06-21
174 | 173,23.5,24.55513327783209,26.060000000000002,26.200592298498044,26.5,27.63706359145335,27.7,,,2024-06-22
175 | 174,24.0,24.57402467641579,25.9,26.19437559935492,26.54,27.607698144031932,28.7,,,2024-06-23
176 | 175,23.6,24.59193460051504,26.16,26.189780504786043,26.48,27.581425962535423,28.4,,,2024-06-24
177 | 176,24.7,24.60954267459663,25.86,26.186818899677544,26.38,27.557546035061044,28.1,,,2024-06-25
178 | 177,23.7,24.626706862966575,25.8,26.18509479141342,26.439999999999998,27.535559272990948,28.2,,,2024-06-26
179 | 178,23.6,24.64305787227376,25.82,26.18399738806161,26.36,27.51522378265496,27.3,,,2024-06-27
180 | 179,23.5,24.658471117067563,26.0,26.182916954269352,26.5,27.49682895438302,27.6,,,2024-06-28
181 | 180,24.8,24.672457108159882,26.36,26.181631841133655,26.5,27.48079983535213,27.7,,,2024-06-29
182 | 181,24.3,24.684549138714814,26.060000000000002,26.18000455568921,26.3,27.467091223083305,28.1,,,2024-06-30
183 | 182,23.5,24.69377753031516,26.12,26.177677998158238,26.6,27.455068882297624,27.5,,,2024-07-01
184 | 183,24.2,24.7001550704912,26.0,26.17457669188746,26.439999999999998,27.443736455717403,27.3,,,2024-07-02
185 | 184,23.7,24.70434326952321,26.26,26.17090377958006,26.7,27.432379862434367,27.5,,,2024-07-03
186 | 185,24.7,24.706754079162863,26.2,26.166848662461547,26.54,27.420470518674954,28.3,,,2024-07-04
187 | 186,24.0,24.708841790212364,25.66,26.162697162625992,26.3,27.40814725452596,28.4,,,2024-07-05
188 | 187,23.9,24.71147761567523,25.96,26.158560854295427,26.5,27.396230390204142,28.0,,,2024-07-06
189 | 188,24.5,24.715283434492168,26.060000000000002,26.154359322596015,26.4,27.385174005820065,27.7,,,2024-07-07
190 | 189,24.3,24.720158276560625,26.16,26.149822715634787,26.34,27.374825559944412,27.2,,,2024-07-08
191 | 190,23.3,24.726077394922285,26.04,26.14486199812718,26.74,27.365008564889646,28.7,,,2024-07-09
192 | 191,23.3,24.7330705569219,25.82,26.139782121527396,26.34,27.355687730487958,27.8,,,2024-07-10
193 | 192,23.4,24.740489572456514,25.9,26.13495509141738,26.4,27.346965995036385,27.8,,,2024-07-11
194 | 193,23.5,24.747604804050166,26.060000000000002,26.13078992602985,26.3,27.33907566019333,28.6,,,2024-07-12
195 | 194,23.1,24.75421268419399,25.96,26.12783511540226,26.279999999999998,27.332853170232706,27.4,,,2024-07-13
196 | 195,24.4,24.75951902300959,26.16,26.12628181714629,26.3,27.328500089351795,27.7,,,2024-07-14
197 | 196,24.1,24.762979978935075,26.26,26.1262642382814,26.439999999999998,27.325766105382858,27.3,,,2024-07-15
198 | 197,24.6,24.765342796346285,25.98,26.12814153284608,26.34,27.324288332283334,27.2,,,2024-07-16
199 | 198,23.9,24.76832523220541,26.1,26.132006399248716,26.4,27.323710618669026,27.5,,,2024-07-17
200 | 199,25.2,24.773206659973727,26.02,26.137793951983785,26.3,27.32383598042013,27.2,,,2024-07-18
201 | 200,23.6,24.78144235569919,25.76,26.14549263634082,26.14,27.32465424122559,27.7,,,2024-07-19
202 | 201,22.7,24.79368209451886,26.060000000000002,26.15526774535212,26.4,27.32650514928466,27.7,,,2024-07-20
203 | 202,24.0,24.81063427429566,26.220000000000002,26.16759733161221,26.5,27.330282541301187,27.2,,,2024-07-21
204 | 203,24.7,24.83152818890207,26.060000000000002,26.1826030545522,26.34,27.33715487199442,27.6,,,2024-07-22
205 | 204,24.6,24.85438033306722,26.060000000000002,26.19986723746874,26.54,27.34806087106499,27.5,,,2024-07-23
206 | 205,24.5,24.877875946253432,25.8,26.218971518175454,26.5,27.36341792911732,28.2,,,2024-07-24
207 | 206,23.6,24.900363248576635,26.1,26.239454807637994,26.3,27.382721523064493,27.5,,,2024-07-25
208 | 207,24.5,24.92080159222895,26.16,26.260996130709724,26.4,27.404661034135998,27.7,,,2024-07-26
209 | 208,23.1,24.93914854752819,26.16,26.283106737284946,26.54,27.427541085258312,27.3,,,2024-07-27
210 | 209,24.4,24.956761189034964,26.2,26.305370101981282,26.54,27.450048389278347,27.9,,,2024-07-28
211 | 210,25.0,24.973927817449304,26.36,26.327430534640236,26.64,27.471601721995665,27.6,,,2024-07-29
212 | 211,23.9,24.990545908672132,26.16,26.348837851806344,26.5,27.49178178777988,28.1,,,2024-07-30
213 | 212,23.8,25.00627378794181,26.2,26.369182142432344,26.6,27.51030152129793,28.2,,,2024-07-31
214 | 213,25.1,25.020544526108598,26.36,26.388391509355095,26.74,27.527983922994988,28.0,,,2024-08-01
215 | 214,24.6,25.032586608650856,26.46,26.406503586386588,26.84,27.545870044507645,28.4,,,2024-08-02
216 | 215,24.7,25.042507399311674,26.32,26.423444829667307,26.68,27.564156257258233,28.0,,,2024-08-03
217 | 216,24.1,25.0516790576201,26.26,26.439409546631996,26.62,27.58219877050926,28.6,,,2024-08-04
218 | 217,24.3,25.060889712551738,26.48,26.454403688017194,26.84,27.599451745048718,27.9,,,2024-08-05
219 | 218,25.5,25.070644266171996,26.26,26.468301397032853,26.54,27.615281580815196,28.0,,,2024-08-06
220 | 219,23.0,25.080780108184396,26.46,26.48106743326385,26.7,27.629031291169714,28.1,,,2024-08-07
221 | 220,25.0,25.090370760579752,26.3,26.49268755732399,26.82,27.6408160118351,27.8,,,2024-08-08
222 | 221,24.4,25.098526435202448,26.4,26.503353178594597,26.64,27.651141236678896,27.4,,,2024-08-09
223 | 222,24.1,25.105653243361584,26.36,26.513578374685657,26.7,27.66087923741398,28.5,,,2024-08-10
224 | 223,23.1,25.113123667318888,26.4,26.523949782753473,26.74,27.67074840973968,28.2,,,2024-08-11
225 | 224,22.7,25.122696924129382,26.6,26.53514579300085,26.9,27.681685447997722,28.0,,,2024-08-12
226 | 225,24.5,25.135696573675954,25.96,26.547707996607993,26.34,27.694338870091656,27.8,,,2024-08-13
227 | 226,25.0,25.15275489675513,26.52,26.56197363784667,26.9,27.709628716322072,28.2,,,2024-08-14
228 | 227,22.1,25.17400660395234,26.560000000000002,26.578226755549895,27.14,27.728834757849384,28.5,,,2024-08-15
229 | 228,25.2,25.199801965035782,26.220000000000002,26.596507838150995,26.6,27.752400964809187,27.6,,,2024-08-16
230 | 229,25.3,25.229423049752263,26.36,26.61623588332251,26.84,27.77941286261305,27.6,,,2024-08-17
231 | 230,23.7,25.26156195542173,26.3,26.636652318408146,26.7,27.808474821939036,27.7,,,2024-08-18
232 | 231,25.2,25.295414326233303,26.36,26.657264105720984,26.8,27.83815845647475,28.2,,,2024-08-19
233 | 232,25.0,25.330595924682186,26.560000000000002,26.67783599893542,27.1,27.86729133219142,28.4,,,2024-08-20
234 | 233,25.2,25.36698869124191,26.46,26.69822217055646,26.7,27.89531971327029,28.5,,,2024-08-21
235 | 234,24.4,25.403082511662976,26.66,26.717988475770067,27.04,27.92165161276059,28.1,,,2024-08-22
236 | 235,24.0,25.437101029261825,26.86,26.73664111500476,27.24,27.946088497102934,28.3,,,2024-08-23
237 | 236,25.0,25.467231661467544,26.5,26.75387244458409,27.24,27.969283725361915,28.4,,,2024-08-24
238 | 237,25.5,25.492661298511248,26.9,26.769793067236456,27.34,27.99180343455785,28.3,,,2024-08-25
239 | 238,25.5,25.51321868338795,26.86,26.78479278451347,27.2,28.013539851063246,28.1,,,2024-08-26
240 | 239,23.9,25.529750692760096,26.7,26.799335401657117,27.04,28.034342789124597,28.6,,,2024-08-27
241 | 240,23.6,25.54246416455506,26.66,26.813537407604098,26.9,28.053950954527323,28.4,,,2024-08-28
242 | 241,25.6,25.55129573034686,26.86,26.827074959263186,27.2,28.071967353113685,28.8,,,2024-08-29
243 | 242,25.4,25.557495072766073,26.58,26.83948521372601,26.939999999999998,28.086963809527862,28.2,,,2024-08-30
244 | 243,25.1,25.563090292167452,26.560000000000002,26.850617096880388,26.84,28.09793102839893,28.5,,,2024-08-31
245 | 244,25.4,25.569129594422755,26.6,26.860588803594226,27.2,28.10483438905332,28.3,,,2024-09-01
246 | 245,24.5,25.57588143048932,26.8,26.869496722835745,27.2,28.10812744469234,28.6,,,2024-09-02
247 | 246,24.5,25.58250133396846,26.6,26.877643573507143,26.84,28.109292453703528,29.2,,,2024-09-03
248 | 247,24.8,25.587681358466217,26.8,26.885453974568446,26.9,28.110403641257758,28.5,,,2024-09-04
249 | 248,24.7,25.590510174056117,26.6,26.893301806821164,27.04,28.113515375102125,28.3,,,2024-09-05
250 | 249,23.9,25.59111025933063,26.8,26.90155487393024,27.1,28.119889429159965,28.4,,,2024-09-06
251 | 250,24.5,25.590433207156238,26.7,26.91051066189165,27.1,28.129891109703316,28.7,,,2024-09-07
252 | 251,25.1,25.58934157990389,26.9,26.92012577439681,27.34,28.14257550997881,28.1,,,2024-09-08
253 | 252,24.8,25.587686927497792,26.9,26.930002322070727,27.3,28.156379245238487,28.2,,,2024-09-09
254 | 253,24.3,25.585335702763647,26.96,26.93956283914019,27.3,28.169792210827186,28.2,,,2024-09-10
255 | 254,24.1,25.58217936168518,26.8,26.948405858810872,27.1,28.18197835708455,28.4,,,2024-09-11
256 | 255,25.0,25.578996426930964,26.8,26.956464499414096,27.22,28.192341162858487,28.0,,,2024-09-12
257 | 256,24.5,25.57716069695356,26.92,26.963842225437848,27.4,28.200926853244766,28.5,,,2024-09-13
258 | 257,25.3,25.57738500886661,26.98,26.970691883135864,27.2,28.2088795873448,29.0,,,2024-09-14
259 | 258,24.5,25.579498843647166,26.82,26.97721815506172,27.2,28.21755962571152,28.3,,,2024-09-15
260 | 259,23.8,25.582420286686915,26.7,26.983526580345167,27.14,28.228001804832175,29.0,,,2024-09-16
261 | 260,25.1,25.584763135405343,26.8,26.98947162730543,27.1,28.241230268315398,29.0,,,2024-09-17
262 | 261,25.3,25.58585656147187,26.720000000000002,26.99464630472679,27.14,28.257325027822674,29.3,,,2024-09-18
263 | 262,24.6,25.584425783021835,26.66,26.998409752240644,27.14,28.27504469908043,28.7,,,2024-09-19
264 | 263,25.4,25.5799426939275,26.8,27.000254315761925,27.34,28.292887975608956,28.7,,,2024-09-20
265 | 264,24.8,25.572714408955385,27.02,27.000253657159565,27.38,28.309532272799792,28.5,,,2024-09-21
266 | 265,25.1,25.563077040705068,26.58,26.998848293606276,27.439999999999998,28.32413135464425,28.4,,,2024-09-22
267 | 266,24.6,25.55019317210839,26.8,26.99605331132975,27.1,28.33624268575196,28.1,,,2024-09-23
268 | 267,24.8,25.53343311453669,26.96,26.991600364513268,27.2,28.345387815081608,28.5,,,2024-09-24
269 | 268,24.8,25.512854625221863,26.96,26.985529716634368,27.4,28.351278107242543,29.0,,,2024-09-25
270 | 269,24.1,25.489761809890535,26.96,26.978362362432602,27.279999999999998,28.353996690228044,28.5,,,2024-09-26
271 | 270,26.0,25.46568394057047,27.16,26.970834723761826,27.5,28.35394147445194,29.2,,,2024-09-27
272 | 271,23.7,25.44123916270308,27.0,26.96330573382302,27.54,28.35191106794111,29.2,,,2024-09-28
273 | 272,24.9,25.41658374458477,26.76,26.95601561719276,27.439999999999998,28.348657381406046,29.3,,,2024-09-29
274 | 273,25.4,25.391076109127777,26.8,26.94907553583767,27.18,28.345019281786044,28.8,,,2024-09-30
275 | 274,24.8,25.364606308058203,26.560000000000002,26.94258867411125,26.939999999999998,28.341538099440335,28.6,,,2024-10-01
276 | 275,24.8,25.338309668671243,26.560000000000002,26.936507494385673,26.8,28.337628720013303,28.4,,,2024-10-02
277 | 276,23.9,25.312893634252596,26.560000000000002,26.930463532026096,27.1,28.332242141869973,28.3,,,2024-10-03
278 | 277,24.5,25.287996304120025,26.92,26.92382475041388,27.24,28.324484379973082,28.5,,,2024-10-04
279 | 278,25.6,25.26240259945794,26.82,26.9159685844739,27.4,28.31374738962046,28.8,,,2024-10-05
280 | 279,24.4,25.234963609761706,26.66,26.90644565605172,27.2,28.3000320063982,28.6,,,2024-10-06
281 | 280,24.4,25.20611825273128,26.560000000000002,26.895326974018328,27.0,28.28422999680823,28.4,,,2024-10-07
282 | 281,24.0,25.177756326727078,26.6,26.883038480140222,27.3,28.267665415079673,28.5,,,2024-10-08
283 | 282,24.8,25.151504298019265,26.8,26.87010518729911,27.2,28.251720027882584,28.2,,,2024-10-09
284 | 283,24.5,25.12858745964685,27.060000000000002,26.856947652327616,27.2,28.237306814148077,28.4,,,2024-10-10
285 | 284,24.3,25.109911363961125,26.66,26.843881321399188,27.04,28.225005363395457,29.1,,,2024-10-11
286 | 285,24.8,25.09528051025392,26.76,26.831050980735487,27.38,28.214506030494285,28.7,,,2024-10-12
287 | 286,22.4,25.08368861194403,26.88,26.818139568222666,27.4,28.204453420270724,28.0,,,2024-10-13
288 | 287,24.5,25.07455993609931,26.7,26.804475398025772,27.3,28.193296535646404,28.5,,,2024-10-14
289 | 288,24.4,25.067827455944578,26.86,26.789565209412483,27.279999999999998,28.179896374719885,29.2,,,2024-10-15
290 | 289,22.5,25.062377311870016,26.9,26.772922859832242,27.4,28.163495243770654,28.6,,,2024-10-16
291 | 290,24.5,25.056828885098625,26.5,26.754448206266204,27.0,28.14440345221482,29.2,,,2024-10-17
292 | 291,24.3,25.05013004805071,26.5,26.734054435480676,27.0,28.123274206927434,28.9,,,2024-10-18
293 | 292,23.7,25.04148973983236,26.96,26.711511075979164,27.24,28.100232011131695,28.9,,,2024-10-19
294 | 293,24.8,25.02963613276868,26.7,26.6862995614748,27.279999999999998,28.075122359113703,29.1,,,2024-10-20
295 | 294,24.5,25.013425545334524,26.0,26.658033872466344,26.84,28.04803414551856,29.8,,,2024-10-21
296 | 295,24.3,24.99249941873476,26.5,26.62699489042094,26.7,28.0194024602752,29.5,,,2024-10-22
297 | 296,23.5,24.967310587483365,26.6,26.594065956059353,27.0,27.98946269290528,29.6,,,2024-10-23
298 | 297,23.2,24.9394600479687,26.5,26.5604280436608,26.9,27.958672490280357,28.5,,,2024-10-24
299 | 298,24.9,24.91003729758272,26.560000000000002,26.52718326582141,26.9,27.927600374064422,28.4,,,2024-10-25
300 | 299,24.4,24.87866534464555,26.62,26.495090408144993,26.9,27.896572127148634,28.5,,,2024-10-26
301 | 300,24.7,24.84524388820944,26.1,26.46482131576951,26.6,27.865873757251734,27.9,,,2024-10-27
302 | 301,24.5,24.80960581592742,26.36,26.436659037882546,27.04,27.835726566020686,27.8,,,2024-10-28
303 | 302,24.3,24.771467170461417,26.060000000000002,26.41045104719403,26.439999999999998,27.806642816354046,28.5,,,2024-10-29
304 | 303,24.5,24.7300761533372,26.26,26.385138897068316,26.64,27.77868946853417,28.2,,,2024-10-30
305 | 304,23.7,24.68443334237557,25.96,26.3593670192826,26.439999999999998,27.75178713530292,27.5,,,2024-10-31
306 | 305,24.2,24.634954796957306,26.1,26.33221317294393,26.64,27.72594978185181,27.6,,,2024-11-01
307 | 306,23.1,24.582962926711456,25.92,26.303231506804654,26.54,27.701730220631884,27.9,,,2024-11-02
308 | 307,24.1,24.529109939688517,25.96,26.27239732757125,26.4,27.679928755055176,28.1,,,2024-11-03
309 | 308,23.5,24.47351013093393,25.92,26.23987059130887,26.48,27.66070413806765,28.1,,,2024-11-04
310 | 309,23.1,24.416380082545135,26.4,26.206146247595637,26.54,27.643941957430155,28.1,,,2024-11-05
311 | 310,25.3,24.35958862922997,26.26,26.172306618734158,26.74,27.629328145213986,28.8,,,2024-11-06
312 | 311,24.6,24.305430910475806,26.3,26.139632197155017,26.8,27.61613112457284,27.8,,,2024-11-07
313 | 312,23.7,24.256158916548056,26.16,26.109013695560215,26.58,27.60354556890182,28.1,,,2024-11-08
314 | 313,23.2,24.212847191421183,26.3,26.08087005951338,26.6,27.590871554427338,28.8,,,2024-11-09
315 | 314,24.0,24.17611077376965,26.38,26.055200081047918,26.8,27.57753708639004,28.2,,,2024-11-10
316 | 315,23.3,24.14565069316819,26.14,26.031644834747006,26.4,27.56327046115802,28.2,,,2024-11-11
317 | 316,22.5,24.120646320260303,25.8,26.009540217434182,26.1,27.548071039999037,28.1,,,2024-11-12
318 | 317,21.2,24.09971644398376,25.8,25.987577748583863,26.34,27.531368226399245,28.8,,,2024-11-13
319 | 318,23.9,24.081612220892435,25.62,25.964547046238312,26.2,27.512160210675226,28.4,,,2024-11-14
320 | 319,22.7,24.06484714268047,25.64,25.939409410558866,25.939999999999998,27.489296359769707,27.8,,,2024-11-15
321 | 320,21.3,24.04836566660602,25.6,25.91207885767433,25.9,27.462504527139973,27.6,,,2024-11-16
322 | 321,21.7,24.03060720972054,25.5,25.883142267636604,25.92,27.43223271711857,27.5,,,2024-11-17
323 | 322,22.5,24.01130468510225,25.82,25.853741706480406,26.279999999999998,27.399702371110543,27.9,,,2024-11-18
324 | 323,22.0,23.991469103931415,25.7,25.824887110044994,26.0,27.36628014006207,28.1,,,2024-11-19
325 | 324,22.7,23.972981577240173,25.86,25.797195738534285,26.38,27.33291695711899,27.6,,,2024-11-20
326 | 325,24.1,23.956343132192327,25.76,25.77092031664851,26.24,27.301055755360263,27.5,,,2024-11-21
327 | 326,24.1,23.940731798349184,25.4,25.746123700566635,26.2,27.272048499284896,27.8,,,2024-11-22
328 | 327,23.7,23.92522114125292,25.66,25.72304710407057,26.04,27.247375086887633,27.5,,,2024-11-23
329 | 328,23.2,23.908888080238416,25.9,25.701724416282282,26.2,27.22799421199982,28.1,,,2024-11-24
330 | 329,23.4,23.890088921591207,25.560000000000002,25.681596332231106,26.2,27.213701442905183,27.8,,,2024-11-25
331 | 330,23.8,23.867887607036263,25.46,25.661779512606298,25.84,27.203405150440148,27.7,,,2024-11-26
332 | 331,23.3,23.842776311340984,25.46,25.641667720674892,25.9,27.19560879369619,27.1,,,2024-11-27
333 | 332,21.3,23.815246600232946,25.36,25.621032821233044,25.6,27.189106434097678,26.9,,,2024-11-28
334 | 333,22.7,23.785396379608727,25.36,25.599823921357732,25.939999999999998,27.18328401950008,27.3,,,2024-11-29
335 | 334,23.2,23.752866622826257,25.46,25.578021695888904,26.18,27.17788989148154,27.2,,,2024-11-30
336 | 335,22.9,23.718022852261843,25.62,25.5557624073539,26.1,27.172576899016597,27.9,,,2024-12-01
337 | 336,22.4,23.6821965809016,24.7,25.533226485433175,25.64,27.168072369415334,28.6,,,2024-12-02
338 | 337,20.6,23.647153348124995,25.18,25.510717203785113,25.8,27.16521560020176,27.7,,,2024-12-03
339 | 338,22.8,23.614446968961857,25.0,25.48888960509094,25.82,27.16453660772422,28.6,,,2024-12-04
340 | 339,22.7,23.584964370999252,24.96,25.4684858747666,25.939999999999998,27.167204489930906,27.5,,,2024-12-05
341 | 340,23.4,23.55903950500895,25.060000000000002,25.449932342674515,26.14,27.174324220210988,27.8,,,2024-12-06
342 | 341,22.5,23.536082022227646,24.92,25.433381487416323,26.08,27.186162910103267,27.8,,,2024-12-07
343 | 342,23.0,23.51537186679947,25.12,25.418571963815943,25.88,27.201617513944964,27.5,,,2024-12-08
344 | 343,23.0,23.496590925511313,25.16,25.4048083466794,25.64,27.218349242822416,28.1,,,2024-12-09
345 | 344,22.8,23.479138397589704,25.36,25.391240885556336,25.64,27.233741284925117,28.0,,,2024-12-10
346 | 345,23.1,23.462796612664686,25.2,25.377473163136955,25.64,27.245950648265197,27.2,,,2024-12-11
347 | 346,23.2,23.44731078317868,25.26,25.363609016038012,25.7,27.25458320000807,27.7,,,2024-12-12
348 | 347,22.8,23.431542025066104,24.96,25.349904930646403,26.14,27.260751111517987,27.5,,,2024-12-13
349 | 348,23.1,23.41372250806052,25.02,25.336121139840063,25.66,27.26573569362008,27.6,,,2024-12-14
350 | 349,23.3,23.391712989900707,24.8,25.321281192673485,25.5,27.27049058679047,28.0,,,2024-12-15
351 | 350,22.2,23.36421153591947,24.4,25.304353581154277,25.14,27.274803886233975,28.3,,,2024-12-16
352 | 351,22.8,23.33086797419195,25.12,25.284803793241466,25.64,27.27788735816525,27.4,,,2024-12-17
353 | 352,23.2,23.27859498526925,24.96,25.259995001707118,25.42,27.27772272047782,28.1,,,2024-12-18
354 | 353,22.6,23.227026080444656,25.060000000000002,25.233883061084175,25.6,27.274274516481082,27.4,,,2024-12-19
355 | 354,22.2,23.176547349482497,24.86,25.207593721385912,25.4,27.26937118959851,27.7,,,2024-12-20
356 | 355,22.2,23.126604125990255,25.12,25.181362757214536,25.48,27.263771580011838,27.9,,,2024-12-21
357 | 356,22.4,23.076780846085164,24.86,25.155215039795806,25.4,27.25776334097246,28.2,,,2024-12-22
358 | 357,21.9,23.026785103575012,24.92,25.129124207882843,25.54,27.25145835822907,27.2,,,2024-12-23
359 | 358,22.9,22.97637914139562,24.96,25.103061149050866,26.14,27.244959181564518,28.5,,,2024-12-24
360 | 359,23.0,22.925413305195583,24.8,25.07700326194738,25.34,27.23836096193224,28.7,,,2024-12-25
361 | 360,21.6,22.873724410556427,25.0,25.050901640053656,25.68,27.231689598922348,28.2,,,2024-12-26
362 | 361,22.4,22.821154905317275,24.860000000000003,25.024681130364243,25.58,27.224920442808376,28.0,,,2024-12-27
363 | 362,22.0,22.767607173283626,24.16,24.99824652360992,25.279999999999998,27.217952143963906,27.7,,,2024-12-28
364 | 363,21.4,22.713067379338305,24.82,24.97151604500083,25.48,27.210632259534666,27.2,,,2024-12-29
365 | 364,21.9,22.65762580392415,24.4,24.944451362059148,25.439999999999998,27.202775319284807,27.5,,,2024-12-30
366 | 365,22.6,22.601445359784478,24.76,24.91704874410255,25.74,27.194280948278937,27.8,,,2024-12-31
367 |
--------------------------------------------------------------------------------
/sample_data/transformed/temperature_mean-addis-ababa-ethiopia_transformed.csv:
--------------------------------------------------------------------------------
1 | dayofyear,min,p05,p40,mean,p60,p95,max,2024,2024_diff,date
2 | 1,10.2,11.345867806169725,13.1,13.218627284942785,13.639999999999999,14.903534895228171,15.0,14.7,1.4813727150572138,2024-01-01
3 | 2,11.2,11.414713439003753,13.16,13.264068069754737,13.44,14.936174936599095,16.1,15.0,1.735931930245263,2024-01-02
4 | 3,11.3,11.483923906983343,12.96,13.309515509311039,13.4,14.969085240565512,15.4,14.5,1.1904844906889611,2024-01-03
5 | 4,11.5,11.553378956814273,13.16,13.354920916973677,13.6,15.002243557661012,15.0,14.5,1.1450790830263227,2024-01-04
6 | 5,10.9,11.622952543119935,13.14,13.400237528656493,13.94,15.035581123278988,14.8,15.5,2.0997624713435066,2024-01-05
7 | 6,10.3,11.692524121263709,13.06,13.445436083317244,13.74,15.069070999534867,15.8,15.1,1.654563916682756,2024-01-06
8 | 7,11.0,11.761990368747519,13.26,13.490499169118685,13.84,15.1027414025616,16.3,14.6,1.1095008308813146,2024-01-07
9 | 8,11.1,11.831299814668293,13.16,13.535426679834623,13.7,15.136613725528791,15.4,13.9,0.3645733201653769,2024-01-08
10 | 9,10.8,11.90046237143164,13.32,13.580232846414464,13.98,15.170670994022988,15.9,14.0,0.4197671535855356,2024-01-09
11 | 10,11.6,11.969512241637325,13.6,13.624952313008466,13.84,15.20490260942161,16.2,14.6,0.9750476869915339,2024-01-10
12 | 11,10.9,12.038535815553463,13.42,13.669642638636411,13.8,15.239287376480748,16.1,15.0,1.330357361363589,2024-01-11
13 | 12,11.3,12.107674224269664,13.360000000000001,13.714383651997368,14.04,15.27380782528201,15.6,15.0,1.2856163480026321,2024-01-12
14 | 13,11.9,12.177210510793682,13.780000000000001,13.759299741714706,14.2,15.308465903624294,16.1,15.2,1.4407002582852932,2024-01-13
15 | 14,11.9,12.247761411152442,13.48,13.804613578668631,14.2,15.343395574822585,16.9,16.3,2.4953864213313697,2024-01-14
16 | 15,12.6,12.32056945095882,13.66,13.850654211614264,14.2,15.379141890294655,17.9,16.0,2.1493457883857356,2024-01-15
17 | 16,11.7,12.384379939571595,13.92,13.894008403411965,14.24,15.414623767305706,16.0,15.2,1.3059915965880347,2024-01-16
18 | 17,11.9,12.443991662155645,13.52,13.936154108316801,14.44,15.450390951965463,15.7,15.2,1.2638458916831983,2024-01-17
19 | 18,12.1,12.49850817730469,13.66,13.976539876360446,14.5,15.487249859790133,16.0,16.2,2.223460123639553,2024-01-18
20 | 19,12.0,12.547338869963593,13.4,14.014726987453766,13.94,15.526021524000278,15.9,15.8,1.7852730125462344,2024-01-19
21 | 20,11.3,12.590403877024645,13.66,14.050440151627368,14.1,15.567718152666012,16.0,15.1,1.0495598483726312,2024-01-20
22 | 21,12.4,12.62814692665104,13.76,14.08357849884322,14.34,15.61263112491847,15.9,14.7,0.6164215011567791,2024-01-21
23 | 22,12.8,12.661061582212911,13.82,14.114373713715624,14.74,15.659754424179207,16.0,14.2,0.0856262862843753,2024-01-22
24 | 23,11.7,12.689164930500288,14.1,14.143268392250077,14.74,15.707387436014539,16.1,15.2,1.0567316077499225,2024-01-23
25 | 24,12.3,12.712357525219238,13.96,14.17073491062804,14.54,15.7541193735213,16.2,15.7,1.5292650893719593,2024-01-24
26 | 25,12.5,12.731297572738159,13.9,14.19726867725026,14.4,15.798827065372963,16.2,16.6,2.402731322749741,2024-01-25
27 | 26,11.4,12.746487990385523,14.06,14.223280098252554,14.54,15.84094137877162,16.6,17.5,3.2767199017474464,2024-01-26
28 | 27,12.3,12.7587519250097,13.860000000000001,14.249064069387412,14.639999999999999,15.881003389809859,16.3,17.0,2.750935930612588,2024-01-27
29 | 28,12.7,12.76944140312583,14.16,14.274910913363707,14.639999999999999,15.920105478103286,16.4,16.2,1.9250890866362926,2024-01-28
30 | 29,12.9,12.780098744709012,13.98,14.301044837786712,14.319999999999999,15.959021420168705,16.9,15.7,1.3989551622132872,2024-01-29
31 | 30,12.3,12.791201954405803,13.96,14.327338955322444,14.319999999999999,15.997822093142165,16.6,15.8,1.4726610446775563,2024-01-30
32 | 31,10.9,12.802138749155523,13.92,14.353480741025406,14.68,16.03691438969532,16.7,15.2,0.8465192589745936,2024-01-31
33 | 32,10.9,12.811642193602012,14.0,14.379074606954843,14.639999999999999,16.076774250869416,16.8,15.3,0.9209253930451577,2024-02-01
34 | 33,12.0,12.81914650810097,13.96,14.403664005417266,14.6,16.117587914254912,16.8,15.8,1.396335994582735,2024-02-02
35 | 34,11.7,12.824997574748775,14.0,14.427079239159703,14.54,16.15976516419755,17.0,15.1,0.672920760840297,2024-02-03
36 | 35,11.1,12.830692304995043,14.5,14.449771932229716,14.7,16.20352514251656,16.7,14.8,0.3502280677702849,2024-02-04
37 | 36,11.8,12.83813398010471,14.2,14.472222665769072,14.88,16.248083916542093,16.4,14.6,0.1277773342309274,2024-02-05
38 | 37,12.6,12.848622334199996,14.26,14.494674596790487,14.639999999999999,16.291749044925744,16.6,15.6,1.1053254032095126,2024-02-06
39 | 38,13.4,12.862954824764525,14.26,14.51718371619581,14.639999999999999,16.332704819647997,16.5,15.7,1.1828162838041898,2024-02-07
40 | 39,13.2,12.88131128580581,14.56,14.539870538664527,14.84,16.369593391890394,17.0,16.3,1.7601294613354739,2024-02-08
41 | 40,13.3,12.903548541404126,14.42,14.562965473420947,14.959999999999999,16.401826488800502,17.4,16.5,1.937034526579053,2024-02-09
42 | 41,12.4,12.930283049538248,14.42,14.587007421629833,15.34,16.43037627458211,18.0,15.9,1.3129925783701673,2024-02-10
43 | 42,12.4,12.962733025709776,14.62,14.612615931999423,15.04,16.4572912196192,16.8,15.6,0.9873840680005763,2024-02-11
44 | 43,12.5,13.000838695044337,14.46,14.64017471750244,14.84,16.484911435327344,16.1,15.2,0.5598252824975596,2024-02-12
45 | 44,12.0,13.042696119955801,14.360000000000001,14.66945581602462,15.24,16.514989284265976,16.6,15.6,0.9305441839753801,2024-02-13
46 | 45,12.5,13.08601595398119,14.42,14.699926152045645,15.24,16.54843567667416,16.8,15.4,0.7000738479543553,2024-02-14
47 | 46,12.7,13.129645716184754,14.16,14.731182936531699,14.719999999999999,16.58566586331646,17.2,15.5,0.7688170634683011,2024-02-15
48 | 47,12.8,13.1744151366423,14.22,14.763178673777059,14.8,16.626491035813153,17.4,15.3,0.5368213262229418,2024-02-16
49 | 48,13.0,13.22146586249732,14.26,14.796131102533616,14.94,16.669506951343116,18.0,15.3,0.5038688974663845,2024-02-17
50 | 49,11.9,13.271554545932727,14.4,14.83026751634088,14.74,16.7123511968189,16.9,15.7,0.8697324836591189,2024-02-18
51 | 50,11.5,13.324888636052423,14.2,14.8656817781041,15.04,16.752962691430113,16.7,16.1,1.2343182218959008,2024-02-19
52 | 51,12.7,13.380598156265584,14.5,14.902106716324633,15.06,16.790189102339102,16.5,15.5,0.5978932836753668,2024-02-20
53 | 52,13.2,13.436996352625123,14.46,14.938916963743985,15.1,16.823948483684536,16.8,15.4,0.46108303625601543,2024-02-21
54 | 53,13.1,13.493096040641557,14.5,14.97550681405446,14.94,16.854623601642686,17.4,15.0,0.02449318594553951,2024-02-22
55 | 54,13.4,13.548489136387035,14.76,15.011808372152231,15.04,16.883198236699137,17.5,14.8,-0.21180837215223036,2024-02-23
56 | 55,13.5,13.60262010414541,14.66,15.048386824975973,15.0,16.91082780465102,18.0,15.8,0.751613175024028,2024-02-24
57 | 56,13.9,13.654355489704994,14.6,15.08603290909959,14.9,16.937465101241358,18.1,17.4,2.313967090900409,2024-02-25
58 | 57,13.3,13.702271144812809,14.66,15.12520629809883,15.36,16.963332215228377,17.5,18.2,3.0747937019011697,2024-02-26
59 | 58,13.1,13.745512141097556,14.7,15.166064933200014,15.7,16.989878942300145,17.4,18.8,3.633935066799987,2024-02-27
60 | 59,12.7,13.784146086371761,15.1,15.208434549264853,15.44,17.01902405493146,18.0,18.4,3.191565450735146,2024-02-28
61 | 60,12.8,13.818310293135692,14.7,15.251743496871047,15.5,17.052025341232827,18.2,16.8,1.548256503128954,2024-03-01
62 | 61,13.0,13.84797736497401,15.0,15.295079580886753,15.56,17.088515605795923,18.0,16.5,1.2049204191132468,2024-03-02
63 | 62,12.4,13.873124515879068,14.8,15.337131871379784,15.319999999999999,17.126412158836075,18.5,17.2,1.8628681286202156,2024-03-03
64 | 63,12.6,13.893784127064363,14.8,15.376499498723184,15.1,17.162599863122544,17.8,16.3,0.9235005012768163,2024-03-04
65 | 64,13.9,13.91039896034953,14.62,15.412372720570273,15.2,17.195045084264176,17.7,15.9,0.48762727942972717,2024-03-05
66 | 65,14.0,13.924033664123437,14.8,15.444959445647509,15.319999999999999,17.224050731528948,17.8,16.3,0.8550405543524917,2024-03-06
67 | 66,13.9,13.936138428073473,15.12,15.475086823297989,15.639999999999999,17.251070177467227,17.3,15.8,0.3249131767020117,2024-03-07
68 | 67,13.8,13.947701843711634,15.360000000000001,15.503718259572304,15.98,17.277838350558387,17.5,15.9,0.39628174042769615,2024-03-08
69 | 68,13.8,13.95933201986744,14.96,15.531668247621381,16.24,17.306061943832724,18.0,16.3,0.7683317523786197,2024-03-09
70 | 69,13.3,13.971113834261844,15.66,15.559354067684373,16.04,17.33622305327758,18.2,15.2,-0.3593540676843734,2024-03-10
71 | 70,13.5,13.98292337280637,15.52,15.586688527679135,16.0,17.367636510758715,18.9,15.2,-0.3866885276791354,2024-03-11
72 | 71,13.4,13.99418788128748,15.4,15.6132853870657,15.94,17.399694740705968,18.9,15.4,-0.21328538706569944,2024-03-12
73 | 72,12.5,14.004314759886404,15.56,15.638766826147823,16.24,17.43281204590114,19.1,15.6,-0.0387668261478229,2024-03-13
74 | 73,12.0,14.013641872241147,15.72,15.66291990479126,16.24,17.467641389129,18.0,16.0,0.3370800952087407,2024-03-14
75 | 74,12.3,14.02221649226714,15.66,15.685077550534311,16.24,17.504180974632593,17.8,16.0,0.3149224494656888,2024-03-15
76 | 75,12.5,14.029792809920885,15.16,15.704273331522163,15.84,17.54144552861641,18.8,16.3,0.5957266684778375,2024-03-16
77 | 76,12.7,14.036312052448258,15.4,15.719618340206232,15.7,17.57734398618461,19.4,17.0,1.2803816597937683,2024-03-17
78 | 77,13.5,14.042150532147957,15.44,15.730895784108062,15.78,17.609952608434316,19.3,17.3,1.5691042158919384,2024-03-18
79 | 78,14.2,14.047874692502157,15.48,15.738432691122442,15.84,17.638307389633702,18.7,18.3,2.5615673088775583,2024-03-19
80 | 79,14.3,14.053994340027394,15.62,15.742755037141789,15.94,17.662024576917315,18.9,17.3,1.5572449628582117,2024-03-20
81 | 80,13.7,14.061104101864537,15.52,15.744470332934611,16.0,17.68175765824273,18.7,17.5,1.7555296670653888,2024-03-21
82 | 81,13.6,14.0697821763758,15.3,15.744380060387911,15.74,17.698752161457247,18.9,18.6,2.8556199396120903,2024-03-22
83 | 82,13.5,14.080481455059155,15.4,15.743269288548044,15.9,17.714001785024475,17.9,18.3,2.556730711451957,2024-03-23
84 | 83,13.3,14.093502862558049,15.4,15.74143524780666,15.78,17.727125613574493,18.1,18.0,2.2585647521933403,2024-03-24
85 | 84,12.5,14.109215333844915,15.32,15.73890448990665,15.6,17.737484571595566,18.4,15.7,-0.03890448990665085,2024-03-25
86 | 85,12.9,14.127692028094845,15.1,15.735728997830675,15.78,17.744993201800856,18.6,15.9,0.16427100216932544,2024-03-26
87 | 86,14.3,14.148210714641724,15.2,15.731993216264113,16.04,17.749995502583932,18.6,15.8,0.06800678373588731,2024-03-27
88 | 87,14.0,14.169545383526232,15.3,15.727515993397496,16.0,17.7525886009462,18.6,15.5,-0.22751599339749617,2024-03-28
89 | 88,13.4,14.190103448020388,15.4,15.72189239214806,15.74,17.752753947783024,19.1,15.5,-0.22189239214806022,2024-03-29
90 | 89,13.8,14.208793558038387,15.26,15.714845178738845,15.94,17.750693784624723,19.5,15.8,0.08515482126115614,2024-03-30
91 | 90,13.8,14.225614824479699,15.2,15.706663943202772,15.9,17.74729405166418,20.1,15.3,-0.406663943202771,2024-03-31
92 | 91,13.4,14.240939691534644,15.16,15.698045677462526,15.84,17.743202558417696,19.5,15.9,0.2019543225374747,2024-04-01
93 | 92,13.4,14.255403380717844,15.16,15.689769821015837,15.74,17.738913123615546,19.2,15.5,-0.18976982101583673,2024-04-02
94 | 93,14.2,14.26966486337477,15.16,15.68242573368031,15.5,17.734453533022347,19.3,15.1,-0.58242573368031,2024-04-03
95 | 94,14.0,14.283940783662569,15.16,15.676283902025082,15.66,17.72924836923131,19.4,15.4,-0.2762839020250816,2024-04-04
96 | 95,14.3,14.298296473163468,15.26,15.671170562428221,15.639999999999999,17.722035367678465,19.5,15.5,-0.17117056242822137,2024-04-05
97 | 96,14.5,14.313051351897768,15.360000000000001,15.66670819325192,15.8,17.711153065655,19.4,15.2,-0.4667081932519199,2024-04-06
98 | 97,14.4,14.328584805207601,15.06,15.662673817022267,15.639999999999999,17.695493730565943,19.3,15.2,-0.4626738170222673,2024-04-07
99 | 98,14.4,14.345251739980617,15.26,15.659076216078605,15.6,17.67569089067465,19.0,15.7,0.04092378392139473,2024-04-08
100 | 99,13.5,14.362658621558055,15.26,15.656211746385233,15.6,17.65313576037942,19.0,16.4,0.7437882536147651,2024-04-09
101 | 100,14.2,14.380137406503184,15.06,15.654455654724577,15.54,17.629158782204154,18.6,15.8,0.14554434527542348,2024-04-10
102 | 101,13.8,14.39632853033337,15.1,15.65377521004162,15.34,17.60434840944295,19.3,15.7,0.046224789958380086,2024-04-11
103 | 102,13.9,14.409338646535724,15.16,15.653765358196534,15.4,17.579526951650024,18.7,15.5,-0.1537653581965337,2024-04-12
104 | 103,14.1,14.418130810745224,15.12,15.65433683959239,15.6,17.55648527959247,18.5,16.2,0.5456631604076101,2024-04-13
105 | 104,14.0,14.422488917040098,15.3,15.65571372563809,15.5,17.537312392152277,18.3,17.0,1.3442862743619095,2024-04-14
106 | 105,13.9,14.422805554616364,15.4,15.658240688697658,15.9,17.524052867565366,18.0,17.2,1.5417593113023411,2024-04-15
107 | 106,14.5,14.420434345908545,15.32,15.662370121116536,15.88,17.517913141886556,18.3,17.2,1.5376298788834628,2024-04-16
108 | 107,14.3,14.417544361794295,15.4,15.668587291327388,15.74,17.51865617139638,18.6,17.2,1.5314127086726117,2024-04-17
109 | 108,14.3,14.415962641942507,15.26,15.677084661615794,15.7,17.525184512759367,18.2,17.0,1.322915338384206,2024-04-18
110 | 109,14.5,14.416642971180613,15.3,15.687694426454438,15.7,17.53514927773403,17.6,17.1,1.412305573545563,2024-04-19
111 | 110,14.6,14.419500751863106,15.5,15.700067562902374,15.7,17.54677256036708,17.4,17.5,1.7999324370976257,2024-04-20
112 | 111,14.3,14.424028195288082,15.26,15.71384757178227,15.819999999999999,17.559575767614948,17.8,15.8,0.08615242821772995,2024-04-21
113 | 112,14.4,14.429376193747572,15.2,15.728711981073848,15.9,17.57463206272056,19.5,16.5,0.7712880189261515,2024-04-22
114 | 113,14.4,14.4346345550317,15.46,15.744420912187275,15.88,17.593397423284383,19.1,16.3,0.5555790878127258,2024-04-23
115 | 114,13.6,14.438881608930712,15.3,15.760579053511107,15.88,17.617421308157237,18.4,16.1,0.33942094648889487,2024-04-24
116 | 115,13.8,14.441682675029355,15.16,15.776485321816015,15.9,17.647299408454355,18.1,16.4,0.6235146781839838,2024-04-25
117 | 116,13.8,14.442430508479738,15.26,15.791420361002677,15.94,17.68276392454577,18.1,15.2,-0.5914203610026778,2024-04-26
118 | 117,14.0,14.440844764623566,15.360000000000001,15.804959492211779,15.84,17.723056643093805,18.6,15.6,-0.20495949221177945,2024-04-27
119 | 118,13.1,14.437184674312121,15.26,15.816768623281328,16.0,17.766786505002262,19.2,15.6,-0.21676862328132884,2024-04-28
120 | 119,13.7,14.43212748707518,15.5,15.826505188888673,15.92,17.812107998021492,18.6,15.6,-0.2265051888886731,2024-04-29
121 | 120,13.8,14.42675883413361,15.5,15.833959517841583,15.78,17.857087797535538,18.6,16.2,0.3660404821584162,2024-04-30
122 | 121,14.4,14.42245558907318,15.46,15.839090769489893,15.94,17.899684441996822,18.7,15.7,-0.13909076948989352,2024-05-01
123 | 122,13.6,14.420136092988223,15.360000000000001,15.842219257422828,15.84,17.93771164682661,19.4,16.6,0.7577807425771734,2024-05-02
124 | 123,13.7,14.4203355742309,15.5,15.844175340740158,16.12,17.969337637965612,19.0,16.2,0.3558246592598415,2024-05-03
125 | 124,13.7,14.422953542494684,15.66,15.845928167864823,16.24,17.99398454564202,19.3,16.6,0.7540718321351783,2024-05-04
126 | 125,13.5,14.427208352374382,15.34,15.848064941631808,16.12,18.011442024768602,19.4,17.3,1.4519350583681927,2024-05-05
127 | 126,13.9,14.43177571439995,15.32,15.850694953004625,15.94,18.021582233530776,19.5,16.0,0.14930504699537472,2024-05-06
128 | 127,14.0,14.435343057365259,15.360000000000001,15.853624565767326,16.0,18.024801141951205,18.9,16.9,1.0463754342326723,2024-05-07
129 | 128,13.6,14.43686630363543,15.3,15.856303084259023,15.7,18.02229691945843,18.8,16.8,0.9436969157409774,2024-05-08
130 | 129,14.2,14.436077267182537,15.360000000000001,15.85816708177445,15.7,18.01570120811145,18.6,15.9,0.04183291822555013,2024-05-09
131 | 130,14.0,14.433587570474899,15.06,15.85916135570989,15.639999999999999,18.006980198798136,18.8,15.9,0.04083864429011008,2024-05-10
132 | 131,14.0,14.430089380751749,15.2,15.859673659704184,15.639999999999999,17.998040481467054,19.0,16.2,0.34032634029581565,2024-05-11
133 | 132,14.3,14.426168932961122,15.16,15.86024018964968,15.54,17.989992767403212,18.5,16.5,0.6397598103503199,2024-05-12
134 | 133,14.3,14.422450581470782,15.2,15.861491062450492,15.639999999999999,17.983821406684104,18.5,17.4,1.5385089375495067,2024-05-13
135 | 134,14.2,14.419483045087848,15.5,15.8640592550382,15.74,17.979929032305876,18.8,17.5,1.6359407449618004,2024-05-14
136 | 135,14.3,14.417171593276475,15.56,15.868403575604882,16.0,17.978595201846097,18.4,16.7,0.8315964243951175,2024-05-15
137 | 136,14.4,14.415156315076846,15.8,15.874921108794974,16.1,17.980536506919353,18.0,16.8,0.9250788912050272,2024-05-16
138 | 137,14.1,14.413016508357066,15.56,15.883972536976248,16.24,17.986603211094113,18.0,17.0,1.1160274630237517,2024-05-17
139 | 138,14.0,14.410521772351922,15.62,15.895588498716405,16.1,17.997965214202953,17.7,17.2,1.3044115012835942,2024-05-18
140 | 139,14.0,14.407371543597874,15.5,15.909320763670134,16.08,18.015272967274758,18.3,16.5,0.5906792363298656,2024-05-19
141 | 140,13.8,14.402963749371423,15.7,15.924189608952897,16.279999999999998,18.03869328717012,18.8,17.0,1.0758103910471029,2024-05-20
142 | 141,14.1,14.39692935156919,15.32,15.938507789054125,16.24,18.068076898041088,19.0,16.6,0.6614922109458767,2024-05-21
143 | 142,14.3,14.389246291091592,15.26,15.950355988428083,16.14,18.103031795509256,18.6,17.2,1.2496440115719167,2024-05-22
144 | 143,14.2,14.380303741915624,15.3,15.958286787508918,16.14,18.14235371650773,18.8,17.1,1.1417132124910836,2024-05-23
145 | 144,14.3,14.370334009883397,15.360000000000001,15.961502432991715,16.439999999999998,18.18449073897112,19.4,17.9,1.9384975670082838,2024-05-24
146 | 145,14.1,14.359539059273352,15.4,15.959650354180747,16.22,18.227757273782018,19.7,17.6,1.6403496458192546,2024-05-25
147 | 146,14.1,14.3478794781422,15.3,15.952630811388197,16.42,18.27078196025358,19.4,18.5,2.5473691886118033,2024-05-26
148 | 147,14.2,14.335280607344565,15.3,15.940583444222936,16.1,18.31175121897134,19.4,18.0,2.059416555777064,2024-05-27
149 | 148,14.2,14.32154570566995,15.26,15.924020714444229,16.14,18.348443032027333,19.2,18.3,2.375979285555772,2024-05-28
150 | 149,14.6,14.30633122860733,15.46,15.903842324634427,15.639999999999999,18.378793859802574,19.5,18.1,2.1961576753655745,2024-05-29
151 | 150,13.8,14.288933676046517,15.7,15.880355175093397,16.24,18.401125107799373,19.7,18.0,2.1196448249066027,2024-05-30
152 | 151,13.5,14.26868358488454,15.360000000000001,15.853309734894099,16.0,18.414696980801715,19.2,17.9,2.0466902651059,2024-05-31
153 | 152,13.9,14.245020264289275,15.46,15.822113909574675,16.1,18.419502320587803,19.1,,,2024-06-01
154 | 153,14.0,14.217714203767317,15.42,15.786404558721197,16.04,18.416663557706084,19.0,,,2024-06-02
155 | 154,14.2,14.187163824621699,15.26,15.746036333564591,15.5,18.406826554211566,19.6,,,2024-06-03
156 | 155,14.1,14.154002231993088,15.16,15.700942075757553,15.52,18.389914375950966,19.4,,,2024-06-04
157 | 156,14.2,14.11871656334856,15.0,15.651375010446833,15.639999999999999,18.365231475553156,19.2,,,2024-06-05
158 | 157,14.1,14.081628219291312,15.12,15.598011654319015,15.5,18.332207699469745,18.9,,,2024-06-06
159 | 158,13.3,14.042826636975029,15.26,15.541607503488368,15.4,18.290614397163537,19.0,,,2024-06-07
160 | 159,13.3,14.002019967063429,15.06,15.482764176388528,15.3,18.240546312415265,19.0,,,2024-06-08
161 | 160,13.9,13.95912539403266,14.98,15.421981159973331,15.3,18.18330555426319,18.6,,,2024-06-09
162 | 161,13.9,13.914431707540478,14.7,15.359792522604911,15.24,18.121536505832267,18.9,,,2024-06-10
163 | 162,13.7,13.868075226883219,15.12,15.296940189532407,15.44,18.05781948696719,19.0,,,2024-06-11
164 | 163,13.7,13.820189900701552,14.66,15.234428500831667,15.24,17.99489972113756,19.3,,,2024-06-12
165 | 164,13.3,13.771366273355714,14.5,15.172971351393963,14.94,17.936184525223425,18.6,,,2024-06-13
166 | 165,13.4,13.722400229249129,14.62,15.1128973285322,15.0,17.88468572020002,18.6,,,2024-06-14
167 | 166,13.4,13.67425361303527,14.5,15.054224124279521,15.08,17.84228644737404,19.4,,,2024-06-15
168 | 167,13.2,13.627595379414666,14.2,14.996983974753775,14.68,17.80950795723076,18.8,,,2024-06-16
169 | 168,13.5,13.582963799576687,14.22,14.941223991431752,14.8,17.784272351558133,18.3,,,2024-06-17
170 | 169,13.4,13.540755109371112,14.3,14.887277804263737,14.74,17.76300636095851,18.9,,,2024-06-18
171 | 170,13.3,13.501156023825395,14.26,14.83571055233721,14.719999999999999,17.743313567912175,18.7,,,2024-06-19
172 | 171,13.3,13.463589906985302,14.06,14.786892692576869,14.74,17.724886330005585,18.7,,,2024-06-20
173 | 172,13.1,13.427175015672281,14.24,14.740821018302347,14.5,17.708254991662017,18.7,,,2024-06-21
174 | 173,13.1,13.39147998765823,14.06,14.697450872822728,14.34,17.694060219024816,18.4,,,2024-06-22
175 | 174,12.9,13.356654845650999,14.06,14.656979175108154,14.34,17.68346851983808,19.6,,,2024-06-23
176 | 175,12.8,13.323168040276148,14.0,14.619501813226645,14.4,17.675725510160444,19.6,,,2024-06-24
177 | 176,12.8,13.291637042411386,14.0,14.584882915260225,14.4,17.66864632187986,19.5,,,2024-06-25
178 | 177,12.9,13.26290222988917,13.8,14.552903720863045,14.3,17.659694897683902,18.2,,,2024-06-26
179 | 178,13.4,13.237721246590258,13.9,14.52329805110705,14.24,17.64715947658195,18.7,,,2024-06-27
180 | 179,13.1,13.216574408332884,13.76,14.495927900430019,14.2,17.630729370271688,18.2,,,2024-06-28
181 | 180,13.0,13.199467161337093,13.860000000000001,14.470770935222392,14.28,17.611090873861567,19.1,,,2024-06-29
182 | 181,12.9,13.185898212190631,13.76,14.447634413854757,14.2,17.587811257706363,19.2,,,2024-06-30
183 | 182,13.1,13.175279050815105,14.1,14.426055029384136,14.34,17.559027084332,19.2,,,2024-07-01
184 | 183,13.5,13.166476955588358,14.12,14.405650606422531,14.5,17.523289226585486,19.6,,,2024-07-02
185 | 184,12.5,13.158166763797675,13.92,14.386182578196387,14.1,17.47980469116873,19.3,,,2024-07-03
186 | 185,12.7,13.149297693201172,13.9,14.367271115426892,14.1,17.42874312467594,19.7,,,2024-07-04
187 | 186,12.8,13.139129893816573,13.76,14.34844790349865,14.1,17.37125079407131,19.1,,,2024-07-05
188 | 187,12.7,13.127339926710299,13.9,14.329483675081212,14.0,17.309313117971147,18.9,,,2024-07-06
189 | 188,12.3,13.114324287067905,13.9,14.31026818672868,14.139999999999999,17.24481613781903,18.5,,,2024-07-07
190 | 189,12.5,13.100761899133724,13.8,14.290604171982897,14.1,17.17818967756343,19.1,,,2024-07-08
191 | 190,13.4,13.08754161177525,13.9,14.270317690181486,14.1,17.10868135243868,18.8,,,2024-07-09
192 | 191,13.2,13.075690301913964,13.860000000000001,14.249451016608795,14.1,17.03631985226286,18.3,,,2024-07-10
193 | 192,12.8,13.065092676503626,13.860000000000001,14.228045492112058,14.139999999999999,16.963205420308732,17.8,,,2024-07-11
194 | 193,13.2,13.054779694387468,13.8,14.20629073257985,14.18,16.89321302545248,18.0,,,2024-07-12
195 | 194,12.6,13.043480812174828,13.860000000000001,14.184449769696599,14.2,16.829913570144214,17.8,,,2024-07-13
196 | 195,12.3,13.030378704219139,13.66,14.162821932458687,14.0,16.77427112100749,17.6,,,2024-07-14
197 | 196,12.8,13.015501712527495,13.66,14.14184382859606,14.0,16.725497663072282,17.4,,,2024-07-15
198 | 197,12.5,12.999238805211561,13.6,14.121977089762773,13.9,16.682299412939862,17.8,,,2024-07-16
199 | 198,12.8,12.982234189503778,13.8,14.103505172836577,14.1,16.644183011655883,18.7,,,2024-07-17
200 | 199,12.6,12.965295226343683,13.56,14.086640241027732,13.94,16.6111459706644,19.7,,,2024-07-18
201 | 200,12.3,12.949596367047128,13.66,14.071714659026512,14.0,16.58261516934936,19.3,,,2024-07-19
202 | 201,12.5,12.936023594957735,13.66,14.059078376714258,13.94,16.557178750124066,19.5,,,2024-07-20
203 | 202,12.2,12.925074060882604,13.66,14.048889102307557,13.9,16.533493069916343,20.5,,,2024-07-21
204 | 203,12.7,12.917158567376896,13.6,14.04101402855214,13.7,16.510240085372555,20.1,,,2024-07-22
205 | 204,12.9,12.912337740240242,13.66,14.035190637423229,13.9,16.486687885327825,20.3,,,2024-07-23
206 | 205,12.3,12.910332623101258,13.6,14.03108085230374,13.9,16.463270117122956,20.0,,,2024-07-24
207 | 206,12.4,12.910613568630449,13.5,14.028124724593484,13.7,16.44214134465568,19.5,,,2024-07-25
208 | 207,12.0,12.912957277309172,13.56,14.025768180644404,13.78,16.425082374765832,20.3,,,2024-07-26
209 | 208,12.2,12.917289522482234,13.5,14.023674794709377,13.7,16.411386305399343,19.8,,,2024-07-27
210 | 209,12.7,12.92347335766013,13.66,14.021801786699552,13.84,16.3974090588869,19.2,,,2024-07-28
211 | 210,12.6,12.931161428126353,13.7,14.020246223912807,13.9,16.378711825152635,19.2,,,2024-07-29
212 | 211,12.7,12.939458682819913,13.66,14.019058903055095,13.94,16.352146332727255,19.4,,,2024-07-30
213 | 212,12.9,12.947882636234933,13.780000000000001,14.01823305254081,14.1,16.318025814115373,19.1,,,2024-07-31
214 | 213,12.8,12.95637664370529,13.8,14.017609351030167,14.0,16.27987090776229,19.4,,,2024-08-01
215 | 214,12.7,12.964411052470037,13.5,14.0167440499284,14.04,16.240812784391206,18.7,,,2024-08-02
216 | 215,12.8,12.971454348597781,13.7,14.01522686481477,13.8,16.201881130963923,18.6,,,2024-08-03
217 | 216,13.0,12.977228962149715,13.7,14.012724886448737,13.9,16.162527007429887,18.0,,,2024-08-04
218 | 217,11.9,12.98165704203744,13.6,14.009170968481286,13.84,16.121296210355354,17.9,,,2024-08-05
219 | 218,13.0,12.985090897296367,13.72,14.004628210077298,13.94,16.077202071346004,17.2,,,2024-08-06
220 | 219,12.7,12.988095169816727,13.7,13.999387945445292,13.8,16.0305416680485,16.7,,,2024-08-07
221 | 220,13.2,12.991409298756654,13.6,13.993899261954567,13.84,15.98196946518665,16.7,,,2024-08-08
222 | 221,13.0,12.995910120854708,13.66,13.988897920721552,13.84,15.93168673347453,18.1,,,2024-08-09
223 | 222,12.8,13.00215778458997,13.8,13.985187138982173,13.94,15.879954562830395,19.0,,,2024-08-10
224 | 223,12.1,13.010221258361664,13.66,13.983385462017894,13.94,15.828239776195772,18.9,,,2024-08-11
225 | 224,12.4,13.019786279204158,13.7,13.98381410229784,13.8,15.779139019625106,17.6,,,2024-08-12
226 | 225,12.3,13.030290683336593,13.76,13.986469003047302,13.94,15.735076559483115,17.8,,,2024-08-13
227 | 226,12.8,13.041565333960438,13.6,13.991199894968851,13.8,15.697357363300355,17.8,,,2024-08-14
228 | 227,12.1,13.053434993543563,13.66,13.997702484994464,13.84,15.66581632235521,17.6,,,2024-08-15
229 | 228,12.3,13.065846410320685,13.7,14.005479006784565,14.0,15.63912721116382,17.1,,,2024-08-16
230 | 229,12.7,13.078947548454607,13.66,14.014009106785757,13.84,15.615418691226246,16.8,,,2024-08-17
231 | 230,12.8,13.093112574837102,13.72,14.022899631998039,14.0,15.59255599245552,17.0,,,2024-08-18
232 | 231,12.7,13.10905597305624,13.7,14.031938822488785,14.0,15.567727323548752,16.2,,,2024-08-19
233 | 232,12.9,13.127637259547955,13.860000000000001,14.04122428639655,14.139999999999999,15.539064967696758,16.5,,,2024-08-20
234 | 233,13.3,13.149527081922827,13.9,14.0510371471461,14.2,15.505999079864548,17.2,,,2024-08-21
235 | 234,12.9,13.174519342803588,13.96,14.061585170916851,14.2,15.468914897769098,17.0,,,2024-08-22
236 | 235,13.1,13.201629979807837,13.92,14.072892510452032,14.1,15.428818088087022,16.4,,,2024-08-23
237 | 236,13.2,13.229411316228488,13.8,14.084613831352563,14.1,15.38726202115753,17.1,,,2024-08-24
238 | 237,13.1,13.256346093783757,13.7,14.096017359342067,14.0,15.345904588732846,17.9,,,2024-08-25
239 | 238,13.1,13.280863424905341,13.860000000000001,14.106179761775598,14.0,15.306076037235641,17.8,,,2024-08-26
240 | 239,13.0,13.301811763116364,14.0,14.114254644085996,14.18,15.2690262913685,16.7,,,2024-08-27
241 | 240,12.8,13.318399460728147,14.0,14.119630501688198,14.4,15.236184771314347,16.3,,,2024-08-28
242 | 241,13.3,13.330165327476632,13.860000000000001,14.121919030116729,14.1,15.208520424333376,16.5,,,2024-08-29
243 | 242,13.2,13.336765156760606,13.9,14.12087995396015,14.139999999999999,15.185903548312213,16.1,,,2024-08-30
244 | 243,12.7,13.338052811138432,14.0,14.116511376333538,14.2,15.167845678356093,16.3,,,2024-08-31
245 | 244,13.2,13.334127008438871,13.96,14.10905261115241,14.139999999999999,15.154096426622816,17.0,,,2024-09-01
246 | 245,13.4,13.325613633297237,13.96,14.099006912145818,14.24,15.144498574934529,18.6,,,2024-09-02
247 | 246,13.1,13.313685682093222,14.0,14.086964911772398,14.24,15.138536916750388,17.9,,,2024-09-03
248 | 247,13.3,13.29940474849913,14.0,14.073403291380263,14.3,15.135516376976653,17.9,,,2024-09-04
249 | 248,13.2,13.283767943626186,13.9,14.058544985073057,14.08,15.135064290908542,18.0,,,2024-09-05
250 | 249,13.4,13.2674007936527,13.8,14.042462717068124,14.0,15.137540333965608,17.7,,,2024-09-06
251 | 250,13.4,13.250622850721854,13.8,14.025439810534943,13.9,15.14365913325228,18.3,,,2024-09-07
252 | 251,13.1,13.233689882017815,13.76,14.007975745537514,14.139999999999999,15.15300898137059,17.1,,,2024-09-08
253 | 252,13.1,13.216635019838899,13.6,13.990530551322118,13.8,15.164793336344662,17.2,,,2024-09-09
254 | 253,12.8,13.19955345541633,13.76,13.97340050701313,13.84,15.178147296727447,16.9,,,2024-09-10
255 | 254,12.9,13.182490124386373,13.66,13.956727113040339,14.0,15.191648055351868,16.5,,,2024-09-11
256 | 255,12.5,13.165207194882075,13.46,13.940519162731846,13.88,15.20416345666686,16.2,,,2024-09-12
257 | 256,12.8,13.147997987039515,13.56,13.924928918619807,13.74,15.214622046489522,16.6,,,2024-09-13
258 | 257,12.8,13.13152057837521,13.42,13.910250359683438,13.68,15.221927829977492,16.8,,,2024-09-14
259 | 258,12.9,13.116663352491754,13.6,13.89681144732328,13.8,15.224894749055847,16.6,,,2024-09-15
260 | 259,12.9,13.104223865964068,13.52,13.88482261135011,13.84,15.22293513493288,16.4,,,2024-09-16
261 | 260,12.9,13.094380078999004,13.52,13.874316194888314,13.78,15.216038317891257,15.9,,,2024-09-17
262 | 261,12.2,13.086997733890325,13.52,13.865214010385257,13.74,15.20393333270195,16.4,,,2024-09-18
263 | 262,12.3,13.08157960496009,13.56,13.85741444172509,13.74,15.186598887638217,16.5,,,2024-09-19
264 | 263,12.3,13.07748689933833,13.6,13.850745409400497,13.8,15.163871835480656,17.0,,,2024-09-20
265 | 264,13.0,13.074172802320016,13.860000000000001,13.845006325152218,14.1,15.135907041157536,15.7,,,2024-09-21
266 | 265,13.0,13.070518056180035,13.66,13.839848990626828,13.94,15.103045889344966,16.0,,,2024-09-22
267 | 266,12.5,13.064496485914205,13.66,13.834596764960443,13.8,15.066073182190157,16.0,,,2024-09-23
268 | 267,12.5,13.054042743520474,13.66,13.828324184909855,13.9,15.025746007865369,17.1,,,2024-09-24
269 | 268,12.5,13.037352159252569,13.5,13.820105135374893,13.84,14.982444235995306,16.5,,,2024-09-25
270 | 269,13.0,13.013524125654532,13.6,13.809102166887843,13.88,14.936643648654615,17.1,,,2024-09-26
271 | 270,12.9,12.982266708572071,13.66,13.794730018097281,13.8,14.889596281471936,16.8,,,2024-09-27
272 | 271,13.0,12.943801257360166,13.6,13.7767776035261,13.94,14.843150756434182,16.1,,,2024-09-28
273 | 272,12.7,12.8990222592057,13.6,13.755389763319375,13.8,14.79939333108339,16.4,,,2024-09-29
274 | 273,12.6,12.848712344744156,13.46,13.731117110358857,13.9,14.759705251807944,16.3,,,2024-09-30
275 | 274,12.6,12.793561809088308,13.5,13.704896898506423,13.74,14.725125197962383,16.9,,,2024-10-01
276 | 275,12.7,12.734304233986736,13.6,13.677753841685846,13.8,14.69656725850157,16.7,,,2024-10-02
277 | 276,12.6,12.671724018654778,13.46,13.650388657082441,13.8,14.67403882240778,17.2,,,2024-10-03
278 | 277,12.7,12.6069487940785,13.5,13.62328556738943,13.74,14.656668936180674,16.0,,,2024-10-04
279 | 278,12.7,12.540860482084424,13.4,13.596572792470301,13.7,14.642975981900134,16.7,,,2024-10-05
280 | 279,11.1,12.473816585972722,13.5,13.570008252688664,13.7,14.63217209744939,16.8,,,2024-10-06
281 | 280,11.3,12.405615475770196,13.360000000000001,13.543229683965842,13.54,14.624451473537373,15.5,,,2024-10-07
282 | 281,11.7,12.33694179836044,13.16,13.516185816337558,13.6,14.619598967767521,15.3,,,2024-10-08
283 | 282,11.5,12.269142060717941,13.2,13.489175836507593,13.48,14.617661295827192,16.1,,,2024-10-09
284 | 283,11.4,12.203821075012476,13.1,13.462683192817893,13.44,14.619726881020357,16.6,,,2024-10-10
285 | 284,12.0,12.142357358481835,12.860000000000001,13.437305646289666,13.4,14.626889041524771,16.3,,,2024-10-11
286 | 285,12.1,12.085645156050349,12.9,13.413776778165474,13.28,14.640670343135467,17.4,,,2024-10-12
287 | 286,11.7,12.034474952135348,13.0,13.392964287564268,13.52,14.662785711008357,17.4,,,2024-10-13
288 | 287,11.5,11.989508191113936,13.040000000000001,13.375548330473977,13.74,14.693872286011606,17.8,,,2024-10-14
289 | 288,11.7,11.951171611482383,13.16,13.362080296932044,13.7,14.732908276056742,17.5,,,2024-10-15
290 | 289,10.6,11.919328179482045,13.2,13.35283281291418,13.5,14.777849256856115,15.7,,,2024-10-16
291 | 290,11.5,11.893319459458343,13.2,13.34775760024937,13.5,14.8264720341374,15.1,,,2024-10-17
292 | 291,11.7,11.871999935316914,13.26,13.346454249064909,13.5,14.876596219902302,15.1,,,2024-10-18
293 | 292,11.4,11.853893133131129,13.16,13.348178919884914,13.7,14.926178002007308,15.2,,,2024-10-19
294 | 293,11.2,11.837620530186143,13.0,13.352070886248471,13.48,14.973205977043621,16.2,,,2024-10-20
295 | 294,11.8,11.822202121517181,13.0,13.357431550729347,13.7,15.016628918994808,15.6,,,2024-10-21
296 | 295,11.6,11.807907505181461,12.98,13.3639521951104,13.639999999999999,15.056032946781379,15.5,,,2024-10-22
297 | 296,11.8,11.795091405786145,13.02,13.371670121297273,13.38,15.091813147993532,15.1,,,2024-10-23
298 | 297,11.0,11.783419691724697,13.06,13.380751544034652,13.54,15.124474549447212,15.5,,,2024-10-24
299 | 298,11.6,11.772764535557277,12.9,13.391467565117843,13.639999999999999,15.154502145768252,16.3,,,2024-10-25
300 | 299,11.5,11.764212792830952,12.7,13.404282950482,13.74,15.182431242638797,16.4,,,2024-10-26
301 | 300,11.2,11.759180849170313,12.7,13.419614337230541,13.24,15.208657749386195,15.8,,,2024-10-27
302 | 301,11.7,11.758747464243758,13.06,13.437537443656785,13.6,15.233368534045537,15.8,,,2024-10-28
303 | 302,11.4,11.763386826904377,13.1,13.45764858498551,13.44,15.256514801625762,15.8,,,2024-10-29
304 | 303,11.3,11.772507791405403,13.16,13.47911919623865,13.6,15.277419153181736,16.0,,,2024-10-30
305 | 304,11.2,11.785024441184282,13.1,13.500887210848271,13.84,15.294842258189078,16.1,,,2024-10-31
306 | 305,10.6,11.79982474508492,13.4,13.521750549702267,13.74,15.307168647831313,15.8,,,2024-11-01
307 | 306,10.8,11.815241961488743,13.32,13.540515210793021,13.84,15.31252410825325,15.3,,,2024-11-02
308 | 307,11.4,11.830327810592376,13.26,13.556437050445968,13.88,15.30949502253787,15.7,,,2024-11-03
309 | 308,11.5,11.845234460110465,13.3,13.569137676099896,13.88,15.2973387695649,15.5,,,2024-11-04
310 | 309,11.1,11.860534686303918,13.5,13.578677929342843,13.94,15.276334131845177,15.5,,,2024-11-05
311 | 310,10.8,11.876138267808836,13.6,13.585473690658556,13.94,15.248185825525173,15.7,,,2024-11-06
312 | 311,11.1,11.891537536952738,13.66,13.59019650701654,13.94,15.215295839120595,15.5,,,2024-11-07
313 | 312,11.6,11.90663319517264,13.6,13.5934937954664,13.94,15.180084869715325,15.4,,,2024-11-08
314 | 313,11.8,11.921378356908152,13.76,13.595809686972444,14.2,15.144573781507574,15.8,,,2024-11-09
315 | 314,11.8,11.935211407605664,13.7,13.597277573095354,14.08,15.109850559543466,16.0,,,2024-11-10
316 | 315,11.5,11.947084137050354,13.3,13.597785510513699,14.139999999999999,15.076419969477518,16.1,,,2024-11-11
317 | 316,11.8,11.95493417712014,13.46,13.597131477197127,13.74,15.044772263836213,16.6,,,2024-11-12
318 | 317,11.5,11.956711371476905,13.3,13.594727419318145,13.54,15.01479119095992,17.2,,,2024-11-13
319 | 318,11.7,11.951379305270068,13.3,13.589889862323503,13.7,14.985741272547013,16.4,,,2024-11-14
320 | 319,11.6,11.938688782865885,13.32,13.582135239125979,13.74,14.95581693890987,16.2,,,2024-11-15
321 | 320,12.0,11.919106320082328,13.12,13.571284859448344,13.6,14.923762484259608,15.3,,,2024-11-16
322 | 321,11.9,11.892960274872104,13.280000000000001,13.557260865897854,13.7,14.889671114415332,15.3,,,2024-11-17
323 | 322,11.5,11.86105874509787,13.46,13.540053668515363,13.94,14.85466565270179,15.8,,,2024-11-18
324 | 323,11.6,11.824589136516172,13.56,13.519821851743833,14.0,14.819676709345089,15.5,,,2024-11-19
325 | 324,11.4,11.784001620910235,13.46,13.496939034850856,13.92,14.786172713617397,15.1,,,2024-11-20
326 | 325,11.2,11.739199708053508,13.5,13.47210410695108,13.9,14.755907881429705,15.2,,,2024-11-21
327 | 326,11.3,11.690674315643655,13.5,13.446372820734453,14.0,14.731107463831497,15.2,,,2024-11-22
328 | 327,10.8,11.640260431467192,13.66,13.421073001287473,13.94,14.713627327331634,15.3,,,2024-11-23
329 | 328,11.4,11.590546623655017,13.5,13.397434626761523,13.7,14.703674357468717,15.5,,,2024-11-24
330 | 329,11.5,11.544088300563503,13.3,13.376263052666042,13.74,14.700071498591914,16.3,,,2024-11-25
331 | 330,11.1,11.50312490981042,13.4,13.357676506633414,13.7,14.700434485565559,15.8,,,2024-11-26
332 | 331,9.6,11.469027526133182,13.0,13.341252753525533,13.3,14.70239059106044,15.4,,,2024-11-27
333 | 332,9.3,11.441859161007853,12.76,13.326348886546889,13.139999999999999,14.704550363685163,15.2,,,2024-11-28
334 | 333,10.2,11.420227173210925,12.860000000000001,13.31240012848497,13.34,14.706602429694273,15.1,,,2024-11-29
335 | 334,10.7,11.403071304316121,12.6,13.298871320905244,13.459999999999999,14.7087179244279,15.1,,,2024-11-30
336 | 335,11.2,11.39029692962534,12.8,13.285202276500282,13.56,14.710960254872468,15.3,,,2024-12-01
337 | 336,11.2,11.381860311937828,13.22,13.271222240368038,13.7,14.713020460089071,15.1,,,2024-12-02
338 | 337,10.2,11.377764488265221,12.8,13.257221995165038,13.48,14.714468190450194,14.9,,,2024-12-03
339 | 338,9.7,11.377125176456476,12.96,13.243830801304055,13.44,14.715351737196134,15.0,,,2024-12-04
340 | 339,9.9,11.378858581147927,12.66,13.232097086104703,13.5,14.716230216073866,15.6,,,2024-12-05
341 | 340,10.2,11.38250004339652,12.96,13.223308431103595,13.4,14.7179733410225,15.7,,,2024-12-06
342 | 341,10.1,11.388937561971119,13.08,13.218391784431027,13.5,14.72132130729919,14.9,,,2024-12-07
343 | 342,10.8,11.399192792708481,13.4,13.21730412611983,13.8,14.725850076055625,14.9,,,2024-12-08
344 | 343,11.2,11.413361045063628,13.46,13.218978750172887,13.9,14.730490895477828,14.9,,,2024-12-09
345 | 344,10.9,11.430914635951563,13.3,13.22189632281183,13.84,14.734070007566306,14.8,,,2024-12-10
346 | 345,10.8,11.450572593661766,13.0,13.22494827534628,13.92,14.736007085306468,15.2,,,2024-12-11
347 | 346,11.0,11.47137361129704,13.42,13.2279630836518,13.92,14.736606834130187,15.4,,,2024-12-12
348 | 347,10.8,11.493046960168998,13.06,13.23138023503523,13.5,14.736491268728944,15.2,,,2024-12-13
349 | 348,10.4,11.51539420488824,12.76,13.235378449014789,13.319999999999999,14.73642792862513,15.4,,,2024-12-14
350 | 349,10.2,11.537882822566095,13.06,13.239944044584323,13.34,14.736949620761134,14.9,,,2024-12-15
351 | 350,10.8,11.560008086694598,12.96,13.245207270086654,13.54,14.738392902537758,14.7,,,2024-12-16
352 | 351,10.1,11.58144655063202,13.14,13.251225576731182,13.54,14.74149827758236,15.7,,,2024-12-17
353 | 352,9.6,11.603230573159527,13.14,13.263464952829379,13.54,14.754245426865007,15.2,,,2024-12-18
354 | 353,10.6,11.623714386175303,13.0,13.275235873609844,13.54,14.76829646961934,15.5,,,2024-12-19
355 | 354,11.2,11.643385524280504,12.780000000000001,13.286288948299918,13.54,14.782837110873363,15.9,,,2024-12-20
356 | 355,11.6,11.662718039266391,12.66,13.296964804001922,13.639999999999999,14.79769077354831,14.8,,,2024-12-21
357 | 356,11.6,11.681993059141078,12.96,13.307567131679454,13.52,14.812782777203852,14.5,,,2024-12-22
358 | 357,11.1,11.701346399430754,12.8,13.318325153104414,13.639999999999999,14.828127871093283,15.0,,,2024-12-23
359 | 358,10.4,11.72084480748282,12.96,13.329413125077108,13.52,14.843797514183581,15.5,,,2024-12-24
360 | 359,10.7,11.740496001354392,13.02,13.340965143645224,13.719999999999999,14.859863537014268,15.4,,,2024-12-25
361 | 360,11.2,11.760329097605695,13.4,13.353077070244932,13.94,14.876353355908037,15.2,,,2024-12-26
362 | 361,11.6,11.78038533631914,13.7,13.365802185982274,14.0,14.893274252605424,15.6,,,2024-12-27
363 | 362,11.8,11.800679540848954,13.360000000000001,13.379142110090438,13.8,14.91062843749329,15.0,,,2024-12-28
364 | 363,11.5,11.821164986794006,13.26,13.393053967887086,13.88,14.92842852762563,16.3,,,2024-12-29
365 | 364,11.6,11.841757264513658,12.9,13.407456334262498,13.38,14.946701690060483,15.7,,,2024-12-30
366 | 365,11.2,11.862368445224284,13.0,13.422242002008597,13.74,14.965457694562406,15.4,,,2024-12-31
367 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Lato:wght@200&display=swap");
2 |
3 | html {
4 | font-family: "Lato, Arial, sans-serif";
5 | font-size: 1.2rem;
6 | }
7 |
8 | /* Widget labels */
9 | .css-16idsys p,
10 | .e1y5xkzn3 p,
11 | .e1nzilvr4 p,
12 | .e121c1cl0 p {
13 | font-size: 1.1rem;
14 | }
15 |
16 | /* Margin below widget labels */
17 | .css-81oif8,
18 | .e1jram340 {
19 | margin-bottom: 0.1rem;
20 | }
21 |
22 | /* Main container */
23 | .css-z5fcl4 {
24 | padding-top: 1.5rem;
25 | }
26 |
27 | div.css-j5r0tf {
28 | min-width: 285px;
29 | }
30 |
31 | /* Make Folium map responsive */
32 | [title~="st.iframe"] {
33 | min-height: 500px;
34 | width: 100%;
35 | }
36 |
37 | /* Remove blank space at top and bottom */
38 | .block-container {
39 | padding-top: 0rem;
40 | padding-bottom: 0rem;
41 | }
42 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yotkadata/meteo_hist/d03b3b6d3887b85886685b69a07a5972adf8e068/tests/__init__.py
--------------------------------------------------------------------------------
/tests/test_base.py:
--------------------------------------------------------------------------------
1 | """
2 | Unit tests (pytest) for the MeteoHist class.
3 | """
4 |
5 | import datetime as dt
6 | from pathlib import Path
7 |
8 | import pandas as pd
9 | import pytest
10 | import requests
11 |
12 | from meteo_hist.base import MeteoHist
13 |
14 |
15 | @pytest.fixture(name="meteohist_instance_default")
16 | def fixture_meteohist_instance_default():
17 | """
18 | Fixture to create an instance of MeteoHist for testing.
19 | """
20 | coords = (52.5170365, 13.3888599) # Berlin coordinates
21 |
22 | # Construct the path to the sample data file relative to this test file
23 | sample_data_path = (
24 | Path(__file__).parent.parent
25 | / "sample_data/raw/temperature_mean-berlin-germany.csv"
26 | )
27 |
28 | # Use sample data to prevent network requests and API rate limits
29 | data = pd.read_csv(sample_data_path, parse_dates=["date"])
30 |
31 | return MeteoHist(coords, data=data)
32 |
33 |
34 | def test_get_data():
35 | """
36 | Test if data is fetched correctly on initialization.
37 | """
38 | coords = (52.5170365, 13.3888599) # Berlin coordinates
39 |
40 | class_instance = MeteoHist(coords)
41 |
42 | assert isinstance(
43 | class_instance.data_raw, pd.DataFrame
44 | ), f"data_raw attribute should be a Pandas DataFrame, but is {type(class_instance.data_raw)}"
45 | assert isinstance(
46 | class_instance.data, pd.DataFrame
47 | ), f"data attribute should be a Pandas DataFrame, but is {type(class_instance.data)}"
48 |
49 |
50 | def test_year_attr(meteohist_instance_default):
51 | """
52 | Test the if MeteoHist with default parameters correctly sets year attribute.
53 | """
54 | year = meteohist_instance_default.year
55 | current_year = dt.datetime.now().year
56 |
57 | assert (
58 | year == current_year
59 | ), f"Year attribute should be current year {current_year}, {year} found."
60 |
61 |
62 | def test_ref_period_attr(meteohist_instance_default):
63 | """
64 | Test the if MeteoHist with default parameters correctly sets reference_period attribute.
65 | """
66 | reference_period = meteohist_instance_default.reference_period
67 |
68 | assert isinstance(
69 | reference_period, tuple
70 | ), f"reference_period attribute should be a tuple, {type(reference_period)} found."
71 |
72 | assert isinstance(reference_period[0], int) and isinstance(
73 | reference_period[1], int
74 | ), (
75 | "reference_period attribute should be a tuple of two integers, "
76 | f"{type(reference_period[0])} and {type(reference_period[1])} found."
77 | )
78 |
79 | assert len(reference_period) == 2, (
80 | "reference_period attribute should be a tuple of two integers, "
81 | f"{len(reference_period)} found."
82 | )
83 |
84 | assert (
85 | reference_period[1] - reference_period[0] == 29
86 | ), "reference_period should be 30 years long."
87 |
88 | valid_start_years = [1941, 1951, 1961, 1971, 1981, 1991]
89 | assert reference_period[0] in valid_start_years, (
90 | "reference_period should start in one of the following years: "
91 | f"{valid_start_years}, {reference_period[0]} found."
92 | )
93 |
94 | valid_end_years = [1970, 1980, 1990, 2000, 2010, 2020]
95 | assert reference_period[1] in valid_end_years, (
96 | "reference_period should end in one of the following years: "
97 | f"{valid_end_years}, {reference_period[1]} found."
98 | )
99 |
100 |
101 | def test_transform_df_dates(meteohist_instance_default):
102 | """
103 | Test the if MeteoHist with default parameters correctly transforms dates.
104 | """
105 |
106 | assert isinstance(meteohist_instance_default.data, pd.DataFrame), (
107 | "data attribute should be a Pandas DataFrame, "
108 | f"{type(meteohist_instance_default.data)} found."
109 | )
110 |
111 |
112 | def test_transform_df_values():
113 | """
114 | Test the if MeteoHist correctly keeps values in line.
115 | """
116 |
117 | coords = (52.5170365, 13.3888599) # Berlin coordinates
118 | instance = MeteoHist(coords, year=2020)
119 |
120 | # Get raw data
121 | df_raw = instance.data_raw
122 |
123 | # Transformed data
124 | df_t = instance.data
125 |
126 | # Pick some dates
127 | dates = ["2020-02-28", "2020-01-01", "2020-07-06", "2020-09-16", "2020-12-31"]
128 |
129 | # Compare values for each date
130 | for date in dates:
131 | value_r = df_raw[df_raw["date"] == date]["value"].values[0]
132 | value_t = df_t[df_t["date"] == date]["2020"].values[0]
133 |
134 | assert (
135 | value_r == value_t
136 | ), f"Value for {date} should be {value_r}, {value_t} found."
137 |
138 |
139 | def test_get_location(meteohist_instance_default):
140 | """
141 | Test the get_location method for valid coordinates.
142 | """
143 | location = meteohist_instance_default.get_location((52.5170365, 13.3888599))
144 | assert location is not None, "Location should not be None for valid coordinates."
145 |
146 |
147 | def test_get_data_with_invalid_coords(meteohist_instance_default):
148 | """
149 | Test get_data method with invalid coordinates.
150 | """
151 | with pytest.raises(requests.exceptions.RequestException):
152 | meteohist_instance_default.get_data(coords=(999, 999))
153 |
154 |
155 | def test_dayofyear_to_date(meteohist_instance_default):
156 | """
157 | Test dayofyear_to_date method.
158 | """
159 | date = meteohist_instance_default.dayofyear_to_date(2020, 60)
160 | assert date == dt.datetime(
161 | 2020, 2, 29
162 | ), "Date should be Feb 29, 2020 for day 60 of a leap year."
163 |
164 |
165 | def test_dayofyear_to_date_non_leap(meteohist_instance_default):
166 | """
167 | Test dayofyear_to_date method for non-leap year.
168 | """
169 | date = meteohist_instance_default.dayofyear_to_date(2021, 60)
170 | assert date == dt.datetime(
171 | 2021, 3, 1
172 | ), "Date should be Mar 1, 2021 for day 60 of a non-leap year."
173 |
174 |
175 | def test_get_stats_for_year(meteohist_instance_default):
176 | """
177 | Test get_stats_for_year method.
178 | """
179 | stats = meteohist_instance_default.get_stats_for_year(2020)
180 | assert isinstance(stats, dict), "Stats should be returned as a dictionary."
181 |
182 |
183 | def test_get_stats_for_period(meteohist_instance_default):
184 | """
185 | Test get_stats_for_period method.
186 | """
187 | stats = meteohist_instance_default.get_stats_for_period((2010, 2020))
188 | assert isinstance(stats, pd.DataFrame), "Stats should be returned as a DataFrame."
189 |
190 |
191 | def test_remove_leap_days(meteohist_instance_default):
192 | """
193 | Test remove_leap_days method.
194 | """
195 | data_with_leap = pd.DataFrame(
196 | {
197 | "date": pd.to_datetime(["2020-02-28", "2020-02-29", "2020-03-01"]),
198 | "value": [1, 2, 3],
199 | }
200 | )
201 | data_without_leap = meteohist_instance_default.remove_leap_days(data_with_leap)
202 | assert len(data_without_leap) == 2, "Data should not contain leap day."
203 |
204 |
205 | def test_transform_data_smooth(meteohist_instance_default):
206 | """
207 | Test transform_data method with smoothing.
208 | """
209 | data = meteohist_instance_default.data
210 | assert "mean" in data.columns, "Transformed data should include mean column."
211 | assert "p05" in data.columns, "Transformed data should include p05 column."
212 | assert "p95" in data.columns, "Transformed data should include p95 column."
213 |
214 |
215 | def test_get_y_limits(meteohist_instance_default):
216 | """
217 | Test get_y_limits method.
218 | """
219 | min_y, max_y = meteohist_instance_default.get_y_limits()
220 | assert isinstance(min_y, float) and isinstance(
221 | max_y, float
222 | ), "Y limits should be floats."
223 |
224 |
225 | def test_get_min_max(meteohist_instance_default):
226 | """
227 | Test get_min_max method.
228 | """
229 | min_val = meteohist_instance_default.get_min_max((1, 365), "min")
230 | max_val = meteohist_instance_default.get_min_max((1, 365), "max")
231 | assert isinstance(min_val, float) and isinstance(
232 | max_val, float
233 | ), "Min and max values should be floats."
234 |
--------------------------------------------------------------------------------