contrails 0.4.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.
- contrails-0.4.0/.gitattributes +1 -0
- contrails-0.4.0/.github/dependabot.yml +23 -0
- contrails-0.4.0/.github/workflows/ci.yml +79 -0
- contrails-0.4.0/.github/workflows/pr-title.yml +21 -0
- contrails-0.4.0/.github/workflows/release-please.yml +56 -0
- contrails-0.4.0/.gitignore +34 -0
- contrails-0.4.0/.markdownlint-cli2.yaml +39 -0
- contrails-0.4.0/.pre-commit-config.yaml +39 -0
- contrails-0.4.0/.prettierignore +7 -0
- contrails-0.4.0/.prettierrc.json +4 -0
- contrails-0.4.0/.release-please-manifest.json +3 -0
- contrails-0.4.0/AGENTS.md +161 -0
- contrails-0.4.0/CHANGELOG.md +102 -0
- contrails-0.4.0/CLAUDE.md +1 -0
- contrails-0.4.0/LICENSE +21 -0
- contrails-0.4.0/PKG-INFO +530 -0
- contrails-0.4.0/README.md +476 -0
- contrails-0.4.0/config.example.json +13 -0
- contrails-0.4.0/config.example.yaml +77 -0
- contrails-0.4.0/docs/contrail-gh.md +102 -0
- contrails-0.4.0/docs/emissions.md +121 -0
- contrails-0.4.0/docs/parsing.md +144 -0
- contrails-0.4.0/docs/resync.md +139 -0
- contrails-0.4.0/docs/storage.md +60 -0
- contrails-0.4.0/docs/tripit-api.md +97 -0
- contrails-0.4.0/pyproject.toml +74 -0
- contrails-0.4.0/release-please-config.json +19 -0
- contrails-0.4.0/scripts/refresh_airline_codes.py +247 -0
- contrails-0.4.0/src/contrail/__init__.py +10 -0
- contrails-0.4.0/src/contrail/airlines.py +268 -0
- contrails-0.4.0/src/contrail/airports.py +101 -0
- contrails-0.4.0/src/contrail/cli.py +760 -0
- contrails-0.4.0/src/contrail/config.py +179 -0
- contrails-0.4.0/src/contrail/data/__init__.py +5 -0
- contrails-0.4.0/src/contrail/data/airline_codes.csv +2225 -0
- contrails-0.4.0/src/contrail/emissions/__init__.py +24 -0
- contrails-0.4.0/src/contrail/emissions/base.py +31 -0
- contrails-0.4.0/src/contrail/emissions/tim.py +316 -0
- contrails-0.4.0/src/contrail/importers/__init__.py +33 -0
- contrails-0.4.0/src/contrail/importers/base.py +28 -0
- contrails-0.4.0/src/contrail/importers/flighty_csv.py +274 -0
- contrails-0.4.0/src/contrail/importers/tripit_ical.py +281 -0
- contrails-0.4.0/src/contrail/models.py +149 -0
- contrails-0.4.0/src/contrail/resync.py +249 -0
- contrails-0.4.0/src/contrail/storage/__init__.py +77 -0
- contrails-0.4.0/src/contrail/storage/base.py +24 -0
- contrails-0.4.0/src/contrail/storage/local_csv.py +184 -0
- contrails-0.4.0/src/contrail/storage/raw_log.py +107 -0
- contrails-0.4.0/tests/conftest.py +20 -0
- contrails-0.4.0/tests/fixtures/sample_feed.ics +79 -0
- contrails-0.4.0/tests/fixtures/sample_flighty.csv +12 -0
- contrails-0.4.0/tests/test_airlines.py +150 -0
- contrails-0.4.0/tests/test_airports.py +82 -0
- contrails-0.4.0/tests/test_cli.py +621 -0
- contrails-0.4.0/tests/test_config.py +129 -0
- contrails-0.4.0/tests/test_flighty_csv_importer.py +191 -0
- contrails-0.4.0/tests/test_local_csv_storage.py +310 -0
- contrails-0.4.0/tests/test_raw_log.py +114 -0
- contrails-0.4.0/tests/test_resync.py +632 -0
- contrails-0.4.0/tests/test_tim_emissions.py +316 -0
- contrails-0.4.0/tests/test_tripit_ical_importer.py +230 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* text=auto
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
# A runtime dependency bump changes what users install, so it has to reach the
|
|
4
|
+
# changelog and cut a release: `fix` is shown by release-please, `chore` is
|
|
5
|
+
# hidden by default and would ship the change invisibly. Dev dependencies
|
|
6
|
+
# affect nobody downstream, so they stay `chore`.
|
|
7
|
+
- package-ecosystem: pip
|
|
8
|
+
directory: /
|
|
9
|
+
schedule:
|
|
10
|
+
interval: weekly
|
|
11
|
+
commit-message:
|
|
12
|
+
prefix: fix
|
|
13
|
+
prefix-development: chore
|
|
14
|
+
include: scope
|
|
15
|
+
|
|
16
|
+
# CI only — nothing here reaches an installed contrail, so it stays hidden.
|
|
17
|
+
- package-ecosystem: github-actions
|
|
18
|
+
directory: /
|
|
19
|
+
schedule:
|
|
20
|
+
interval: weekly
|
|
21
|
+
commit-message:
|
|
22
|
+
prefix: chore
|
|
23
|
+
include: scope
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
lint:
|
|
13
|
+
name: ruff
|
|
14
|
+
# Ten minutes against a job that takes ten seconds. The point is a `pip`
|
|
15
|
+
# fetch that stalls rather than fails, which would otherwise sit until
|
|
16
|
+
# GitHub's six-hour default.
|
|
17
|
+
timeout-minutes: 10
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v7
|
|
21
|
+
- uses: actions/setup-python@v7
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.12"
|
|
24
|
+
- run: pip install -e ".[dev]"
|
|
25
|
+
- run: ruff check .
|
|
26
|
+
- run: ruff format --check .
|
|
27
|
+
|
|
28
|
+
test:
|
|
29
|
+
# Interpolated, not a fixed string: a matrix job with a static `name:`
|
|
30
|
+
# produces three checks called the same thing, and the version under test
|
|
31
|
+
# is the only thing that tells them apart.
|
|
32
|
+
name: pytest ${{ matrix.python-version }}
|
|
33
|
+
timeout-minutes: 10
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
strategy:
|
|
36
|
+
fail-fast: false
|
|
37
|
+
matrix:
|
|
38
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v7
|
|
41
|
+
- uses: actions/setup-python@v7
|
|
42
|
+
with:
|
|
43
|
+
python-version: ${{ matrix.python-version }}
|
|
44
|
+
- run: pip install -e ".[dev]"
|
|
45
|
+
- name: Run tests
|
|
46
|
+
run: pytest -q
|
|
47
|
+
- name: Dry-run against the fixture sources
|
|
48
|
+
# Catches a broken parser here, before it can reach anyone's production
|
|
49
|
+
# instance through a version tag. Both importers, since they also have to
|
|
50
|
+
# agree with each other about which flights are the same one. Needs no
|
|
51
|
+
# secrets: --dry-run never calls the emissions API, and both importers
|
|
52
|
+
# accept a local path.
|
|
53
|
+
env:
|
|
54
|
+
TRIPIT_ICAL_URL: tests/fixtures/sample_feed.ics
|
|
55
|
+
FLIGHTY_CSV_PATH: tests/fixtures/sample_flighty.csv
|
|
56
|
+
run: contrail sync --dry-run --csv-path "${RUNNER_TEMP}/should-not-be-written.csv"
|
|
57
|
+
|
|
58
|
+
docs:
|
|
59
|
+
name: markdown
|
|
60
|
+
# `npx` fetches Prettier from the registry on every run, and a stalled
|
|
61
|
+
# fetch hangs rather than failing. The same line in atdr/contrail-gh has
|
|
62
|
+
# sat silent for exactly 301 seconds, npm's fetch-timeout expiring and the
|
|
63
|
+
# retry succeeding, in a job that takes twelve seconds when the fetch is
|
|
64
|
+
# clean. Ten minutes bounds a stall that never recovers.
|
|
65
|
+
timeout-minutes: 10
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@v7
|
|
69
|
+
# An action rather than `npx`, so the github-actions dependabot ecosystem
|
|
70
|
+
# already configured for this repo keeps it current. Prettier has no
|
|
71
|
+
# first-party action, so its pin below is a hand edit — the same deal as
|
|
72
|
+
# the `rev:` pins in .pre-commit-config.yaml.
|
|
73
|
+
- uses: DavidAnson/markdownlint-cli2-action@v24
|
|
74
|
+
- uses: actions/setup-node@v7
|
|
75
|
+
with:
|
|
76
|
+
node-version: "24"
|
|
77
|
+
# Prettier owns table alignment and whitespace; markdownlint owns the
|
|
78
|
+
# rules it can't express. Neither is redundant.
|
|
79
|
+
- run: npx prettier@3.9.6 --check "**/*.md"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: pr-title
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request_target:
|
|
5
|
+
types: [opened, edited, synchronize, reopened]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
pull-requests: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
lint:
|
|
12
|
+
name: conventional title
|
|
13
|
+
timeout-minutes: 5
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
# On a squash merge the PR title becomes the commit subject, which is what
|
|
17
|
+
# release-please reads. An unconventional title silently drops the change
|
|
18
|
+
# from the changelog, so validate it up front.
|
|
19
|
+
- uses: amannn/action-semantic-pull-request@v6
|
|
20
|
+
env:
|
|
21
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: release-please
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
pull-requests: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release-please:
|
|
13
|
+
name: maintain the release PR
|
|
14
|
+
# This one holds every release behind it, so a hang here is a release that
|
|
15
|
+
# silently never happens. Ten minutes against a job that takes seconds.
|
|
16
|
+
timeout-minutes: 10
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
outputs:
|
|
19
|
+
release_created: ${{ steps.release.outputs.release_created }}
|
|
20
|
+
steps:
|
|
21
|
+
# Maintains a rolling release PR from the conventional commits on main.
|
|
22
|
+
# Merging it bumps the version in pyproject.toml, updates CHANGELOG.md,
|
|
23
|
+
# tags the release, and publishes it.
|
|
24
|
+
#
|
|
25
|
+
# Note: PRs opened by the default GITHUB_TOKEN do not trigger other
|
|
26
|
+
# workflows, so the release PR itself shows no CI run. The commits it
|
|
27
|
+
# releases were already tested on main.
|
|
28
|
+
- uses: googleapis/release-please-action@v5
|
|
29
|
+
id: release
|
|
30
|
+
with:
|
|
31
|
+
config-file: release-please-config.json
|
|
32
|
+
manifest-file: .release-please-manifest.json
|
|
33
|
+
|
|
34
|
+
publish:
|
|
35
|
+
name: publish to PyPI
|
|
36
|
+
needs: release-please
|
|
37
|
+
if: needs.release-please.outputs.release_created
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
environment: pypi
|
|
40
|
+
permissions:
|
|
41
|
+
# No API token: PyPI trusts this workflow (owner/repo/environment) to
|
|
42
|
+
# mint its own short-lived credential via OIDC.
|
|
43
|
+
id-token: write
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@v7
|
|
46
|
+
|
|
47
|
+
- uses: actions/setup-python@v7
|
|
48
|
+
with:
|
|
49
|
+
python-version: "3.12"
|
|
50
|
+
|
|
51
|
+
- name: Build
|
|
52
|
+
run: |
|
|
53
|
+
pip install build
|
|
54
|
+
python -m build
|
|
55
|
+
|
|
56
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Local configuration and personal data — never commit these.
|
|
2
|
+
# `contrail sync` writes ./flight_emissions.csv by default, and this repo is public.
|
|
3
|
+
config.json
|
|
4
|
+
config.yaml
|
|
5
|
+
config.yml
|
|
6
|
+
# Glob, not just the default filename: backups and per-account variants
|
|
7
|
+
# (flight_emissions.backup-*.csv, flight_emissions-work.csv) are real flight
|
|
8
|
+
# data too, and this repo is public.
|
|
9
|
+
flight_emissions*.csv
|
|
10
|
+
*.backup-*.csv
|
|
11
|
+
# The raw provider log holds real itineraries too.
|
|
12
|
+
*.raw.jsonl
|
|
13
|
+
last_checked.txt
|
|
14
|
+
.env
|
|
15
|
+
.env.*
|
|
16
|
+
|
|
17
|
+
# Python
|
|
18
|
+
__pycache__/
|
|
19
|
+
*.py[cod]
|
|
20
|
+
*.egg-info/
|
|
21
|
+
build/
|
|
22
|
+
dist/
|
|
23
|
+
.pytest_cache/
|
|
24
|
+
.ruff_cache/
|
|
25
|
+
venv/
|
|
26
|
+
.venv/
|
|
27
|
+
|
|
28
|
+
# Editors / OS
|
|
29
|
+
.DS_Store
|
|
30
|
+
.idea/
|
|
31
|
+
.vscode/
|
|
32
|
+
|
|
33
|
+
# Planning notes stay local — this repo is public.
|
|
34
|
+
_planning/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Prettier owns whitespace and table alignment. These are the rules it cannot
|
|
2
|
+
# express. Kept in step by hand across contrail, contrail-gh and any instance
|
|
3
|
+
# created from it — the three files are copies, not a shared package.
|
|
4
|
+
#
|
|
5
|
+
# .prettierrc.json carries two settings and no room to explain them:
|
|
6
|
+
# `proseWrap: preserve`, because the prose here is wrapped by hand at
|
|
7
|
+
# deliberate points and `always` would rewrap every paragraph in the repo; and
|
|
8
|
+
# `embeddedLanguageFormatting: off`, because the code inside a fence is a
|
|
9
|
+
# worked example or a command someone will paste, not source to be tidied.
|
|
10
|
+
gitignore: true
|
|
11
|
+
|
|
12
|
+
# Without this, a bare `markdownlint-cli2` finds nothing and reports success.
|
|
13
|
+
globs:
|
|
14
|
+
- "**/*.md"
|
|
15
|
+
|
|
16
|
+
config:
|
|
17
|
+
MD013:
|
|
18
|
+
# AGENTS.md and docs/ already hold 80. README.md opts up to 100 with a
|
|
19
|
+
# markdownlint-configure-file comment at the foot of the file.
|
|
20
|
+
line_length: 80
|
|
21
|
+
# A padded table row is wider than any prose limit, and a fenced block is
|
|
22
|
+
# usually a command that must not be broken.
|
|
23
|
+
tables: false
|
|
24
|
+
code_blocks: false
|
|
25
|
+
MD046:
|
|
26
|
+
# GitHub only syntax-highlights fenced blocks, so an indented one silently
|
|
27
|
+
# loses the highlighting the author meant to have.
|
|
28
|
+
style: fenced
|
|
29
|
+
|
|
30
|
+
ignores:
|
|
31
|
+
# release-please regenerates this from commit subjects. Anything done to it
|
|
32
|
+
# here is clobbered on the next release.
|
|
33
|
+
- CHANGELOG.md
|
|
34
|
+
# A symlink to AGENTS.md. Linting it reports every line of that file twice.
|
|
35
|
+
- CLAUDE.md
|
|
36
|
+
# Local agent scratch, kept out of this public repo via .git/info/exclude —
|
|
37
|
+
# which `gitignore: true` above does not read, so it needs naming here. CI
|
|
38
|
+
# never sees the file; a full-tree run on a dev machine does.
|
|
39
|
+
- .claude/**
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Optional local convenience: `pre-commit install`. CI is the real gate.
|
|
2
|
+
repos:
|
|
3
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
4
|
+
rev: v0.16.3
|
|
5
|
+
hooks:
|
|
6
|
+
# `ruff-check`, not `ruff`: the latter is a deprecated alias kept for
|
|
7
|
+
# compatibility, running the identical `ruff check`. It will be removed
|
|
8
|
+
# eventually, and the failure would land on an unrelated `rev` bump.
|
|
9
|
+
- id: ruff-check
|
|
10
|
+
args: [--fix]
|
|
11
|
+
- id: ruff-format
|
|
12
|
+
|
|
13
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
14
|
+
rev: v6.0.0
|
|
15
|
+
hooks:
|
|
16
|
+
- id: trailing-whitespace
|
|
17
|
+
- id: end-of-file-fixer
|
|
18
|
+
- id: check-yaml
|
|
19
|
+
- id: check-json
|
|
20
|
+
- id: check-added-large-files
|
|
21
|
+
|
|
22
|
+
# Markdown. Keep these two in step with the pins in .github/workflows/ci.yml —
|
|
23
|
+
# a hook that lints to a different version than CI is worse than no hook.
|
|
24
|
+
- repo: https://github.com/DavidAnson/markdownlint-cli2
|
|
25
|
+
rev: v0.23.2
|
|
26
|
+
hooks:
|
|
27
|
+
- id: markdownlint-cli2
|
|
28
|
+
# The `globs` in .markdownlint-cli2.yaml already cover every file, and
|
|
29
|
+
# markdownlint-cli2 adds any filenames it is given to them rather than
|
|
30
|
+
# replacing them — so passing the staged files just reports each of
|
|
31
|
+
# them twice.
|
|
32
|
+
pass_filenames: false
|
|
33
|
+
|
|
34
|
+
# The maintained fork; pre-commit/mirrors-prettier is archived.
|
|
35
|
+
- repo: https://github.com/rbubley/mirrors-prettier
|
|
36
|
+
rev: v3.9.6
|
|
37
|
+
hooks:
|
|
38
|
+
- id: prettier
|
|
39
|
+
types_or: [markdown]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Regenerated by release-please; see .markdownlint-cli2.yaml for why.
|
|
2
|
+
CHANGELOG.md
|
|
3
|
+
# Symlink to AGENTS.md — formatting it writes through to the same file twice.
|
|
4
|
+
CLAUDE.md
|
|
5
|
+
# Local agent scratch, excluded via .git/info/exclude, which Prettier does not
|
|
6
|
+
# read. See .markdownlint-cli2.yaml.
|
|
7
|
+
.claude/
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# contrail
|
|
2
|
+
|
|
3
|
+
Estimates the CO2e of flights taken or booked and keeps a log. Reads a TripIt
|
|
4
|
+
iCal feed and Flighty CSV exports, prices each flight with Google's Travel Impact
|
|
5
|
+
Model, writes a CSV.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
python3.12 -m venv venv && ./venv/bin/pip install -e ".[dev]"
|
|
11
|
+
./venv/bin/pytest -q
|
|
12
|
+
./venv/bin/ruff check . && ./venv/bin/ruff format .
|
|
13
|
+
TRIPIT_ICAL_URL=tests/fixtures/sample_feed.ics \
|
|
14
|
+
FLIGHTY_CSV_PATH=tests/fixtures/sample_flighty.csv ./venv/bin/contrail sync --dry-run
|
|
15
|
+
./venv/bin/python scripts/refresh_airline_codes.py # needs network; run by hand
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Python 3.11+. The default `python3` on this machine is
|
|
19
|
+
3.7 — use `/usr/local/bin/python3.12` explicitly.
|
|
20
|
+
|
|
21
|
+
## Architecture
|
|
22
|
+
|
|
23
|
+
Three protocol seams:
|
|
24
|
+
|
|
25
|
+
| Seam | Protocol | Implementations |
|
|
26
|
+
| ------------ | ----------------------------------------------------------------------- | ---------------------------- |
|
|
27
|
+
| `importers/` | `Importer.fetch(config) -> Iterable[FlightRecord \| UnparsedEvent]` | `tripit_ical`, `flighty_csv` |
|
|
28
|
+
| `emissions/` | `EmissionsProvider.compute(flights, now) -> dict[key, EmissionsResult]` | `tim` |
|
|
29
|
+
| `storage/` | `Storage.load() -> list[dict]`, `save(rows)` | `local_csv` |
|
|
30
|
+
|
|
31
|
+
`cli.py` owns the flow: load config → load storage → collect from every source →
|
|
32
|
+
collapse records that are the same flight → reconcile against what's stored →
|
|
33
|
+
price → build rows → normalize → save.
|
|
34
|
+
|
|
35
|
+
Two keys, and the difference matters:
|
|
36
|
+
|
|
37
|
+
- **`f"{source}:{source_id}"`** identifies a _record_. It is the dedup key and
|
|
38
|
+
what `also_seen_as` stores.
|
|
39
|
+
- **`(flight_date, origin, destination)`** identifies a _flight_, across sources.
|
|
40
|
+
`resync.identity()`. Never the flight number — see the gotchas.
|
|
41
|
+
|
|
42
|
+
## Conventions
|
|
43
|
+
|
|
44
|
+
- Conventional commits. release-please owns versions, tags and `CHANGELOG.md` —
|
|
45
|
+
never hand-edit the changelog or the version in `pyproject.toml`.
|
|
46
|
+
- `__version__` is read from installed package metadata, so `pyproject.toml` is
|
|
47
|
+
the single source of truth. Code that needs the version imports `__version__`
|
|
48
|
+
rather than spelling it out — see the gotcha on version pins for the two
|
|
49
|
+
literals that remain.
|
|
50
|
+
- **No test may make a real network call.** Mock `requests` in both directions.
|
|
51
|
+
- **Every workflow is named after its own file**, and the description goes on
|
|
52
|
+
the job. GitHub labels a check `<workflow name> / <job name>` and never shows
|
|
53
|
+
the filename, so `pr-title / conventional title` names both the file to open
|
|
54
|
+
and what it did. A matrix job's `name:` has to interpolate the matrix value
|
|
55
|
+
(`pytest ${{ matrix.python-version }}`) or all three legs produce checks
|
|
56
|
+
called the same thing. `atdr/contrail-gh` enforces this in CI; here it is
|
|
57
|
+
convention. The `Analyze (python)` and `Analyze (actions)` checks are the
|
|
58
|
+
exception and always will be: CodeQL runs from default setup, which is a repo
|
|
59
|
+
setting rather than a file, so its name and its timeout are GitHub's to
|
|
60
|
+
choose.
|
|
61
|
+
- **Every job that can carry `timeout-minutes` sets one.** GitHub's default is
|
|
62
|
+
six hours, and the failure that matters is a stall rather than an error: a
|
|
63
|
+
`pip` or `npx` fetch that hangs never fails on its own. Ten minutes
|
|
64
|
+
everywhere except `pr-title.yml`, which gets five. A job whose only key is
|
|
65
|
+
`uses:` cannot carry it, which is why the setting lives inside a reusable
|
|
66
|
+
workflow rather than on its callers.
|
|
67
|
+
- **Markdown is formatted, not hand-aligned.** Prettier owns table padding and
|
|
68
|
+
whitespace; markdownlint-cli2 owns line length and the rest. Run
|
|
69
|
+
`npx prettier@3.9.6 --write "**/*.md"` rather than lining a table up by
|
|
70
|
+
hand. Prose wraps at 80, except `README.md`, which wraps at 100 and says so
|
|
71
|
+
in a `markdownlint-configure-file` comment at its foot.
|
|
72
|
+
- This repo is public. Never commit a real CSV, a raw log, or `config.json` —
|
|
73
|
+
all are gitignored.
|
|
74
|
+
- **When updating docs at the end of a change, skim the open issues**
|
|
75
|
+
(`gh issue list`) for any the change touched. Cheap, and it catches both
|
|
76
|
+
directions: an issue quietly fixed, and one made easier to hit.
|
|
77
|
+
|
|
78
|
+
**Findings about your own change belong in the same PR** — that is not two
|
|
79
|
+
things in one PR, it is one change described accurately. A doc line your change
|
|
80
|
+
just made wrong, or an issue whose shape it altered, is part of that change; a
|
|
81
|
+
reviewer needs it in front of them, and splitting it out means `main` is briefly
|
|
82
|
+
wrong on purpose. Only a finding that stands entirely apart from what you built
|
|
83
|
+
earns its own PR; note it on the issue either way.
|
|
84
|
+
|
|
85
|
+
The one exception is the release PR: `chore(main): release X.Y.Z` is generated
|
|
86
|
+
by release-please from commit subjects, so anything hand-added there is
|
|
87
|
+
clobbered on the next regeneration.
|
|
88
|
+
|
|
89
|
+
## Depth
|
|
90
|
+
|
|
91
|
+
- [docs/parsing.md](docs/parsing.md) — the ported regexes, local dates,
|
|
92
|
+
codeshares, why `parse()` is two-pass, reading a Flighty export
|
|
93
|
+
- [docs/emissions.md](docs/emissions.md) — exact vs route average, the past-date
|
|
94
|
+
400, what gets kept, and the open question about timing
|
|
95
|
+
- [docs/resync.md](docs/resync.md) — what a sync may change, the freeze boundary,
|
|
96
|
+
cancellation, matching one flight across two sources
|
|
97
|
+
- [docs/storage.md](docs/storage.md) — CSV invariants and why there's no total
|
|
98
|
+
- [docs/tripit-api.md](docs/tripit-api.md) — investigated, not used, and why
|
|
99
|
+
- [docs/contrail-gh.md](docs/contrail-gh.md) — **what a change here obliges in the
|
|
100
|
+
template repo**
|
|
101
|
+
|
|
102
|
+
## Gotchas most likely to bite
|
|
103
|
+
|
|
104
|
+
- **The regexes in `importers/tripit_ical.py` are validated against real TripIt
|
|
105
|
+
feeds.** They look untidy because real feeds are. Don't rewrite one without a
|
|
106
|
+
failing test that proves the current form is wrong.
|
|
107
|
+
- **Mutation is confined to flights that haven't departed.** Past rows are never
|
|
108
|
+
touched — that's what makes "absent from the feed" unambiguous. One exception:
|
|
109
|
+
`resync.backfill()` fills a _blank_ cabin, aircraft or reason on a row of any
|
|
110
|
+
age, because a Flighty export is almost entirely past flights and it re-prices
|
|
111
|
+
nothing.
|
|
112
|
+
- **A flight is identified by route and date, never by flight number.** `BA16` is
|
|
113
|
+
SYD-SIN-LHR on one day: two legs, two cabins, two rows. Any "dedup by flight
|
|
114
|
+
number" idea silently merges them and loses a figure.
|
|
115
|
+
- **The file is written only when content actually changed**, or contrail-gh
|
|
116
|
+
commits every day for nothing.
|
|
117
|
+
- **Every value in a row dict is a string**, so a fresh row compares equal to one
|
|
118
|
+
loaded from the CSV. Every row builder must set every column.
|
|
119
|
+
- **TIM cannot be asked twice.** It won't price a departed flight, so anything not
|
|
120
|
+
captured while it was upcoming is gone permanently.
|
|
121
|
+
- **A runtime dependency bump must be `fix(deps):`, not `chore(deps):`.**
|
|
122
|
+
release-please hides `chore` by default and counts only `feat` and breaking
|
|
123
|
+
changes toward a bump, so a `chore(deps)` bump of a runtime dependency changes
|
|
124
|
+
what users install with nothing in the changelog and no release. Dev
|
|
125
|
+
dependencies stay `chore(deps-dev):`.
|
|
126
|
+
- **The two install pins in `README.md` are rewritten by release-please**, not by
|
|
127
|
+
hand. They sit inside `<!-- x-release-please-start-version -->` /
|
|
128
|
+
`<!-- x-release-please-end -->` comments, and `README.md` is listed under
|
|
129
|
+
`extra-files` in `release-please-config.json` — both halves are needed. Delete
|
|
130
|
+
a comment and the pin quietly rots to whatever release it was written against,
|
|
131
|
+
which is exactly how it drifted before. A _new_ literal version anywhere else
|
|
132
|
+
is a bug: import `__version__`.
|
|
133
|
+
- **release-please needs the repo setting "Allow GitHub Actions to create and
|
|
134
|
+
approve pull requests"** (Settings → Actions → General). `permissions:
|
|
135
|
+
pull-requests: write` in the workflow is _not_ sufficient on its own, and the
|
|
136
|
+
API can report the flag as enabled while it is still blocked. Without it the
|
|
137
|
+
release job fails with "GitHub Actions is not permitted to create or approve
|
|
138
|
+
pull requests".
|
|
139
|
+
- **`CHANGELOG.md` and `CLAUDE.md` are excluded from both Markdown tools.**
|
|
140
|
+
release-please regenerates the changelog from commit subjects, so a
|
|
141
|
+
reformat there is undone on the next release and can desync the manifest;
|
|
142
|
+
`CLAUDE.md` is a symlink to `AGENTS.md`, so linting it reports every line
|
|
143
|
+
twice and formatting it writes the same file twice. Both are named in
|
|
144
|
+
`.markdownlint-cli2.yaml` and `.prettierignore`.
|
|
145
|
+
- **`cli._now()` exists to be monkeypatched.** Tests that use the real clock rot
|
|
146
|
+
once the fixture's dates fall into the past.
|
|
147
|
+
- **`src/contrail/data/airline_codes.csv` is generated, never hand-edited.** Fix
|
|
148
|
+
it upstream in Wikidata and re-run the script, or the next refresh reverts you.
|
|
149
|
+
It must be written with LF: `.gitattributes` normalizes, and the csv module
|
|
150
|
+
defaults to CRLF.
|
|
151
|
+
- **`tests/fixtures/sample_flighty.csv` is synthetic.** This repo is public and
|
|
152
|
+
a real export is someone's entire travel history.
|
|
153
|
+
|
|
154
|
+
## Related repos
|
|
155
|
+
|
|
156
|
+
- `atdr/contrail-gh` — public GitHub Actions template. Pins a contrail release
|
|
157
|
+
tag and ships a header-only CSV that has to match it. **A schema or output
|
|
158
|
+
change here needs a matching change there** — see
|
|
159
|
+
[docs/contrail-gh.md](docs/contrail-gh.md).
|
|
160
|
+
- A **private instance** created from that template (`octocat/my-contrail` in the
|
|
161
|
+
docs). Real data and secrets — never referenced by name in anything public.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.4.0](https://github.com/atdr/contrail/compare/v0.3.1...v0.4.0) (2026-09-05)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **release:** publish to PyPI as `contrails` ([#34](https://github.com/atdr/contrail/issues/34)) ([b34d65f](https://github.com/atdr/contrail/commit/b34d65ff050dee790bb416d272cc3dad0eb7be9f))
|
|
9
|
+
|
|
10
|
+
## [0.3.1](https://github.com/atdr/contrail/compare/v0.3.0...v0.3.1) (2026-09-05)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **deps:** Update airportsdata requirement from >=20260803 to >=20260902 ([#31](https://github.com/atdr/contrail/issues/31)) ([24f6c2e](https://github.com/atdr/contrail/commit/24f6c2eac97357a3ae01a4887210f8032b4406d8))
|
|
16
|
+
* **deps:** Update icalendar requirement from >=7.2.2 to >=7.3.0 ([#29](https://github.com/atdr/contrail/issues/29)) ([640788f](https://github.com/atdr/contrail/commit/640788fd2720046957835ea4036d7ae94e8716fb))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Documentation
|
|
20
|
+
|
|
21
|
+
* a file-reading importer now needs its env var in two workflows ([#20](https://github.com/atdr/contrail/issues/20)) ([353b0c4](https://github.com/atdr/contrail/commit/353b0c4774e0e3cca444623bb437813eff13ad79))
|
|
22
|
+
|
|
23
|
+
## [0.3.0](https://github.com/atdr/contrail/compare/v0.2.0...v0.3.0) (2026-08-16)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* add a flighty_csv importer and match flights across sources ([ba0d292](https://github.com/atdr/contrail/commit/ba0d2921f04f6e3b92ec4f6c18c7024206cf9c54))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Bug Fixes
|
|
32
|
+
|
|
33
|
+
* stop version references drifting from the release ([429507c](https://github.com/atdr/contrail/commit/429507c8d3d121c20c62e67b883cdaeb44776ffb))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Documentation
|
|
37
|
+
|
|
38
|
+
* note the new way to reach the per-source identity gap ([941db88](https://github.com/atdr/contrail/commit/941db88d90a1b203aed0e5e7ce1905df389d4ef0))
|
|
39
|
+
|
|
40
|
+
## [0.2.0](https://github.com/atdr/contrail/compare/v0.1.0...v0.2.0) (2026-08-15)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
### ⚠ BREAKING CHANGES
|
|
44
|
+
|
|
45
|
+
* Python 3.10 is no longer supported. requires-python is now >=3.11.
|
|
46
|
+
|
|
47
|
+
### Features
|
|
48
|
+
|
|
49
|
+
* require Python 3.11 ([18cdc6e](https://github.com/atdr/contrail/commit/18cdc6ea6251f10870303a9d17fe20e7f7a26d64))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
### Bug Fixes
|
|
53
|
+
|
|
54
|
+
* **deps:** declare the pyyaml floor once and bound ruff ([8790755](https://github.com/atdr/contrail/commit/8790755521ace3cdbaf39e4a4e658762e6a28a0a))
|
|
55
|
+
* **deps:** Update icalendar requirement from >=5.0 to >=7.2.2 ([ee39316](https://github.com/atdr/contrail/commit/ee393167748db7b2cabd5d3667eb50b0837ab6e5))
|
|
56
|
+
* **deps:** Update pyyaml requirement from >=6 to >=6.0.3 ([2ed4484](https://github.com/atdr/contrail/commit/2ed4484142c9ef200c743634d54e286a43ca12b9))
|
|
57
|
+
* release runtime dependency bumps instead of hiding them ([68ff550](https://github.com/atdr/contrail/commit/68ff550a4d14db328fe017c235d5e59539767445))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### Documentation
|
|
61
|
+
|
|
62
|
+
* note the Actions setting release-please needs ([5e1778b](https://github.com/atdr/contrail/commit/5e1778b7c0ffde2475b83b00482d21ac8e843446))
|
|
63
|
+
|
|
64
|
+
## 0.1.0 (2026-08-15)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
### Features
|
|
68
|
+
|
|
69
|
+
* add config resolution and CLI ([ca9f865](https://github.com/atdr/contrail/commit/ca9f86567ec0a223ea8823d75799f5fa79aaadc2))
|
|
70
|
+
* add core data model ([5c7b0b6](https://github.com/atdr/contrail/commit/5c7b0b639853c1f18d8a0f080a638baccd02d6d4))
|
|
71
|
+
* add CSV storage keyed on cumulative_kg_actual ([c633763](https://github.com/atdr/contrail/commit/c63376384d358b8024dd2eb7d6a842dab2695d54))
|
|
72
|
+
* add importer seam and tripit_ical importer ([e52aafd](https://github.com/atdr/contrail/commit/e52aafd810b73730ac003ea2224a39f9069bcddf))
|
|
73
|
+
* add TIM emissions provider with route-average fallback ([9badcc4](https://github.com/atdr/contrail/commit/9badcc4c990d618d6363dbc7b01cea2df9672605))
|
|
74
|
+
* drop the stored cumulative total from the CSV ([0ce92b6](https://github.com/atdr/contrail/commit/0ce92b6bd8f62dc5837c33dd68df0618f69ab4e3))
|
|
75
|
+
* price codeshares as the operating flight ([f456f73](https://github.com/atdr/contrail/commit/f456f73e912d4ae1e1bbac1e3eaaf5bfc5d1d23d))
|
|
76
|
+
* re-sync flights that have not yet departed ([372606f](https://github.com/atdr/contrail/commit/372606f365d19fc3ad2f99cd047c5de617000fb7))
|
|
77
|
+
* resolve departure dates in the origin airport's timezone ([b9032c9](https://github.com/atdr/contrail/commit/b9032c9c0a3572154cb2ddc20427b6d07c01ff40))
|
|
78
|
+
* ship example config files ([17214b8](https://github.com/atdr/contrail/commit/17214b8019f2f47e6e7356951bae029ea62dbf18))
|
|
79
|
+
* store departure times and keep everything TIM returns ([4f8840a](https://github.com/atdr/contrail/commit/4f8840a596dd5f3b65240edda8fa224add24bf89))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
### Bug Fixes
|
|
83
|
+
|
|
84
|
+
* **emissions:** keep the TIM key out of URLs and error messages ([6e90e96](https://github.com/atdr/contrail/commit/6e90e968dee6a7b335ca2da6dea7ff655326025a))
|
|
85
|
+
* gitignore flight data backups and variants ([7b61808](https://github.com/atdr/contrail/commit/7b618087d6ecb473b3d998dfec7ebacc5f363ad5))
|
|
86
|
+
* harden re-sync against silent source failures and data loss ([25073ec](https://github.com/atdr/contrail/commit/25073ec4fad7473bed5e58ebb66596fcb67e4f30))
|
|
87
|
+
* **importers:** give UID-less events distinct dedup keys ([4b01c8c](https://github.com/atdr/contrail/commit/4b01c8c8bde1c1cf0353a60edb4586d9e92a5980))
|
|
88
|
+
* stop an upgrade downgrading every open exact figure ([2e618eb](https://github.com/atdr/contrail/commit/2e618eb8cd9094a0f48e8bf318aed2c512d75bc5))
|
|
89
|
+
* **storage:** count hand-edited rows and write the CSV atomically ([625c8a9](https://github.com/atdr/contrail/commit/625c8a9eefc8cab9bd2edd6225351a7e87e40f95))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
### Documentation
|
|
93
|
+
|
|
94
|
+
* add README ([f43641d](https://github.com/atdr/contrail/commit/f43641d7f8bdd766dd8398c6e1c148d40df7c8a8))
|
|
95
|
+
* add working notes, status and build plan ([84c42bb](https://github.com/atdr/contrail/commit/84c42bb3f2608c00def9c9b293c0b66493a6fd9d))
|
|
96
|
+
* document the repair workflow and departure-date caveat ([211e7a3](https://github.com/atdr/contrail/commit/211e7a3e263022fffd1c05a01f2582260b6dcb89))
|
|
97
|
+
* record findings from the first real-feed run ([28bebe0](https://github.com/atdr/contrail/commit/28bebe08435d66c21fcc853238a056d31ee921af))
|
|
98
|
+
* record the changed-itinerary and UTC-date open questions ([d01b09a](https://github.com/atdr/contrail/commit/d01b09ae59d939ab0fd270834a50a6c292d37752))
|
|
99
|
+
* record what a change here obliges in contrail-gh ([44eb9e1](https://github.com/atdr/contrail/commit/44eb9e14b09c329c1fbd270814af063c41e023ef))
|
|
100
|
+
* split working notes into AGENTS.md and docs/ ([cdb85f2](https://github.com/atdr/contrail/commit/cdb85f2d49569c503fe14095952111317a6da050))
|
|
101
|
+
* state what is verified, drop the archaeology ([e2b4d49](https://github.com/atdr/contrail/commit/e2b4d496b047773acb646552b570e688dbd46573))
|
|
102
|
+
* use a generic owner for instance repos ([cbfbffd](https://github.com/atdr/contrail/commit/cbfbffde322b37a6a89932c4c258af8e9db414f8))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
AGENTS.md
|
contrails-0.4.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andreas Richardson
|
|
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.
|