parkingstudy 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,232 @@
1
+ Metadata-Version: 2.4
2
+ Name: parkingstudy
3
+ Version: 0.1.0
4
+ Summary: Parking occupancy and turnover survey analysis: inventory, occupancy by beat, effective capacity, licence-plate turnover and overstay with salted plate hashing, heatmaps, demand-responsive pricing rules and GeoJSON block-face joins.
5
+ Author-email: Yusuf Eminoğlu <geospacephilo@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://gitlab.com/geophilo1/parkingstudy_sdk
8
+ Project-URL: Repository, https://gitlab.com/geophilo1/parkingstudy_sdk
9
+ Project-URL: Issues, https://gitlab.com/geophilo1/parkingstudy_sdk/-/work_items
10
+ Project-URL: Changelog, https://gitlab.com/geophilo1/parkingstudy_sdk/-/blob/main/CHANGELOG.md
11
+ Keywords: parking,parking-study,parking-occupancy,parking-turnover,licence-plate-survey,lpr,curb-management,demand-responsive-pricing,sfpark,transport-planning,urban-planning,geojson,gis
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Environment :: Console
21
+ Classifier: Topic :: Scientific/Engineering :: GIS
22
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
23
+ Classifier: Intended Audience :: Science/Research
24
+ Classifier: Intended Audience :: Developers
25
+ Classifier: Typing :: Typed
26
+ Requires-Python: >=3.9
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
31
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
32
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
33
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ # parkingstudy
37
+
38
+ [![PyPI version](https://img.shields.io/pypi/v/parkingstudy.svg)](https://pypi.org/project/parkingstudy/)
39
+ [![Python versions](https://img.shields.io/pypi/pyversions/parkingstudy.svg)](https://pypi.org/project/parkingstudy/)
40
+ [![pipeline](https://gitlab.com/geophilo1/parkingstudy_sdk/badges/main/pipeline.svg)](https://gitlab.com/geophilo1/parkingstudy_sdk/-/pipelines)
41
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://gitlab.com/geophilo1/parkingstudy_sdk/-/blob/main/LICENSE)
42
+
43
+ **Parking occupancy and turnover survey analysis for planners.**
44
+
45
+ `parkingstudy` turns the spreadsheets of a classic parking study into the numbers
46
+ a parking management plan needs:
47
+
48
+ - **Inventory**: spaces by block face, zone, space type and facility, with time limits,
49
+ restrictions, part-time windows (clearways, street cleaning) and hourly rates.
50
+ - **Occupancy counts by time beat**: occupancy rate, peak occupancy, effective capacity at
51
+ a target occupancy (85 % by default, configurable), practical capacity, accumulation
52
+ curve and space-hours of demand and supply.
53
+ - **Licence-plate (beat / LPR) surveys**: stays and durations, turnover, mean and median
54
+ duration, overstay share against posted time limits, utilization.
55
+ - **Heatmap tables** of occupancy by zone (or block face) x hour.
56
+ - **Demand-responsive pricing rules**: the SFMTA occupancy-band policy and the SFpark
57
+ pilot table, both as configurable policies.
58
+ - **GeoJSON join** of any result table to block-face geometries for mapping.
59
+ - **Privacy by design**: licence plates are replaced on input by a salted keyed hash
60
+ (HMAC-SHA256). Raw plates are never stored in memory objects or written to outputs.
61
+
62
+ Standard library only, no required dependencies. Requires Python 3.9 or newer.
63
+
64
+ ```bash
65
+ pip install parkingstudy
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Quick start (command line)
71
+
72
+ Input files are plain CSV (column names are case-insensitive):
73
+
74
+ | file | required columns | optional columns |
75
+ |---|---|---|
76
+ | inventory | `block_face_id`, `spaces` | `zone`, `space_type`, `facility` (`on_street`/`off_street`), `time_limit_min`, `restriction`, `unavailable` (`07:00-09:00;16:00-18:00`), `hourly_rate` |
77
+ | counts | `block_face_id`, `beat`, `occupied` | `space_type`, `date` |
78
+ | sightings | `block_face_id`, `beat`, and `plate_token` **or** `plate` | `space_type`, `date` |
79
+
80
+ `beat` is a clock time (`08:30`) or an ISO date-time (`2026-03-10T08:30`).
81
+
82
+ ```bash
83
+ # 1. Create a secret salt once per survey, keep it out of version control
84
+ parkingstudy salt > survey.salt
85
+ export PARKINGSTUDY_SALT=$(cat survey.salt)
86
+
87
+ # 2. Anonymise the field sheets: the plate column is replaced by a token column
88
+ parkingstudy hash-plates sightings_raw.csv -o sightings.csv
89
+
90
+ # 3. Supply summary, occupancy by beat and peak vs. 85 % effective capacity
91
+ parkingstudy inventory inventory.csv
92
+ parkingstudy occupancy inventory.csv counts.csv --by zone -o occupancy.csv
93
+ parkingstudy occupancy inventory.csv counts.csv --by zone --peak --target 0.85 \
94
+ --factor on_street=0.85 --factor off_street=0.90
95
+ parkingstudy occupancy inventory.csv counts.csv --by zone --space-hours
96
+
97
+ # 4. Turnover, duration and overstay from the plate survey
98
+ parkingstudy turnover inventory.csv sightings.csv --by block_face --beat-minutes 30 \
99
+ --stays stays.csv -o turnover.csv
100
+ parkingstudy turnover inventory.csv sightings.csv --durations # duration classes
101
+
102
+ # 5. Heatmap (zone x hour) and pricing recommendations for the 09:00-12:00 rate period
103
+ parkingstudy heatmap inventory.csv counts.csv --by zone --bin-minutes 60 -o heatmap.csv
104
+ parkingstudy pricing inventory.csv counts.csv --from 09:00 --to 12:00 --policy sfmta
105
+
106
+ # 6. Map it: join any result CSV (key column) to block-face GeoJSON
107
+ parkingstudy geojson block_faces.geojson turnover.csv -o turnover_map.geojson
108
+ ```
109
+
110
+ Every analysis command writes CSV (or `--format json`) to standard output or `-o FILE`.
111
+ `turnover` also accepts raw plates directly if a salt is supplied (`--salt-env VAR` or
112
+ `--salt-file FILE`); without one it refuses to read them.
113
+
114
+ ## Quick start (Python)
115
+
116
+ ```python
117
+ from parkingstudy import (
118
+ PlateHasher, read_inventory, read_counts, read_sightings,
119
+ capacity_assessment, build_stays, turnover_summary, utilization_heatmap,
120
+ )
121
+
122
+ inv = read_inventory("inventory.csv")
123
+ counts = read_counts("counts.csv")
124
+
125
+ for row in capacity_assessment(inv, counts, by="zone", target=0.85):
126
+ print(row.key, row.peak_occupied, row.spaces, row.effective_capacity, row.above_target)
127
+
128
+ hasher = PlateHasher.from_env() # salt from $PARKINGSTUDY_SALT
129
+ sightings = read_sightings("sightings_raw.csv", hasher) # plates hashed while reading
130
+ stays = build_stays(sightings, beat_minutes=30, inventory=inv)
131
+ for r in turnover_summary(stays, inv, by="block_face"):
132
+ print(r.key, r.turnover, r.mean_duration_min, r.overstay_share)
133
+
134
+ print(utilization_heatmap(inv, counts, by="zone").to_csv())
135
+ ```
136
+
137
+ See [`examples/quickstart.py`](examples/quickstart.py) for a complete synthetic survey.
138
+
139
+ ## API overview
140
+
141
+ | area | objects |
142
+ |---|---|
143
+ | inventory | `InventoryRecord`, `Inventory` (`capacity`, `time_limit`, `hourly_rate`, `summary`) |
144
+ | occupancy | `Count`, `occupancy_by_beat`, `peak_occupancy`, `effective_capacity`, `practical_capacity`, `capacity_assessment`, `accumulation_curve`, `space_hours`, `infer_beat_minutes` |
145
+ | plate survey | `Sighting`, `build_stays`, `Stay`, `turnover_summary`, `TurnoverRow`, `accumulation_from_stays`, `duration_distribution` |
146
+ | heatmap | `utilization_heatmap`, `HeatmapTable` (`get`, `max_cell`, `to_rows`, `to_csv`) |
147
+ | pricing | `PricingPolicy`, `PriceBand`, `SFMTA_POLICY`, `SFPARK_PILOT_POLICY`, `recommend_rate`, `recommend_rates`, `mean_occupancy` |
148
+ | GeoJSON | `join_to_geojson`, `rows_by_key`, `load_geojson`, `write_geojson` |
149
+ | privacy | `PlateHasher`, `generate_salt`, `normalize_plate`, `anonymise_rows`, `anonymise_csv` |
150
+ | I/O | `read_inventory`, `read_counts`, `read_sightings`, `write_rows`, `records_to_rows` |
151
+
152
+ Aggregation everywhere uses `by="block_face" | "zone" | "total"`.
153
+
154
+ ## Method and sources
155
+
156
+ **Occupancy.** Occupancy rate at a beat = vehicles / legal spaces available at that beat.
157
+ Spaces inside an `unavailable` window count as zero supply; vehicles parked there are still
158
+ counted, so rates above 100 % (illegal parking) are kept and flagged (`over_capacity`).
159
+ Zone and total rates sum vehicles and spaces of the block faces counted at that beat.
160
+
161
+ **Effective capacity** = spaces x target occupancy. The 85 % default is the
162
+ "85 percent rule" for kerb parking advocated by D. Shoup, *The High Cost of Free Parking*
163
+ (APA Planners Press, 2005): above roughly 85 % occupancy drivers start cruising for a space.
164
+ **Practical capacity** (also "effective supply") multiplies spaces by an efficiency factor
165
+ per facility type. The defaults (0.85 on-street, 0.90 off-street) are common planning rules
166
+ of thumb rather than values from a standard; pass your own `factors=` / `--factor`.
167
+
168
+ **Licence-plate beat survey.** A vehicle seen on consecutive beats at the same block face
169
+ (or zone, `level="zone"`) is one stay. With beat length *b*, first and last sightings
170
+ *t1* and *tn*: minimum duration = *tn - t1*; estimated duration = *tn - t1 + b* (each
171
+ sighting stands for one beat, the usual beat-survey convention). The true duration lies
172
+ between *tn - t1* and *tn - t1 + 2b*. Stays on the first or last beat of a survey session
173
+ are flagged as censored. `max_gap_beats` tolerates missed reads.
174
+ Turnover = vehicles / spaces over the survey period (and per hour). Space-hours of demand
175
+ = sum of estimated durations; utilization = space-hours / (spaces x survey hours).
176
+ **Overstay** compares a stay with the posted limit of its block face: the default
177
+ `basis="minimum"` counts only stays certainly longer than the limit (enforcement-grade,
178
+ conservative); `basis="estimate"` uses the estimated duration.
179
+
180
+ **Pricing.** `SFMTA_POLICY` follows SFMTA, *Demand-responsive parking pricing policies:
181
+ on-street parking meters and off-street lots* (5 December 2017): occupancy 80 % or above
182
+ raises the hourly rate by 0.25, 60 % to below 80 % leaves it unchanged, below 60 % lowers
183
+ it by 0.25, within 0.50 to 8.00 per hour. `SFPARK_PILOT_POLICY` is the four-band table
184
+ reported for the 2011-2013 SFpark pilot (80-100 % +0.25, 60-80 % hold, 30-60 % -0.25,
185
+ below 30 % -0.50, cap 6.00). Both are **configurable examples**: build a `PricingPolicy`
186
+ with your own bands, steps, currency, floor, cap and rounding.
187
+
188
+ **Plate anonymisation.** Plates are normalised (Unicode NFKC, upper case, letters and
189
+ digits only) and replaced by HMAC-SHA256 (RFC 2104) keyed with a secret salt of at least
190
+ 16 bytes, truncated to 16 hex characters by default. Unlike an unsalted hash, tokens cannot
191
+ be reversed by enumerating the small space of valid plates without the salt. Use a fresh
192
+ salt per survey and destroy it afterwards. Licence plates are personal data under, for
193
+ example, the EU GDPR and the Turkish KVKK (Law No. 6698); check the rules that apply to
194
+ your survey.
195
+
196
+ ## Limitations
197
+
198
+ - Beat surveys cannot see stays shorter than one beat that fall between two passes, so
199
+ turnover is underestimated and mean duration overestimated, more so with long beats.
200
+ - Zone-level stays take the time limit of the first block face where the vehicle was seen.
201
+ - A block face with several different time limits (for different space types) is not
202
+ assessed for overstay unless sightings carry a `space_type`.
203
+ - No time zones; beats are naive local times. Bare clock times are treated as one day.
204
+ - Practical-capacity factors and the SFpark pilot table are planning conventions and
205
+ secondary-source values; verify them for your context.
206
+
207
+ ## Development
208
+
209
+ ```bash
210
+ pip install -e ".[dev]"
211
+ python -m pytest
212
+ python -m ruff check . && python -m ruff format --check src tests
213
+ python -m mypy src
214
+ ```
215
+
216
+ ## License
217
+
218
+ MIT License, see [LICENSE](LICENSE).
219
+
220
+ ## Academic citation
221
+
222
+ If you use `parkingstudy` in research, please cite:
223
+
224
+ ```bibtex
225
+ @software{eminoglu_parkingstudy,
226
+ author = {Eminoğlu, Yusuf},
227
+ title = {parkingstudy},
228
+ year = {2026},
229
+ version = {0.1.0},
230
+ url = {https://gitlab.com/geophilo1/parkingstudy_sdk}
231
+ }
232
+ ```
@@ -0,0 +1,21 @@
1
+ parkingstudy/__init__.py,sha256=poF-4qVoE_DMGSnu8LJGzcvF-1dV5KKB5SLnMqmENMA,3222
2
+ parkingstudy/__main__.py,sha256=7qAmboxkh6K-H3Cnl81UQ63qzYybylKGiga0_j-9cyA,87
3
+ parkingstudy/_time.py,sha256=iTNL2u9e1JghwAn1nAJ7zaSEZOqFMf34d--L3VcTqlQ,4021
4
+ parkingstudy/_version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
5
+ parkingstudy/cli.py,sha256=HeQIJTagM5CxcGU-rdPTq_67FpHPAZn_dB49ATbWoX4,12526
6
+ parkingstudy/errors.py,sha256=KEoR7_SZQYdv3XHTeZ1e8tm1kFvO0nefMUIAU_V5V4s,424
7
+ parkingstudy/geojson.py,sha256=dIww7-gq_VtBGIUYzk9Pt-KsT9Vj5Ehav615O8rKt34,4113
8
+ parkingstudy/heatmap.py,sha256=7SZaa8pHY0B4pSayQWWKQMNYJrGS_mDLhc6OeWmkSZ8,4454
9
+ parkingstudy/inventory.py,sha256=n1mvB0YK_hAiDuIpARweXm_Sfw9QQrUzmzUtN2PpE3o,10224
10
+ parkingstudy/io.py,sha256=Y9CHmk3cu0Sd1PXFtIg1Th7TVX83J6WLCUk0zD5uR4I,8866
11
+ parkingstudy/lpr.py,sha256=8XrqiBP7iYdQXqsuCSF5l_3oskUsdRMDrzkD2uM-yfM,16255
12
+ parkingstudy/occupancy.py,sha256=moq2bANo-xJx0Rk4y5RWhXpGu7z4M2Tb-BKp-pS1bgs,11806
13
+ parkingstudy/pricing.py,sha256=Fp2ITj2bxtynqbKdR6LZczKnwyl74Mhgy6HBC-MvDOY,7616
14
+ parkingstudy/privacy.py,sha256=4SC-N_jxQfiYYHf1Qvsf6dh9zbPS_jN26poiimGngTU,5430
15
+ parkingstudy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ parkingstudy-0.1.0.dist-info/licenses/LICENSE,sha256=Z9meOk3opsRgHOqC5mA4B8NjZoU5WU1Kqhn82FL4ICs,1072
17
+ parkingstudy-0.1.0.dist-info/METADATA,sha256=ClS2C1brsThWTnHTKX00nDrwqOAl8tdy4Ze2Zdc3GIc,11985
18
+ parkingstudy-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
19
+ parkingstudy-0.1.0.dist-info/entry_points.txt,sha256=bLsx4cerRONmmsBrSLDekLgkzsX1eikj7Rcvd5HR3dE,55
20
+ parkingstudy-0.1.0.dist-info/top_level.txt,sha256=66lvWZGOjLlJdTNVFPsexS7V9rxDJf5YSOS1dqxbfSs,13
21
+ parkingstudy-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ parkingstudy = parkingstudy.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yusuf Eminoğlu
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 @@
1
+ parkingstudy