Proof-of-Concept implementation of a community-driven system for extreme weather events reporting. The aim of the project is to create a system that allows users to report the occurrence of various types of weather phenomena and their intensity. The system employs algorithms to interpret incoming reports and identify areas for which a weather advisory or warning should be issued. The system is inspired by the Downdetector project, which allows users to report issues with popular websites.
@online{weatherwhistle-krzysztofz01,
author = {Krzysztof Zoń},
title = {Weather Whistle - Proof-of-Concept Community-Driven Weather Reporting},
year = {2025},
url = {https://github.com/Krzysztofz01/weather-whistle},
urldate = {2025-10-20}
}The software (without seeding data) is licensed under AGPL. Look at the LICENSE file.
The software was developed without the use of LLMs or AI assistants.
The system is a Web API service implemented in C# as part of the .NET platform. The web application service is based on ASP.NET. Due to the requirements for storing spatial data, a PostgreSQL database with the PostGIS extension was used. Communication with the database is handled using the Entity Framework Core ORM. The implementation of the business logic related to spatial data was carried out using the NetTopologySuite library, which provides GIS type definitions and algorithms for their processing. To facilitate the implementation of the mediator pattern in the application layer, the MediatR library was used. Validation of individual request data was implemented using FluentValidation, which enables the use of the so-called “Fluent API” for defining data validation rules. The Serilog library serves as the logging abstraction implementation. To improve code quality, the SonarAnalyzer static code analyzer was used. The service supports automatic generation of OpenAPI documentation. The application is built with support for Linux distribution deployment and is containerized using the Podman tool (an alternative to Docker).
The spatial data used to seed the system can be found in geo/ww_geospatial_seed.blob.zip. This is a collection of files containing the geometries of administrative divisions in Poland. The geometries have been simplified, converted to the EPSG:4326 projection, and saved in WKT format. The data was generated based on geometries from the geoportal.gov.pl - PRG WFS service.
-
N1 - Providing users with information about the areas covered by the system’s operation.
-
N2 - The ability to report a predefined weather phenomenon and its intensity.
-
N3 - Obtaining information regarding the occurrence of weather phenomena within a given area covered by the system.
-
F1 - Support for handling GIS data in order to import spatial data from the GeoPortal platform.
-
F2 - Compliance of spatial data with the OGC standard.
-
F3 - Distinguishing unauthenticated users without de-anonymizing them.
-
F4 - Protection of the reporting system against sabotage (a large number of reports submitted by a single user).
The system was developed in accordance with the Domain Driven Design (DDD) approach, where the first step was modeling the fundamental operations of the system, namely the business logic. These processes revealed the types of data the system would need to handle.
The system consists of five libraries:
- WeatherWhistle.Domain - business logic and entity definitions
- WeatherWhistle.Infrastructure - implementation of integrations with external services (database)
- WeatherWhistle.Application - implementation of use cases and operations between entities
- WeatherWhistle.API - implementation of the Web API service, exposing system functionality through a web application
- WeatherWhistle.Common - shared code (types, helper functions)
The system entities are divided into two groups:
- Geospatial - physical areas subject to reports
- Submissions - weather phenomenon reports
Entities represent the fundamental representations of data types processed by the system. Entity fields are implemented as Value Objects, which are immutable and define their own validation logic. Due to the nature of the system, entities themselves may also be treated as immutable objects representing an unchangeable state at a given point in time. Entity creation is implemented using the factory pattern.
Entities belonging to the Geospatial group include: Country, Province, County, and Town, which model a country, voivodeship, county, and locality respectively. They are represented by their name, geometry, and subordinate organizational units (e.g., a country contains multiple provinces). The primary entity belonging to the Submissions group is Submission, which stores information about a predefined weather phenomenon and its intensity, the submission timestamp, the identifier of the county related to the submission, and the user identifier. This section of the code also contains helper functions responsible for analyzing collections of submissions for a specific organizational unit in order to determine an appropriate status. A status can be determined for each organizational unit and consists of identifying the most frequently reported weather phenomenon and its intensity within a time range defined by the corresponding execution schedule. The domain layer also defines all errors that may occur during system operation.
Due to the DDD approach, the database structure (or even the data model itself) was not the first element to be defined. Therefore, the functionality for generating the database structure directly from the code was used (the code-first approach). As a result, the infrastructure layer contains definitions of tables and relationships for individual entities, as well as database migration code.
The application layer is based on the CQRS approach, where a separation is introduced between read logic and logic responsible for modifying the system state. In this project, the separation consists of dividing classes responsible for specific operations (this separation could also be implemented, for example, using separate data sources). The mediator pattern is particularly useful here, where individual requests are represented by Commands and Queries, which are then routed to appropriate Handlers after validation by Validators. Data access is performed through the database context provided by the ORM, where the context itself implements patterns such as Repository and Unit of Work.
An example of a query is a request returning all counties within a given province. Such a request enters the system together with the province identifier. The first step is validation of the input parameter - in this case, verification of the GUID v4 format. The next step is building a database query using the LINQ2SQL Fluent API. The query disables change tracking, which is unnecessary in the context of read operations, filters by the specified province, and performs an initial projection into DTOs. The query result is then mapped to the final DTO, including appropriate geometry mapping. In the case of status queries, additional logic is involved for caching results in memory to avoid redundant operations.
An example of a command is the process of creating a new submission. A new submission requires providing data regarding the weather phenomenon, its intensity, and the province to which the report applies. Input data is validated in terms of format correctness and whether the values belong to the allowed set of values. A fundamental feature is the ability to represent the reporting user without requiring authentication or requiring them to provide personally identifiable information - this is achieved using a special user checksum. The user checksum is the result of the SHA256 hash function applied to the byte sequence formed by concatenating the user’s IP address and the browser User-Agent string. Before saving the submission, the system verifies whether a user with the same checksum has already submitted a report within the last two minutes. After verifying that the specified county exists in the system, the submission entity is created and stored in the database.
The service includes functionality for populating spatial data from JSON files. The seeding process involves interpreting JSON files representing organizational units that contain defined names and geometries in WKT format. The service is also equipped with functionality for generating several demonstration submissions to showcase the system’s behavior.
The API layer contains the system entry point, where configuration is initialized and services are registered in a way that supports dependency injection. The layer also includes controller definitions along with all API endpoints.