fastnascar 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 FastNASCAR contributors
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,13 @@
1
+ include README.md LICENSE pyproject.toml
2
+ recursive-include src *.py
3
+ recursive-include examples *.py *.ps1
4
+ recursive-include tests *.py *.json *.md
5
+ prune build
6
+ prune dist
7
+ prune outputs
8
+ prune .plot-deps
9
+ prune .matplotlib
10
+ prune .fastnascar-cache
11
+ prune .release-tools
12
+ prune .release-install
13
+ global-exclude __pycache__ *.py[cod]
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastnascar
3
+ Version: 0.1.0
4
+ Summary: Pandas-based NASCAR sessions, laps, results, and live timing
5
+ License-Expression: MIT
6
+ Keywords: nascar,motorsport,timing,pandas
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: pandas<4,>=2.2
15
+ Provides-Extra: plot
16
+ Requires-Dist: matplotlib>=3.8; extra == "plot"
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=8; extra == "dev"
19
+ Requires-Dist: build>=1; extra == "dev"
20
+ Requires-Dist: twine>=6; extra == "dev"
21
+ Dynamic: license-file
22
+
23
+ # FastNASCAR
24
+
25
+ FastNASCAR provides NASCAR Cup lap times, results, and live timing in pandas.
26
+ Select a race, load the session, and work with its lap data:
27
+
28
+ ```python
29
+ import fastnascar
30
+
31
+ session = fastnascar.get_session(2026, race_id=5626).load()
32
+ hocevar = session.laps.pick_driver("77")
33
+ print(hocevar.pick_fastest())
34
+ ```
35
+
36
+ Early release. Race laps and weekend results work; historical practice and
37
+ qualifying laps aren't supported yet. No throttle, brake, RPM, or GPS telemetry.
38
+ This project is unaffiliated with NASCAR.
39
+
40
+ ## Install
41
+
42
+ Python 3.10+ is required. From a checkout:
43
+
44
+ ```sh
45
+ python -m pip install -e .
46
+ python -m pip install -e ".[plot]" # optional matplotlib charts
47
+ ```
48
+
49
+ ## Pick a race
50
+
51
+ ```python
52
+ schedule = fastnascar.get_event_schedule(2026)
53
+ print(schedule[["RoundNumber", "RaceId", "EventName", "EventDate"]])
54
+
55
+ session = fastnascar.get_session(2026, 29)
56
+ session = fastnascar.get_session(2026, "Bass Pro Shops Night Race")
57
+ session = fastnascar.get_session(2026, race_id=5626)
58
+ session.load()
59
+ ```
60
+
61
+ Rounds count points races in scheduled order. Use
62
+ `include_exhibition=True` to include exhibitions in the schedule; select those
63
+ by name or race ID. Repeated names raise `AmbiguousEventError`, with the matching
64
+ race IDs. Schedule dates retain NASCAR's local timestamps without a timezone.
65
+
66
+ ## Work with laps
67
+
68
+ ```python
69
+ hocevar = session.laps.pick_driver("77")
70
+ print(hocevar.pick_fastest())
71
+ print(hocevar.pick_laps([1, 2, 3]))
72
+ print(hocevar.pick_quicklaps().LapTime.dt.total_seconds().mean())
73
+ print(session.results[["Position", "DriverNumber", "FullName", "Points"]])
74
+ ```
75
+
76
+ | Table | Columns |
77
+ | --- | --- |
78
+ | `session.laps` | Driver, DriverNumber, DriverId, LapNumber, LapTime, Speed, Position |
79
+ | `session.results` | DriverNumber, DriverId, FullName, TeamName, Manufacturer, Position, GridPosition, Laps, LapsLed, Points, Status, BestLapTime |
80
+ | `session.track_status` | LeaderLapNumber, FlagState |
81
+
82
+ Lap times are pandas timedeltas. Speeds are lap-average mph. Car numbers are
83
+ strings; missing values stay missing. Lap zero is excluded because it records
84
+ the starting order.
85
+
86
+ `pick_drivers()` accepts one or more car numbers or exact full names.
87
+ `pick_quicklaps(1.07)` keeps times below 107% of the fastest lap in the current
88
+ selection. Select a driver first to use that driver's pace. The filter doesn't
89
+ identify cautions or pit laps.
90
+
91
+ Flags refer to leader laps and stay separate from car laps. A lapped car's lap
92
+ number doesn't line up with the leader's. FlagState contains raw feed codes.
93
+
94
+ ## Practice and qualifying
95
+
96
+ ```python
97
+ practice = fastnascar.get_session(2026, race_id=5626, identifier="P")
98
+ practice.load(laps=False)
99
+ qualifying = fastnascar.get_session(2026, race_id=5626, identifier="Q")
100
+ qualifying.load(laps=False)
101
+ ```
102
+
103
+ These sessions provide results and best lap times. When a weekend has several
104
+ runs of the same type, pass the exact `run_name` from the weekend feed.
105
+ Qualifying results may cover only the final round.
106
+
107
+ ## Plot a race
108
+
109
+ ```python
110
+ from fastnascar.plotting import plot_lap_times
111
+ import matplotlib.pyplot as plt
112
+
113
+ plot_lap_times(session.laps, drivers=["77", "5"])
114
+ plt.show()
115
+ ```
116
+
117
+ The Bristol example saves a full-race chart, a pace detail view, and a CSV:
118
+
119
+ ```sh
120
+ python examples/hocevar_bristol_2026.py
121
+ python examples/hocevar_bristol_2026.py --race spring --show
122
+ ```
123
+
124
+ The default is the September 19, 2026 night race. Files go into `outputs/`.
125
+ On Windows, `examples/hocevar_bristol_2026.ps1` also runs the script using
126
+ an available Python installation or the bundled desktop runtime.
127
+
128
+ ## Live timing
129
+
130
+ ```python
131
+ import fastnascar
132
+
133
+ snapshot = fastnascar.get_live_session()
134
+ print(snapshot.timing)
135
+ print(snapshot.pit_stops)
136
+
137
+ for snapshot in fastnascar.iter_live(interval=2, max_updates=10):
138
+ print(snapshot.timestamp)
139
+ print(snapshot.timing.query("DriverNumber == '77'"))
140
+ ```
141
+
142
+ The live feed serves the currently active session. It can't replay an earlier
143
+ race. Requests bypass the cache. Updates can repeat or become stale; check the
144
+ source timestamp. Pass `race_id=...` to require a specific race.
145
+
146
+ Polling raises an error if the feed switches race, timing run, or series.
147
+ The default delay is two seconds, with a minimum of one second. LastLapTime
148
+ and ElapsedTime are timedeltas; gaps and status retain the feed's values.
149
+ Polling can miss laps between updates.
150
+
151
+ Record and replay snapshots:
152
+
153
+ ```python
154
+ fastnascar.record_live("race.jsonl", interval=2, max_updates=100)
155
+ for snapshot in fastnascar.replay_live("race.jsonl"):
156
+ print(snapshot.timestamp, snapshot.timing.head())
157
+ ```
158
+
159
+ Recordings append to the file and flush after each update. Replay runs offline.
160
+
161
+ ## Cache
162
+
163
+ ```python
164
+ fastnascar.Cache.enable_cache(".fastnascar-cache")
165
+ session = fastnascar.get_session(2026, race_id=5626).load()
166
+
167
+ # Reuse downloaded data without network access.
168
+ fastnascar.Cache.enable_cache(".fastnascar-cache", offline=True)
169
+ session = fastnascar.get_session(2026, race_id=5626).load()
170
+ ```
171
+
172
+ Settings apply across the process. Cached responses expire after an hour.
173
+ `refresh=True` on `load()` fetches new data. Offline mode accepts old entries
174
+ and raises on a cache miss, including live requests.
175
+ `Cache.disable_cache()` restores network access without deleting files;
176
+ `Cache.clear_cache()` removes this package's cached responses.
177
+
178
+ Unavailable feeds raise `DataNotAvailableError`; unsupported schemas raise
179
+ `InvalidDataError`. Accessing a table before loading it raises
180
+ `SessionNotLoadedError`. A failed reload keeps the previous session tables.
181
+ Function docstrings describe arguments, return values, and errors.
182
+
183
+ ## Data
184
+
185
+ Feeds are hosted at `https://cf.nascar.com`:
186
+
187
+ - Schedule: `cacher/{year}/race_list_basic.json`
188
+ - Results: `cacher/{year}/1/{race_id}/weekend-feed.json`
189
+ - Race laps and flags: `cacher/{year}/1/{race_id}/lap-times.json`
190
+ - Live timing: `live/feeds/live-feed.json`
191
+
192
+ Coverage depends on what NASCAR makes available. Verified examples include
193
+ the 2024 Food City 500, the 2026 Bristol night race, and Cup live timing.
194
+ Feed URLs and schemas can change.
195
+
196
+ Code is MIT licensed. NASCAR feed data and test excerpts aren't covered by
197
+ that license.
198
+
199
+ ## Development
200
+
201
+ ```sh
202
+ python -m pip install -e ".[dev]"
203
+ python -m unittest discover -s tests -v
204
+ python -m build
205
+ python -m twine check dist/*
206
+ ```
207
+
208
+ Tests run offline with recorded feed excerpts and mocked requests. Examples
209
+ make real requests. CI runs the tests on Python 3.10, 3.12, and 3.13.
@@ -0,0 +1,187 @@
1
+ # FastNASCAR
2
+
3
+ FastNASCAR provides NASCAR Cup lap times, results, and live timing in pandas.
4
+ Select a race, load the session, and work with its lap data:
5
+
6
+ ```python
7
+ import fastnascar
8
+
9
+ session = fastnascar.get_session(2026, race_id=5626).load()
10
+ hocevar = session.laps.pick_driver("77")
11
+ print(hocevar.pick_fastest())
12
+ ```
13
+
14
+ Early release. Race laps and weekend results work; historical practice and
15
+ qualifying laps aren't supported yet. No throttle, brake, RPM, or GPS telemetry.
16
+ This project is unaffiliated with NASCAR.
17
+
18
+ ## Install
19
+
20
+ Python 3.10+ is required. From a checkout:
21
+
22
+ ```sh
23
+ python -m pip install -e .
24
+ python -m pip install -e ".[plot]" # optional matplotlib charts
25
+ ```
26
+
27
+ ## Pick a race
28
+
29
+ ```python
30
+ schedule = fastnascar.get_event_schedule(2026)
31
+ print(schedule[["RoundNumber", "RaceId", "EventName", "EventDate"]])
32
+
33
+ session = fastnascar.get_session(2026, 29)
34
+ session = fastnascar.get_session(2026, "Bass Pro Shops Night Race")
35
+ session = fastnascar.get_session(2026, race_id=5626)
36
+ session.load()
37
+ ```
38
+
39
+ Rounds count points races in scheduled order. Use
40
+ `include_exhibition=True` to include exhibitions in the schedule; select those
41
+ by name or race ID. Repeated names raise `AmbiguousEventError`, with the matching
42
+ race IDs. Schedule dates retain NASCAR's local timestamps without a timezone.
43
+
44
+ ## Work with laps
45
+
46
+ ```python
47
+ hocevar = session.laps.pick_driver("77")
48
+ print(hocevar.pick_fastest())
49
+ print(hocevar.pick_laps([1, 2, 3]))
50
+ print(hocevar.pick_quicklaps().LapTime.dt.total_seconds().mean())
51
+ print(session.results[["Position", "DriverNumber", "FullName", "Points"]])
52
+ ```
53
+
54
+ | Table | Columns |
55
+ | --- | --- |
56
+ | `session.laps` | Driver, DriverNumber, DriverId, LapNumber, LapTime, Speed, Position |
57
+ | `session.results` | DriverNumber, DriverId, FullName, TeamName, Manufacturer, Position, GridPosition, Laps, LapsLed, Points, Status, BestLapTime |
58
+ | `session.track_status` | LeaderLapNumber, FlagState |
59
+
60
+ Lap times are pandas timedeltas. Speeds are lap-average mph. Car numbers are
61
+ strings; missing values stay missing. Lap zero is excluded because it records
62
+ the starting order.
63
+
64
+ `pick_drivers()` accepts one or more car numbers or exact full names.
65
+ `pick_quicklaps(1.07)` keeps times below 107% of the fastest lap in the current
66
+ selection. Select a driver first to use that driver's pace. The filter doesn't
67
+ identify cautions or pit laps.
68
+
69
+ Flags refer to leader laps and stay separate from car laps. A lapped car's lap
70
+ number doesn't line up with the leader's. FlagState contains raw feed codes.
71
+
72
+ ## Practice and qualifying
73
+
74
+ ```python
75
+ practice = fastnascar.get_session(2026, race_id=5626, identifier="P")
76
+ practice.load(laps=False)
77
+ qualifying = fastnascar.get_session(2026, race_id=5626, identifier="Q")
78
+ qualifying.load(laps=False)
79
+ ```
80
+
81
+ These sessions provide results and best lap times. When a weekend has several
82
+ runs of the same type, pass the exact `run_name` from the weekend feed.
83
+ Qualifying results may cover only the final round.
84
+
85
+ ## Plot a race
86
+
87
+ ```python
88
+ from fastnascar.plotting import plot_lap_times
89
+ import matplotlib.pyplot as plt
90
+
91
+ plot_lap_times(session.laps, drivers=["77", "5"])
92
+ plt.show()
93
+ ```
94
+
95
+ The Bristol example saves a full-race chart, a pace detail view, and a CSV:
96
+
97
+ ```sh
98
+ python examples/hocevar_bristol_2026.py
99
+ python examples/hocevar_bristol_2026.py --race spring --show
100
+ ```
101
+
102
+ The default is the September 19, 2026 night race. Files go into `outputs/`.
103
+ On Windows, `examples/hocevar_bristol_2026.ps1` also runs the script using
104
+ an available Python installation or the bundled desktop runtime.
105
+
106
+ ## Live timing
107
+
108
+ ```python
109
+ import fastnascar
110
+
111
+ snapshot = fastnascar.get_live_session()
112
+ print(snapshot.timing)
113
+ print(snapshot.pit_stops)
114
+
115
+ for snapshot in fastnascar.iter_live(interval=2, max_updates=10):
116
+ print(snapshot.timestamp)
117
+ print(snapshot.timing.query("DriverNumber == '77'"))
118
+ ```
119
+
120
+ The live feed serves the currently active session. It can't replay an earlier
121
+ race. Requests bypass the cache. Updates can repeat or become stale; check the
122
+ source timestamp. Pass `race_id=...` to require a specific race.
123
+
124
+ Polling raises an error if the feed switches race, timing run, or series.
125
+ The default delay is two seconds, with a minimum of one second. LastLapTime
126
+ and ElapsedTime are timedeltas; gaps and status retain the feed's values.
127
+ Polling can miss laps between updates.
128
+
129
+ Record and replay snapshots:
130
+
131
+ ```python
132
+ fastnascar.record_live("race.jsonl", interval=2, max_updates=100)
133
+ for snapshot in fastnascar.replay_live("race.jsonl"):
134
+ print(snapshot.timestamp, snapshot.timing.head())
135
+ ```
136
+
137
+ Recordings append to the file and flush after each update. Replay runs offline.
138
+
139
+ ## Cache
140
+
141
+ ```python
142
+ fastnascar.Cache.enable_cache(".fastnascar-cache")
143
+ session = fastnascar.get_session(2026, race_id=5626).load()
144
+
145
+ # Reuse downloaded data without network access.
146
+ fastnascar.Cache.enable_cache(".fastnascar-cache", offline=True)
147
+ session = fastnascar.get_session(2026, race_id=5626).load()
148
+ ```
149
+
150
+ Settings apply across the process. Cached responses expire after an hour.
151
+ `refresh=True` on `load()` fetches new data. Offline mode accepts old entries
152
+ and raises on a cache miss, including live requests.
153
+ `Cache.disable_cache()` restores network access without deleting files;
154
+ `Cache.clear_cache()` removes this package's cached responses.
155
+
156
+ Unavailable feeds raise `DataNotAvailableError`; unsupported schemas raise
157
+ `InvalidDataError`. Accessing a table before loading it raises
158
+ `SessionNotLoadedError`. A failed reload keeps the previous session tables.
159
+ Function docstrings describe arguments, return values, and errors.
160
+
161
+ ## Data
162
+
163
+ Feeds are hosted at `https://cf.nascar.com`:
164
+
165
+ - Schedule: `cacher/{year}/race_list_basic.json`
166
+ - Results: `cacher/{year}/1/{race_id}/weekend-feed.json`
167
+ - Race laps and flags: `cacher/{year}/1/{race_id}/lap-times.json`
168
+ - Live timing: `live/feeds/live-feed.json`
169
+
170
+ Coverage depends on what NASCAR makes available. Verified examples include
171
+ the 2024 Food City 500, the 2026 Bristol night race, and Cup live timing.
172
+ Feed URLs and schemas can change.
173
+
174
+ Code is MIT licensed. NASCAR feed data and test excerpts aren't covered by
175
+ that license.
176
+
177
+ ## Development
178
+
179
+ ```sh
180
+ python -m pip install -e ".[dev]"
181
+ python -m unittest discover -s tests -v
182
+ python -m build
183
+ python -m twine check dist/*
184
+ ```
185
+
186
+ Tests run offline with recorded feed excerpts and mocked requests. Examples
187
+ make real requests. CI runs the tests on Python 3.10, 3.12, and 3.13.
@@ -0,0 +1,9 @@
1
+ """Run after installing: python examples/analyze_race.py"""
2
+ import fastnascar
3
+
4
+ fastnascar.Cache.enable_cache(".fastnascar-cache")
5
+ session = fastnascar.get_session(2024, race_id=5392).load()
6
+ print(session.results[["Position", "DriverNumber", "FullName", "Points"]].head(10))
7
+ hamlin = session.laps.pick_driver("11")
8
+ print(hamlin.pick_fastest())
9
+ print(hamlin.pick_quicklaps().LapTime.dt.total_seconds().describe())
@@ -0,0 +1,26 @@
1
+ # Run from PowerShell: .\examples\hocevar_bristol_2026.ps1
2
+ # Pass Python script options, for example: --race spring --show
3
+ $taskRoot = Split-Path $PSScriptRoot -Parent
4
+ $taskPython = Get-Command python -ErrorAction SilentlyContinue
5
+ $taskBundledPython = Join-Path $env:USERPROFILE '.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe'
6
+ if ($taskPython) {
7
+ $taskPythonPath = $taskPython.Source
8
+ } elseif (Test-Path -LiteralPath $taskBundledPython) {
9
+ $taskPythonPath = $taskBundledPython
10
+ } else {
11
+ throw 'Python is required. Install Python 3.10 or newer and the package with its plot extra.'
12
+ }
13
+ $taskOldPythonPath = $env:PYTHONPATH
14
+ $taskOldMplConfig = $env:MPLCONFIGDIR
15
+ try {
16
+ $taskPlotDeps = Join-Path $taskRoot '.plot-deps'
17
+ if (Test-Path -LiteralPath $taskPlotDeps) {
18
+ $env:PYTHONPATH = "$taskPlotDeps;$taskOldPythonPath"
19
+ }
20
+ $env:MPLCONFIGDIR = Join-Path $taskRoot '.matplotlib'
21
+ & $taskPythonPath (Join-Path $PSScriptRoot 'hocevar_bristol_2026.py') @args
22
+ if ($LASTEXITCODE -ne 0) { throw "Chart script failed with exit code $LASTEXITCODE" }
23
+ } finally {
24
+ $env:PYTHONPATH = $taskOldPythonPath
25
+ $env:MPLCONFIGDIR = $taskOldMplConfig
26
+ }
@@ -0,0 +1,123 @@
1
+ """Chart Carson Hocevar's lap times at Bristol in 2026 using real NASCAR data.
2
+
3
+ From the repository root:
4
+ python -m pip install -e ".[plot]"
5
+ python examples/hocevar_bristol_2026.py
6
+ python examples/hocevar_bristol_2026.py --race spring --show
7
+
8
+ The default is the September 19 Bass Pro Shops Night Race (race ID 5626).
9
+ """
10
+ import argparse
11
+ from pathlib import Path
12
+ import sys
13
+
14
+ # Also works directly from the checkout when pandas/matplotlib are installed.
15
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
16
+
17
+ import fastnascar
18
+
19
+ RACES = {"fall": 5626, "spring": 5604}
20
+
21
+
22
+ def main():
23
+ """Parse CLI options, load Hocevar's race laps, and write PNG/SVG/CSV files.
24
+
25
+ Returns:
26
+ None
27
+
28
+ Raises:
29
+ SystemExit: Invalid CLI options, missing plotting dependencies, unavailable
30
+ race data, or no usable Hocevar lap times.
31
+ OSError: Output files cannot be created.
32
+ """
33
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
34
+ parser.add_argument("--race", choices=RACES, default="fall")
35
+ parser.add_argument("--output-dir", type=Path, default=Path(__file__).resolve().parents[1] / "outputs")
36
+ parser.add_argument("--show", action="store_true", help="Open the chart in a matplotlib window")
37
+ parser.add_argument("--refresh", action="store_true", help="Fetch fresh lap and result data")
38
+ args = parser.parse_args()
39
+
40
+ try:
41
+ import matplotlib
42
+ if not args.show:
43
+ matplotlib.use("Agg")
44
+ import matplotlib.pyplot as plt
45
+ from matplotlib.ticker import MultipleLocator
46
+ except ImportError:
47
+ parser.exit(1, 'Plotting requires matplotlib. Run: python -m pip install -e ".[plot]"\n')
48
+
49
+ output_dir = args.output_dir.expanduser().resolve()
50
+ output_dir.mkdir(parents=True, exist_ok=True)
51
+ fastnascar.Cache.enable_cache(Path(__file__).resolve().parents[1] / ".fastnascar-cache")
52
+ try:
53
+ session = fastnascar.get_session(2026, race_id=RACES[args.race]).load(refresh=args.refresh)
54
+ except fastnascar.FastNASCARException as exc:
55
+ parser.exit(1, f"Could not load the Bristol race: {exc}\n")
56
+
57
+ # Results use a plain full name; lap feeds may add a '(C)' playoff suffix.
58
+ driver = session.get_driver("Carson Hocevar")
59
+ laps = session.laps.loc[session.laps.DriverId == driver.DriverId].sort_values("LapNumber").copy()
60
+ seconds = laps.LapTime.dt.total_seconds()
61
+ if laps.empty or seconds.notna().sum() == 0:
62
+ parser.exit(1, "No lap times were available for Carson Hocevar.\n")
63
+
64
+ # Reindex missing lap numbers so the line breaks across any absent records.
65
+ plot_seconds = seconds.set_axis(laps.LapNumber.astype(int)).reindex(range(1, int(laps.LapNumber.max()) + 1))
66
+ median = plot_seconds.rolling(10, min_periods=10).median()
67
+ fastest = laps.pick_fastest()
68
+ base = f"hocevar_bristol_2026_{args.race}"
69
+ csv_path = output_dir / f"{base}.csv"
70
+ png_path = output_dir / f"{base}.png"
71
+ svg_path = output_dir / f"{base}.svg"
72
+ export = laps.copy()
73
+ export["LapTimeSeconds"] = seconds
74
+ export["Rolling10LapMedianSeconds"] = laps.LapNumber.map(median)
75
+ export.to_csv(csv_path, index=False)
76
+
77
+ with plt.rc_context({"font.family": "DejaVu Sans", "font.size": 10,
78
+ "axes.spines.top": False, "axes.spines.right": False}):
79
+ fig, axes = plt.subplots(2, 1, figsize=(13, 8), sharex=True,
80
+ gridspec_kw={"height_ratios": [1, 1.4]}, layout="constrained")
81
+ fig.patch.set_facecolor("#f7f9fc")
82
+ date = session.event.EventDate.strftime("%B %d, %Y")
83
+ fig.suptitle(f"Carson Hocevar · #{driver.DriverNumber} · Bristol 2026\n"
84
+ f"{session.event.EventName} · {date}", fontsize=17, fontweight="bold")
85
+ for ax in axes:
86
+ ax.set_facecolor("white")
87
+ ax.plot(plot_seconds.index, plot_seconds, color="#2374ab", linewidth=0.8,
88
+ marker=".", markersize=2.4, alpha=0.85, label="Recorded lap time")
89
+ ax.set_ylabel("Lap time (seconds)")
90
+ ax.grid(axis="y", color="#dce3ea", linewidth=0.6)
91
+ ax.set_xlim(1, max(2, int(laps.LapNumber.max())))
92
+ ax.xaxis.set_major_locator(MultipleLocator(50))
93
+ axes[0].set_title("All laps · full time range, including slow laps", loc="left", fontsize=11)
94
+ axes[0].legend(loc="upper right", frameon=False)
95
+ axes[1].plot(median.index, median, color="#e07826", linewidth=1.8,
96
+ label="10-lap rolling median (all recorded laps)")
97
+ # Same data in both panels; only the lower panel's displayed y-range is limited.
98
+ lower = max(0, float(seconds.min()) - 0.5)
99
+ upper = max(lower + 3, float(seconds.quantile(0.75)) + 2)
100
+ axes[1].set_ylim(lower, upper)
101
+ axes[1].set_title(f"Pace detail · y-axis zoom {lower:.1f}–{upper:.1f} s; slower laps remain above",
102
+ loc="left", fontsize=11)
103
+ axes[1].scatter([int(fastest.LapNumber)], [fastest.LapTime.total_seconds()],
104
+ color="#b93645", s=38, zorder=5,
105
+ label=f"Fastest: {fastest.LapTime.total_seconds():.3f} s (lap {fastest.LapNumber})")
106
+ axes[1].legend(loc="upper right", frameon=False, fontsize=9)
107
+ axes[1].set_xlabel("Hocevar's completed car lap")
108
+ fig.supxlabel("Source: NASCAR lap-times feed · No laps filtered · Gaps indicate missing data", fontsize=9)
109
+ fig.savefig(png_path, dpi=180, facecolor=fig.get_facecolor())
110
+ fig.savefig(svg_path, facecolor=fig.get_facecolor())
111
+ if args.show:
112
+ plt.show()
113
+ plt.close(fig)
114
+
115
+ print(f"{session.event.EventName} (race ID {session.event.RaceId})")
116
+ print(f"#{driver.DriverNumber} {driver.FullName}: {len(laps)} laps; finish P{driver.Position}")
117
+ print(f"Fastest lap: {fastest.LapTime.total_seconds():.3f}s on lap {fastest.LapNumber}")
118
+ for path in (png_path, svg_path, csv_path):
119
+ print(path)
120
+
121
+
122
+ if __name__ == "__main__":
123
+ main()
@@ -0,0 +1,6 @@
1
+ """Show ten updates from the currently active NASCAR Cup feed."""
2
+ import fastnascar
3
+
4
+ for snapshot in fastnascar.iter_live(interval=2, max_updates=10):
5
+ print(f"Race {snapshot.race_id}, lap {snapshot.lap_number}, flag {snapshot.flag_state}")
6
+ print(snapshot.timing[["Position", "DriverNumber", "Driver", "LastLapTime"]].head())
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fastnascar"
7
+ version = "0.1.0"
8
+ description = "Pandas-based NASCAR sessions, laps, results, and live timing"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ keywords = ["nascar", "motorsport", "timing", "pandas"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Programming Language :: Python :: 3",
17
+ "Operating System :: OS Independent",
18
+ "Topic :: Software Development :: Libraries :: Python Modules",
19
+ ]
20
+ dependencies = ["pandas>=2.2,<4"]
21
+
22
+ [project.optional-dependencies]
23
+ plot = ["matplotlib>=3.8"]
24
+ dev = ["pytest>=8", "build>=1", "twine>=6"]
25
+
26
+ [tool.setuptools.packages.find]
27
+ where = ["src"]
28
+
29
+ [tool.pytest.ini_options]
30
+ pythonpath = ["src"]
31
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ """FastNASCAR: NASCAR Cup data for pandas analysis."""
2
+ from .api import NASCARClient
3
+ from .cache import Cache
4
+ from .core import Laps, Session, get_event, get_event_schedule, get_session
5
+ from .exceptions import (AmbiguousEventError, DataNotAvailableError, EventNotFoundError,
6
+ FastNASCARException, InvalidDataError, SessionNotLoadedError)
7
+ from .live import LiveSnapshot, get_live_session, iter_live, record_live, replay_live
8
+
9
+ __version__ = "0.1.0"
10
+ __all__ = ["Cache", "NASCARClient", "Laps", "Session", "get_event", "get_event_schedule",
11
+ "get_session", "LiveSnapshot", "get_live_session", "iter_live", "record_live", "replay_live",
12
+ "FastNASCARException", "DataNotAvailableError", "InvalidDataError", "SessionNotLoadedError",
13
+ "EventNotFoundError", "AmbiguousEventError"]
@@ -0,0 +1,18 @@
1
+ """Conversions shared by historical and live timing parsers."""
2
+ import pandas as pd
3
+
4
+
5
+ def seconds_to_timedelta(values):
6
+ """Convert a Series of seconds to nanosecond-resolution pandas timedeltas.
7
+
8
+ Args:
9
+ values (pandas.Series): Numeric seconds or strings that represent seconds.
10
+
11
+ Returns:
12
+ pandas.Series: Timedeltas with the original index. Missing, nonnumeric,
13
+ and infinite values become ``NaT``. Empty Series keep the same dtype.
14
+ """
15
+ numeric = pd.to_numeric(values, errors="coerce")
16
+ numeric = numeric.mask(numeric.isin([float("inf"), -float("inf")]))
17
+ converted = pd.to_timedelta(numeric.fillna(0), unit="s").astype("timedelta64[ns]")
18
+ return converted.mask(numeric.isna())