areaFluForecast 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. areafluforecast-0.1.0/LICENSE +21 -0
  2. areafluforecast-0.1.0/PKG-INFO +251 -0
  3. areafluforecast-0.1.0/README.md +214 -0
  4. areafluforecast-0.1.0/pyproject.toml +55 -0
  5. areafluforecast-0.1.0/setup.cfg +4 -0
  6. areafluforecast-0.1.0/src/areaFluForecast.egg-info/PKG-INFO +251 -0
  7. areafluforecast-0.1.0/src/areaFluForecast.egg-info/SOURCES.txt +51 -0
  8. areafluforecast-0.1.0/src/areaFluForecast.egg-info/dependency_links.txt +1 -0
  9. areafluforecast-0.1.0/src/areaFluForecast.egg-info/entry_points.txt +2 -0
  10. areafluforecast-0.1.0/src/areaFluForecast.egg-info/requires.txt +23 -0
  11. areafluforecast-0.1.0/src/areaFluForecast.egg-info/top_level.txt +1 -0
  12. areafluforecast-0.1.0/src/areafluforecast/__init__.py +28 -0
  13. areafluforecast-0.1.0/src/areafluforecast/adjacency.py +172 -0
  14. areafluforecast-0.1.0/src/areafluforecast/backtest.py +247 -0
  15. areafluforecast-0.1.0/src/areafluforecast/checkpoint.py +41 -0
  16. areafluforecast-0.1.0/src/areafluforecast/city.py +337 -0
  17. areafluforecast-0.1.0/src/areafluforecast/cli.py +114 -0
  18. areafluforecast-0.1.0/src/areafluforecast/climatology.py +78 -0
  19. areafluforecast-0.1.0/src/areafluforecast/configs.py +225 -0
  20. areafluforecast-0.1.0/src/areafluforecast/datasets.py +82 -0
  21. areafluforecast-0.1.0/src/areafluforecast/engine.py +359 -0
  22. areafluforecast-0.1.0/src/areafluforecast/forecast.py +175 -0
  23. areafluforecast-0.1.0/src/areafluforecast/graphs.py +157 -0
  24. areafluforecast-0.1.0/src/areafluforecast/intervals.py +410 -0
  25. areafluforecast-0.1.0/src/areafluforecast/models.py +227 -0
  26. areafluforecast-0.1.0/src/areafluforecast/plots.py +45 -0
  27. areafluforecast-0.1.0/src/areafluforecast/progress.py +55 -0
  28. areafluforecast-0.1.0/src/areafluforecast/sample_data/boston/README.md +15 -0
  29. areafluforecast-0.1.0/src/areafluforecast/sample_data/boston/centroids.csv +15 -0
  30. areafluforecast-0.1.0/src/areafluforecast/sample_data/boston/city.json +20 -0
  31. areafluforecast-0.1.0/src/areafluforecast/sample_data/boston/edges.csv +45 -0
  32. areafluforecast-0.1.0/src/areafluforecast/sample_data/boston/rates.csv +437 -0
  33. areafluforecast-0.1.0/src/areafluforecast/sample_data/boston/weather.csv +6105 -0
  34. areafluforecast-0.1.0/src/areafluforecast/sample_data/chicago/README.md +27 -0
  35. areafluforecast-0.1.0/src/areafluforecast/sample_data/chicago/centroids.csv +59 -0
  36. areafluforecast-0.1.0/src/areafluforecast/sample_data/chicago/city.json +15 -0
  37. areafluforecast-0.1.0/src/areafluforecast/sample_data/chicago/edges.csv +116 -0
  38. areafluforecast-0.1.0/src/areafluforecast/sample_data/chicago/rates.csv +366 -0
  39. areafluforecast-0.1.0/src/areafluforecast/sample_data/chicago/weather.csv +21171 -0
  40. areafluforecast-0.1.0/src/areafluforecast/samples.py +214 -0
  41. areafluforecast-0.1.0/src/areafluforecast/scoring.py +178 -0
  42. areafluforecast-0.1.0/src/areafluforecast/timing.py +142 -0
  43. areafluforecast-0.1.0/src/areafluforecast/training.py +253 -0
  44. areafluforecast-0.1.0/src/areafluforecast/tune.py +53 -0
  45. areafluforecast-0.1.0/src/areafluforecast/weather.py +146 -0
  46. areafluforecast-0.1.0/src/areafluforecast/windows.py +120 -0
  47. areafluforecast-0.1.0/tests/test_adjacency.py +72 -0
  48. areafluforecast-0.1.0/tests/test_city.py +105 -0
  49. areafluforecast-0.1.0/tests/test_research_parity.py +241 -0
  50. areafluforecast-0.1.0/tests/test_scoring.py +75 -0
  51. areafluforecast-0.1.0/tests/test_smoke.py +134 -0
  52. areafluforecast-0.1.0/tests/test_timing.py +65 -0
  53. areafluforecast-0.1.0/tests/test_weather.py +22 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mateo Biggs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,251 @@
1
+ Metadata-Version: 2.4
2
+ Name: areaFluForecast
3
+ Version: 0.1.0
4
+ Summary: Neighborhood-scale influenza forecasts for public health departments: a graph neural network, a tuned LSTM and Cola-GNN, each checked against persistence on your own data.
5
+ Author-email: Mateo Biggs <biggs.m@northeastern.edu>
6
+ License-Expression: MIT
7
+ Project-URL: Source, https://github.com/CodeMateo15/areaFluForecast
8
+ Project-URL: Documentation, https://areafluforecast.readthedocs.io
9
+ Keywords: influenza,forecasting,graph neural network,public health,epidemiology,FluSight
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Intended Audience :: Healthcare Industry
13
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: numpy>=1.24
18
+ Requires-Dist: pandas>=2.0
19
+ Requires-Dist: scipy>=1.10
20
+ Requires-Dist: torch>=2.1
21
+ Requires-Dist: torch-geometric>=2.4
22
+ Requires-Dist: requests>=2.28
23
+ Provides-Extra: plot
24
+ Requires-Dist: matplotlib>=3.7; extra == "plot"
25
+ Provides-Extra: geo
26
+ Requires-Dist: shapely>=2.0; extra == "geo"
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7; extra == "dev"
29
+ Requires-Dist: matplotlib>=3.7; extra == "dev"
30
+ Requires-Dist: shapely>=2.0; extra == "dev"
31
+ Provides-Extra: docs
32
+ Requires-Dist: mkdocs>=1.6; extra == "docs"
33
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
34
+ Requires-Dist: mkdocstrings[python]>=0.26; extra == "docs"
35
+ Requires-Dist: mkdocs-jupyter>=0.25; extra == "docs"
36
+ Dynamic: license-file
37
+
38
+ # areaFluForecast
39
+
40
+ **Documentation: https://areafluforecast.readthedocs.io**
41
+
42
+ Neighborhood-scale influenza forecasts, 1 to 4 weeks ahead, for public health
43
+ departments. You bring weekly flu rates for your areas (neighborhoods, ZIP-code
44
+ groups, districts) and their boundaries. The package trains three models, checks
45
+ each against the simplest possible forecast on your own past seasons, and only
46
+ then gives you forecasts, with honest uncertainty bands.
47
+
48
+ The three models:
49
+ - **`gnn_st`**: a graph neural network that uses which areas border each other
50
+ and whose flu curves move together. This is the model from the accompanying paper.
51
+ - **`lstm`**: a recurrent network that sees every area's series but no map.
52
+ - **`colagnn`**: Cola-GNN (Deng et al. 2020), a published graph model for flu.
53
+ This is our reimplementation from the paper, not the authors' code.
54
+
55
+ Every model is compared with **persistence**, "next week looks like this week".
56
+ A forecaster that can't beat that on your data has learned nothing you can use.
57
+ The package tells you, for each model and horizon, whether it did.
58
+
59
+ > **What the evidence says.** In Boston the graph model's error was 18–28% below
60
+ > persistence on seasons it never saw. It tied a carefully tuned LSTM at 1 week
61
+ > and led it on average at 2–4 weeks. In Columbus, with only one unseen season,
62
+ > its error was 12–23% below persistence on average, but that lead was not
63
+ > statistically clear. Your city may be either, and the backtest is how you
64
+ > find out.
65
+
66
+ ## Install
67
+
68
+ ```bash
69
+ pip install areaFluForecast # once published; until then:
70
+ pip install git+https://github.com/CodeMateo15/areaFluForecast
71
+ pip install "areaFluForecast[plot]" # adds plotting
72
+ ```
73
+
74
+ Python 3.10+, CPU only. Installing the package also installs PyTorch and
75
+ PyTorch Geometric.
76
+
77
+ ## Try it on Boston
78
+
79
+ ```python
80
+ import areafluforecast as af
81
+
82
+ city = af.load_sample("boston")
83
+ print(city.summary())
84
+ bt = af.backtest(city, seasons=1, horizons=(1, 2), configs={"gnn_st": {"n_seeds": 3}})
85
+ fc = af.forecast(city, horizons=(1, 2))
86
+ fc.summary()
87
+ ```
88
+
89
+ Or from the shell: `areaflu backtest --sample boston --seasons 1 --quick`.
90
+
91
+ **Worked examples:** [`examples/`](examples/) has five notebooks, saved with
92
+ their outputs:
93
+ - a Boston quickstart
94
+ - bringing your own files
95
+ - a city with only two years of data
96
+ - reading the results and choosing a model
97
+ - a real city (Chicago) built from public open data
98
+
99
+ ## Use your own data
100
+
101
+ You need two things.
102
+
103
+ **1. Weekly rates**, as a CSV. Either long format:
104
+
105
+ | week | area | rate |
106
+ |---|---|---|
107
+ | 2024-01-07 | Riverside | 41.2 |
108
+ | 2024-01-07 | Old Town | 28.9 |
109
+
110
+ or wide format, with one column per area and the week start date first. Rates
111
+ per 100,000 residents are expected. Leave missing or suppressed weeks blank.
112
+ Don't fill them with 0. You need at least two years of data, and three or more
113
+ is better. If you have counts, `af.rates_from_counts(counts, population)`
114
+ converts them.
115
+
116
+ **2. The map**, given one of two ways:
117
+ - a GeoJSON boundary file whose features carry the area name (`id_col`). The
118
+ package works out which areas share a border.
119
+ - or a CSV edge list with one row per pair of bordering areas.
120
+
121
+ ```python
122
+ city = af.City.from_files("rates.csv", areas="areas.geojson", id_col="NAME",
123
+ flu_months=(10, 11, 12, 1, 2, 3))
124
+ city.fetch_weather() # optional: Open-Meteo, free, no API key
125
+ print(city.summary()) # check this before training
126
+ ```
127
+
128
+ Southern-Hemisphere cities pass their own flu months, e.g. `(4, 5, 6, 7, 8, 9)`.
129
+
130
+ **Optional inputs:**
131
+ - **Anchors** are neighbouring towns or water that border your areas but have
132
+ no data. List them in `anchors=[...]`, and the edge list or boundary file
133
+ supplies their borders.
134
+ - **Static area characteristics** (a CSV with an `area` column). They are off
135
+ by default, because in the paper they did not help.
136
+
137
+ ## The workflow
138
+
139
+ ```python
140
+ bt = af.backtest(city) # would it have worked here? (last 2 complete seasons)
141
+ bt.report() # each model vs persistence, per horizon
142
+ fc = af.forecast(city) # weeks 1-4 after your last observed week
143
+ fc.to_csv("forecast.csv") # median, 95% band, 23 FluSight quantiles per area
144
+ fc.to_flusight() # FluSight hub layout
145
+ fc.plot("gnn_st")
146
+ ```
147
+
148
+ `forecast()` reads the most recent backtest for the city. It adds a `trust`
149
+ column and raises a `TrustWarning` for any model or horizon that did not
150
+ clearly beat persistence, or was never backtested.
151
+
152
+ How to read the backtest report:
153
+ - **WIS** (weighted interval score) asks how good the whole forecast was, the
154
+ band as well as the point. Lower is better, and it's in rate units. CDC's
155
+ FluSight ranks forecasts by it.
156
+ - **Relative WIS** is the model's WIS divided by persistence's, on the same
157
+ weeks. Below 1 means the model beat persistence.
158
+ - **p (Holm)** asks whether the weekly gap could be week-to-week noise. It is
159
+ tested over weeks, not area-weeks, because neighbouring areas move together,
160
+ and it is corrected for the number of comparisons. "Better than persistence"
161
+ means a relative WIS below 1 and p below 0.05.
162
+ - **Coverage** is how often the truth fell inside the 95% band. It should be
163
+ near 95%.
164
+
165
+ ## How long it takes
166
+
167
+ On a laptop CPU, for about 15 areas and 8 years of weekly data:
168
+ - One graph-model ensemble member takes about 3 minutes, so the default
169
+ 10-member ensemble takes about 30 minutes per horizon.
170
+ - The LSTM and Cola-GNN take a few minutes per horizon.
171
+ - A default backtest (2 seasons × 4 horizons) takes about 4–5 hours.
172
+ - A forecast takes about 2–3 hours.
173
+
174
+ To go faster, use `configs={"gnn_st": {"n_seeds": 3}}`, fewer horizons, or
175
+ `--quick` to check the pipeline end to end. Everything is single-threaded, so
176
+ separate horizons or cities can run in parallel. `tune=True` picks each model's
177
+ settings from 12 candidates on validation weeks, the way the paper did, and
178
+ takes about 12× longer.
179
+
180
+ ## Starting from Boston's model
181
+
182
+ A department with only a season or two of data can start from a model trained
183
+ elsewhere:
184
+
185
+ ```python
186
+ fc = af.forecast(city, init_from="boston_gnn_st_noadapt_h1.pt", zero_shot=True, horizons=(1,))
187
+ ```
188
+
189
+ In the paper, a Boston model applied to Columbus with no Columbus training tied
190
+ a model trained on Columbus itself and beat persistence. The reverse direction
191
+ (Columbus to Boston, with a third of the history) was worse at 2 and 4 weeks.
192
+
193
+ Limits:
194
+ - **The inputs must match.** The pretrained model uses weather and no static
195
+ characteristics, so call `city.fetch_weather()` first.
196
+ - **Backtest it on your data before trusting it.** Pretrained Boston weights
197
+ are published with the release.
198
+
199
+ ## Limits to know
200
+
201
+ - **The forecasts run late.** Every model here, like persistence, rises after
202
+ the curve rises and peaks after it peaks. It runs about *h* weeks behind at
203
+ *h* weeks ahead. The models beat persistence by getting the size of the
204
+ epidemic right, not by seeing turns earlier. This happens because the models
205
+ only see past rates, and a turn isn't visible in weekly data until after it
206
+ happens. A growth-rate feature and a median loss were tested and didn't
207
+ change it. The backtest report has a **timing** section that measures this
208
+ on your data. Earlier warning would need inputs that move before ED visits
209
+ do, such as wastewater or a shorter reporting delay.
210
+ - **Reporting delays are not simulated.** A 1-week forecast assumes last week's
211
+ data is complete. If your data arrives L weeks late, treat the h-week forecast
212
+ as roughly an (h + L)-week one.
213
+ - **The backtest scores the models, not your data pipeline.** If your counts get
214
+ revised after the fact, the backtest saw final numbers and your real-time
215
+ forecasts will not.
216
+ - **One season is one season.** Two or more backtest seasons are better than one.
217
+
218
+ ## Reproducing the paper
219
+
220
+ `scripts/parity/` reruns the paper's tuned Boston backtest with this package and
221
+ compares the forecasts week by week. `tests/test_research_parity.py` checks,
222
+ against the research code, that the inputs, graph, initial weights and trained
223
+ forecasts are identical, down to the last bit for training.
224
+
225
+ ## Data availability and credits
226
+
227
+ - **The bundled Boston sample** comes from the Boston Public Health Commission's
228
+ public dashboard.
229
+ - **Weather data** is by [Open-Meteo.com](https://open-meteo.com/) (CC BY 4.0).
230
+ - **The bundled Chicago sample** (`load_sample("chicago")`) comes from the Chicago
231
+ Department of Public Health's "Influenza Risk Level by ZIP Code" on the City of
232
+ Chicago Data Portal. It is a 1-10 activity level, not a rate; its README carries
233
+ the portal's required disclaimer.
234
+ - **Nothing else is bundled.** The package contains no data from any other
235
+ health department.
236
+ - **Paper data.** The Columbus data used in the accompanying paper are
237
+ described in its data availability statement. Columbus weekly
238
+ area-level counts are public in the
239
+ [research repository](https://github.com/CodeMateo15/GNN-Influenza-Boston).
240
+ Columbus Public Health's visit-level extracts are available from them on
241
+ request.
242
+ - **Your data stays with you.** The package sends nothing anywhere. Its only
243
+ network call is the optional weather fetch, which sends area centroids to
244
+ Open-Meteo.
245
+ - This work was supported by Boston Public Health Commission, Columbus Public
246
+ Health, the PEAK Trailblazer fellowship, and Prof. Ariel Hamlin (Northeastern
247
+ University).
248
+
249
+ ## Citing
250
+
251
+ See `CITATION.cff`.
@@ -0,0 +1,214 @@
1
+ # areaFluForecast
2
+
3
+ **Documentation: https://areafluforecast.readthedocs.io**
4
+
5
+ Neighborhood-scale influenza forecasts, 1 to 4 weeks ahead, for public health
6
+ departments. You bring weekly flu rates for your areas (neighborhoods, ZIP-code
7
+ groups, districts) and their boundaries. The package trains three models, checks
8
+ each against the simplest possible forecast on your own past seasons, and only
9
+ then gives you forecasts, with honest uncertainty bands.
10
+
11
+ The three models:
12
+ - **`gnn_st`**: a graph neural network that uses which areas border each other
13
+ and whose flu curves move together. This is the model from the accompanying paper.
14
+ - **`lstm`**: a recurrent network that sees every area's series but no map.
15
+ - **`colagnn`**: Cola-GNN (Deng et al. 2020), a published graph model for flu.
16
+ This is our reimplementation from the paper, not the authors' code.
17
+
18
+ Every model is compared with **persistence**, "next week looks like this week".
19
+ A forecaster that can't beat that on your data has learned nothing you can use.
20
+ The package tells you, for each model and horizon, whether it did.
21
+
22
+ > **What the evidence says.** In Boston the graph model's error was 18–28% below
23
+ > persistence on seasons it never saw. It tied a carefully tuned LSTM at 1 week
24
+ > and led it on average at 2–4 weeks. In Columbus, with only one unseen season,
25
+ > its error was 12–23% below persistence on average, but that lead was not
26
+ > statistically clear. Your city may be either, and the backtest is how you
27
+ > find out.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install areaFluForecast # once published; until then:
33
+ pip install git+https://github.com/CodeMateo15/areaFluForecast
34
+ pip install "areaFluForecast[plot]" # adds plotting
35
+ ```
36
+
37
+ Python 3.10+, CPU only. Installing the package also installs PyTorch and
38
+ PyTorch Geometric.
39
+
40
+ ## Try it on Boston
41
+
42
+ ```python
43
+ import areafluforecast as af
44
+
45
+ city = af.load_sample("boston")
46
+ print(city.summary())
47
+ bt = af.backtest(city, seasons=1, horizons=(1, 2), configs={"gnn_st": {"n_seeds": 3}})
48
+ fc = af.forecast(city, horizons=(1, 2))
49
+ fc.summary()
50
+ ```
51
+
52
+ Or from the shell: `areaflu backtest --sample boston --seasons 1 --quick`.
53
+
54
+ **Worked examples:** [`examples/`](examples/) has five notebooks, saved with
55
+ their outputs:
56
+ - a Boston quickstart
57
+ - bringing your own files
58
+ - a city with only two years of data
59
+ - reading the results and choosing a model
60
+ - a real city (Chicago) built from public open data
61
+
62
+ ## Use your own data
63
+
64
+ You need two things.
65
+
66
+ **1. Weekly rates**, as a CSV. Either long format:
67
+
68
+ | week | area | rate |
69
+ |---|---|---|
70
+ | 2024-01-07 | Riverside | 41.2 |
71
+ | 2024-01-07 | Old Town | 28.9 |
72
+
73
+ or wide format, with one column per area and the week start date first. Rates
74
+ per 100,000 residents are expected. Leave missing or suppressed weeks blank.
75
+ Don't fill them with 0. You need at least two years of data, and three or more
76
+ is better. If you have counts, `af.rates_from_counts(counts, population)`
77
+ converts them.
78
+
79
+ **2. The map**, given one of two ways:
80
+ - a GeoJSON boundary file whose features carry the area name (`id_col`). The
81
+ package works out which areas share a border.
82
+ - or a CSV edge list with one row per pair of bordering areas.
83
+
84
+ ```python
85
+ city = af.City.from_files("rates.csv", areas="areas.geojson", id_col="NAME",
86
+ flu_months=(10, 11, 12, 1, 2, 3))
87
+ city.fetch_weather() # optional: Open-Meteo, free, no API key
88
+ print(city.summary()) # check this before training
89
+ ```
90
+
91
+ Southern-Hemisphere cities pass their own flu months, e.g. `(4, 5, 6, 7, 8, 9)`.
92
+
93
+ **Optional inputs:**
94
+ - **Anchors** are neighbouring towns or water that border your areas but have
95
+ no data. List them in `anchors=[...]`, and the edge list or boundary file
96
+ supplies their borders.
97
+ - **Static area characteristics** (a CSV with an `area` column). They are off
98
+ by default, because in the paper they did not help.
99
+
100
+ ## The workflow
101
+
102
+ ```python
103
+ bt = af.backtest(city) # would it have worked here? (last 2 complete seasons)
104
+ bt.report() # each model vs persistence, per horizon
105
+ fc = af.forecast(city) # weeks 1-4 after your last observed week
106
+ fc.to_csv("forecast.csv") # median, 95% band, 23 FluSight quantiles per area
107
+ fc.to_flusight() # FluSight hub layout
108
+ fc.plot("gnn_st")
109
+ ```
110
+
111
+ `forecast()` reads the most recent backtest for the city. It adds a `trust`
112
+ column and raises a `TrustWarning` for any model or horizon that did not
113
+ clearly beat persistence, or was never backtested.
114
+
115
+ How to read the backtest report:
116
+ - **WIS** (weighted interval score) asks how good the whole forecast was, the
117
+ band as well as the point. Lower is better, and it's in rate units. CDC's
118
+ FluSight ranks forecasts by it.
119
+ - **Relative WIS** is the model's WIS divided by persistence's, on the same
120
+ weeks. Below 1 means the model beat persistence.
121
+ - **p (Holm)** asks whether the weekly gap could be week-to-week noise. It is
122
+ tested over weeks, not area-weeks, because neighbouring areas move together,
123
+ and it is corrected for the number of comparisons. "Better than persistence"
124
+ means a relative WIS below 1 and p below 0.05.
125
+ - **Coverage** is how often the truth fell inside the 95% band. It should be
126
+ near 95%.
127
+
128
+ ## How long it takes
129
+
130
+ On a laptop CPU, for about 15 areas and 8 years of weekly data:
131
+ - One graph-model ensemble member takes about 3 minutes, so the default
132
+ 10-member ensemble takes about 30 minutes per horizon.
133
+ - The LSTM and Cola-GNN take a few minutes per horizon.
134
+ - A default backtest (2 seasons × 4 horizons) takes about 4–5 hours.
135
+ - A forecast takes about 2–3 hours.
136
+
137
+ To go faster, use `configs={"gnn_st": {"n_seeds": 3}}`, fewer horizons, or
138
+ `--quick` to check the pipeline end to end. Everything is single-threaded, so
139
+ separate horizons or cities can run in parallel. `tune=True` picks each model's
140
+ settings from 12 candidates on validation weeks, the way the paper did, and
141
+ takes about 12× longer.
142
+
143
+ ## Starting from Boston's model
144
+
145
+ A department with only a season or two of data can start from a model trained
146
+ elsewhere:
147
+
148
+ ```python
149
+ fc = af.forecast(city, init_from="boston_gnn_st_noadapt_h1.pt", zero_shot=True, horizons=(1,))
150
+ ```
151
+
152
+ In the paper, a Boston model applied to Columbus with no Columbus training tied
153
+ a model trained on Columbus itself and beat persistence. The reverse direction
154
+ (Columbus to Boston, with a third of the history) was worse at 2 and 4 weeks.
155
+
156
+ Limits:
157
+ - **The inputs must match.** The pretrained model uses weather and no static
158
+ characteristics, so call `city.fetch_weather()` first.
159
+ - **Backtest it on your data before trusting it.** Pretrained Boston weights
160
+ are published with the release.
161
+
162
+ ## Limits to know
163
+
164
+ - **The forecasts run late.** Every model here, like persistence, rises after
165
+ the curve rises and peaks after it peaks. It runs about *h* weeks behind at
166
+ *h* weeks ahead. The models beat persistence by getting the size of the
167
+ epidemic right, not by seeing turns earlier. This happens because the models
168
+ only see past rates, and a turn isn't visible in weekly data until after it
169
+ happens. A growth-rate feature and a median loss were tested and didn't
170
+ change it. The backtest report has a **timing** section that measures this
171
+ on your data. Earlier warning would need inputs that move before ED visits
172
+ do, such as wastewater or a shorter reporting delay.
173
+ - **Reporting delays are not simulated.** A 1-week forecast assumes last week's
174
+ data is complete. If your data arrives L weeks late, treat the h-week forecast
175
+ as roughly an (h + L)-week one.
176
+ - **The backtest scores the models, not your data pipeline.** If your counts get
177
+ revised after the fact, the backtest saw final numbers and your real-time
178
+ forecasts will not.
179
+ - **One season is one season.** Two or more backtest seasons are better than one.
180
+
181
+ ## Reproducing the paper
182
+
183
+ `scripts/parity/` reruns the paper's tuned Boston backtest with this package and
184
+ compares the forecasts week by week. `tests/test_research_parity.py` checks,
185
+ against the research code, that the inputs, graph, initial weights and trained
186
+ forecasts are identical, down to the last bit for training.
187
+
188
+ ## Data availability and credits
189
+
190
+ - **The bundled Boston sample** comes from the Boston Public Health Commission's
191
+ public dashboard.
192
+ - **Weather data** is by [Open-Meteo.com](https://open-meteo.com/) (CC BY 4.0).
193
+ - **The bundled Chicago sample** (`load_sample("chicago")`) comes from the Chicago
194
+ Department of Public Health's "Influenza Risk Level by ZIP Code" on the City of
195
+ Chicago Data Portal. It is a 1-10 activity level, not a rate; its README carries
196
+ the portal's required disclaimer.
197
+ - **Nothing else is bundled.** The package contains no data from any other
198
+ health department.
199
+ - **Paper data.** The Columbus data used in the accompanying paper are
200
+ described in its data availability statement. Columbus weekly
201
+ area-level counts are public in the
202
+ [research repository](https://github.com/CodeMateo15/GNN-Influenza-Boston).
203
+ Columbus Public Health's visit-level extracts are available from them on
204
+ request.
205
+ - **Your data stays with you.** The package sends nothing anywhere. Its only
206
+ network call is the optional weather fetch, which sends area centroids to
207
+ Open-Meteo.
208
+ - This work was supported by Boston Public Health Commission, Columbus Public
209
+ Health, the PEAK Trailblazer fellowship, and Prof. Ariel Hamlin (Northeastern
210
+ University).
211
+
212
+ ## Citing
213
+
214
+ See `CITATION.cff`.
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "areaFluForecast"
7
+ version = "0.1.0"
8
+ description = "Neighborhood-scale influenza forecasts for public health departments: a graph neural network, a tuned LSTM and Cola-GNN, each checked against persistence on your own data."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Mateo Biggs", email = "biggs.m@northeastern.edu" }]
14
+ keywords = ["influenza", "forecasting", "graph neural network", "public health", "epidemiology", "FluSight"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Intended Audience :: Science/Research",
18
+ "Intended Audience :: Healthcare Industry",
19
+ "Topic :: Scientific/Engineering :: Medical Science Apps.",
20
+ ]
21
+ dependencies = [
22
+ "numpy>=1.24",
23
+ "pandas>=2.0",
24
+ "scipy>=1.10",
25
+ "torch>=2.1",
26
+ "torch-geometric>=2.4",
27
+ "requests>=2.28",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ plot = ["matplotlib>=3.7"]
32
+ geo = ["shapely>=2.0"]
33
+ dev = ["pytest>=7", "matplotlib>=3.7", "shapely>=2.0"]
34
+ docs = ["mkdocs>=1.6", "mkdocs-material>=9.5", "mkdocstrings[python]>=0.26", "mkdocs-jupyter>=0.25"]
35
+
36
+ [project.scripts]
37
+ areaflu = "areafluforecast.cli:main"
38
+
39
+ [project.urls]
40
+ Source = "https://github.com/CodeMateo15/areaFluForecast"
41
+ Documentation = "https://areafluforecast.readthedocs.io"
42
+
43
+ [tool.setuptools.packages.find]
44
+ where = ["src"]
45
+
46
+ [tool.setuptools.package-data]
47
+ areafluforecast = ["sample_data/*/*.csv", "sample_data/*/*.json", "sample_data/*/*.md"]
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths = ["tests"]
51
+ markers = [
52
+ "slow: trains small models end to end (still under a few minutes on CPU)",
53
+ "network: needs internet access (Open-Meteo, TIGERweb)",
54
+ "research: needs the GNN-Influenza-Boston research repo (set AREAFLU_RESEARCH_REPO)",
55
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+