microsegments 0.2.0__tar.gz → 0.2.1__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.
- {microsegments-0.2.0 → microsegments-0.2.1}/PKG-INFO +1 -1
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/_version.py +2 -2
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/io/coverage.py +24 -1
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_coverage.py +21 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/.github/workflows/publish.yml +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/.github/workflows/test.yml +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/.gitignore +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/LICENSE +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/README.md +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/docs/report.jpg +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/examples/gtfsrt_csv.toml +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/examples/make_synth.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/examples/stib1_platform.toml +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/examples/stib55.toml +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/examples/stib55_full.toml +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/examples/stib71_platform.toml +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/examples/synth.toml +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/pyproject.toml +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/__init__.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/aggregate.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/cli.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/compare.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/config.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/contract.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/hotspots.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/html/__init__.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/html/export.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/html/leaflet.min.css +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/html/template.html +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/io/__init__.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/io/events.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/io/gtfsrt.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/io/ids.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/io/stib.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/io/tabular.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/io/timeutil.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/locate/__init__.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/locate/common.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/locate/linear.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/locate/mapmatch.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/locate/resample.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/metrics.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/network/__init__.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/network/calendar.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/network/geometry.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/network/keys.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/network/patterns.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/pipeline.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/plot.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/py.typed +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/report.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/schema.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/segments.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/simulate.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/tracks.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/src/microsegments/tune.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/golden/buckets.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/golden/drops.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/golden/flen.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/golden/obs_day.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/golden/obs_dow.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/golden/passages.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/golden/summary.json +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/golden/validation.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/gtfs/agency.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/gtfs/calendar.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/gtfs/calendar_dates.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/gtfs/routes.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/gtfs/shapes.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/gtfs/stop_times.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/gtfs/stops.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/gtfs/trips.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/punctuality/route55.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/speed55_ref.json +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/vd55/20250318.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/vd55/20250318.snaps.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/vd55/20250319.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/vd55/20250319.snaps.parquet +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/gtfs_fixtures.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/regression/make_golden.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/t4_fixtures.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_aggregate.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_compare.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_hotspots.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_hotspots_criteria.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_io_events.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_io_gtfsrt.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_io_stib.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_io_tabular.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_locate_linear.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_locate_mapmatch.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_metrics.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_network_geometry.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_network_patterns.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_pipeline_cli.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_regression_stib55.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_report_html.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_scaffold.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_segments.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_simulate.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_tracks.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_tune.py +0 -0
- {microsegments-0.2.0 → microsegments-0.2.1}/tests/test_versions_merge.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: microsegments
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Where do transit vehicles linger? Count vehicle position observations per micro-segment of a line, from GTFS + AVL / GTFS-RT positions.
|
|
5
5
|
Project-URL: Homepage, https://github.com/GaspardMerten/microsegments
|
|
6
6
|
Project-URL: Repository, https://github.com/GaspardMerten/microsegments
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
21
|
+
__version__ = version = '0.2.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 2, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -5,6 +5,12 @@ non-frozen poll (the last poll covers min(tick_s, gap_cap_s)). Each frozen snaps
|
|
|
5
5
|
[t_i, t_i + min(t_next_any - t_i, gap_cap_s)) of ``frozen_s`` (t_next_any: next snapshot of any kind).
|
|
6
6
|
Intervals are split exactly at local hour boundaries and credited to the hour they fall in.
|
|
7
7
|
``coverage = covered_s / 3600`` clipped to 1. Frozen snapshots never count as polls.
|
|
8
|
+
|
|
9
|
+
FROZEN flag: an hour is flagged when it overlaps a *stall*, a run of consecutive frozen snapshots
|
|
10
|
+
lasting more than ``max_frozen_s`` (each covering min(next snapshot - t, gap_cap_s)). Isolated
|
|
11
|
+
repeats are normal for feeds that refresh more slowly than they are polled (STIB since 2026: about
|
|
12
|
+
one poll in five repeats the previous one, 300-700 s per hour in total) and do not flag the hour;
|
|
13
|
+
``frozen_s`` still reports their total.
|
|
8
14
|
"""
|
|
9
15
|
from __future__ import annotations
|
|
10
16
|
|
|
@@ -79,9 +85,22 @@ def coverage(snapshots: pl.DataFrame | pl.LazyFrame, tz: str = "Europe/Brussels"
|
|
|
79
85
|
tf, gf = t[fr], (nxt_any - t)[fr]
|
|
80
86
|
fs, fl = _split_by_hour(tf, tf + np.minimum(gf, cap), tz)
|
|
81
87
|
|
|
88
|
+
# stalls: runs of consecutive frozen snapshots longer than max_frozen_s
|
|
89
|
+
ss, sl = np.zeros(0), np.zeros(0)
|
|
90
|
+
if fr.any():
|
|
91
|
+
span = np.minimum(nxt_any - t, cap)
|
|
92
|
+
edge = np.diff(np.concatenate([[0], fr.astype(np.int8), [0]]))
|
|
93
|
+
r0, r1 = np.flatnonzero(edge == 1), np.flatnonzero(edge == -1) # [r0, r1) frozen runs
|
|
94
|
+
csum = np.concatenate([[0.0], np.cumsum(np.where(fr, span, 0.0))])
|
|
95
|
+
dur = csum[r1] - csum[r0]
|
|
96
|
+
long = dur > max_frozen_s
|
|
97
|
+
if long.any():
|
|
98
|
+
ss, sl = _split_by_hour(t[r0[long]], t[r0[long]] + dur[long], tz)
|
|
99
|
+
|
|
82
100
|
sds = service_day_start
|
|
83
101
|
cov = _hour_table(ps, pl_, "covered_s", tz, sds)
|
|
84
102
|
frz = _hour_table(fs, fl, "frozen_s", tz, sds)
|
|
103
|
+
stall = _hour_table(ss, sl, "stall_s", tz, sds) if ss.size else None
|
|
85
104
|
nsn = _hour_table(tp, np.ones_like(tp), "n_snapshots", tz, sds)
|
|
86
105
|
|
|
87
106
|
if dates is None:
|
|
@@ -96,9 +115,13 @@ def coverage(snapshots: pl.DataFrame | pl.LazyFrame, tz: str = "Europe/Brussels"
|
|
|
96
115
|
.join(frz, on=["service_date", "hour"], how="left")
|
|
97
116
|
.join(nsn, on=["service_date", "hour"], how="left")
|
|
98
117
|
.with_columns(pl.col("covered_s", "frozen_s", "n_snapshots").fill_null(0.0)))
|
|
118
|
+
if stall is not None:
|
|
119
|
+
out = out.join(stall, on=["service_date", "hour"], how="left").with_columns(pl.col("stall_s").fill_null(0.0))
|
|
120
|
+
else:
|
|
121
|
+
out = out.with_columns(pl.lit(0.0).alias("stall_s"))
|
|
99
122
|
c = (pl.col("covered_s") / 3600.0).clip(0.0, 1.0)
|
|
100
123
|
flags = (pl.when(c < min_coverage).then(Flag.LOW_COVERAGE).otherwise(0)
|
|
101
|
-
+ pl.when(pl.col("
|
|
124
|
+
+ pl.when(pl.col("stall_s") > 0).then(Flag.FROZEN).otherwise(0))
|
|
102
125
|
out = out.with_columns(c.alias("coverage"), flags.alias("flags"))
|
|
103
126
|
return out.select([pl.col(k).cast(v) for k, v in COVERAGE.items()]).sort("service_date", "hour")
|
|
104
127
|
|
|
@@ -101,6 +101,27 @@ def test_frozen_not_counted():
|
|
|
101
101
|
assert c2.filter(pl.col("hour") == 10)["covered_s"][0] == row["covered_s"]
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
def test_scattered_repeats_do_not_flag_frozen():
|
|
105
|
+
"""One poll in four repeating the previous one (a feed refreshed more slowly than polled) is not a
|
|
106
|
+
stall: ~900 s of frozen snapshots in the hour, no run longer than 20 s."""
|
|
107
|
+
t = _day_polls()
|
|
108
|
+
frozen = (np.arange(t.size) % 4) == 3
|
|
109
|
+
c = coverage(_snaps(t, frozen), TZ, gap_cap_s=40, max_frozen_s=300)
|
|
110
|
+
row = c.filter(pl.col("hour") == 10).row(0, named=True)
|
|
111
|
+
assert row["frozen_s"] > 300
|
|
112
|
+
assert not row["flags"] & Flag.FROZEN and not row["flags"] & Flag.LOW_COVERAGE
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_stall_across_hours_flags_both():
|
|
116
|
+
t = _day_polls()
|
|
117
|
+
f1, f2 = local_epoch(DAY, 10 * 3600 + 3400, TZ), local_epoch(DAY, 11 * 3600 + 200, TZ) # 400 s stall
|
|
118
|
+
c = coverage(_snaps(t, (t >= f1) & (t < f2)), TZ, gap_cap_s=40, max_frozen_s=300)
|
|
119
|
+
fl = dict(zip(c["hour"].to_list(), c["flags"].to_list()))
|
|
120
|
+
assert fl[10] & Flag.FROZEN and fl[11] & Flag.FROZEN and not fl[12] & Flag.FROZEN
|
|
121
|
+
c = coverage(_snaps(t, (t >= f1) & (t < f1 + 200)), TZ, gap_cap_s=40, max_frozen_s=300) # 200 s: fine
|
|
122
|
+
assert not any(f & Flag.FROZEN for f in c["flags"].to_list())
|
|
123
|
+
|
|
124
|
+
|
|
104
125
|
def test_silent_hours_are_zero_rows():
|
|
105
126
|
t = _day_polls()
|
|
106
127
|
t = t[(t < local_epoch(DAY, 12 * 3600, TZ)) | (t >= local_epoch(DAY, 14 * 3600, TZ))]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/gtfs/calendar_dates.parquet
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/punctuality/route55.parquet
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/vd55/20250318.snaps.parquet
RENAMED
|
File without changes
|
|
File without changes
|
{microsegments-0.2.0 → microsegments-0.2.1}/tests/fixtures/stib55/vd55/20250319.snaps.parquet
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|