stage-cli 1.1.2__tar.gz → 1.1.3__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.
- {stage_cli-1.1.2 → stage_cli-1.1.3}/.gitignore +0 -2
- {stage_cli-1.1.2 → stage_cli-1.1.3}/CHANGELOG.md +15 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/PKG-INFO +8 -7
- {stage_cli-1.1.2 → stage_cli-1.1.3}/README.md +5 -5
- {stage_cli-1.1.2 → stage_cli-1.1.3}/SECURITY.md +1 -1
- {stage_cli-1.1.2 → stage_cli-1.1.3}/assets/ci-flow.svg +1 -1
- {stage_cli-1.1.2 → stage_cli-1.1.3}/pyproject.toml +2 -1
- stage_cli-1.1.3/src/stage/__init__.py +1 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/h.yaml +2 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/o.yaml +2 -3
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/__init__.py +2 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/health.py +20 -6
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/storage/sqlite_repo.py +5 -1
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_health.py +45 -1
- stage_cli-1.1.2/src/stage/__init__.py +0 -1
- {stage_cli-1.1.2 → stage_cli-1.1.3}/.gitattributes +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/.python-version +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/LICENSE +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/assets/cli.png +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/assets/tui.png +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/__main__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/banner.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/bootstrap/__init__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/bootstrap/openjobs.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/classify/__init__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/classify/eligibility.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/classify/internship.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/classify/role.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/classify/scope.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/__init__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/app.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/commands/__init__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/commands/discovery.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/commands/insight.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/commands/pipeline.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/commands/postings.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/commands/schedule.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/housekeeping.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/logfile.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/notify.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/options.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/render.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/runlock.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/schedule.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/schedule_state.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/selection.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/cli/serialize.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/companies.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/a.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/b.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/c.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/d.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/e.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/f.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/g.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/i.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/j.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/k.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/l.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/m.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/n.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/other.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/p.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/q.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/r.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/s.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/t.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/u.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/v.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/w.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/x.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/y.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/companies/z.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/fonts/DejaVuSans.LICENSE.txt +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/fonts/DejaVuSans.ttf +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/lexicon/company_tokens.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/lexicon/eligibility.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/lexicon/inclusive_suffixes.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/lexicon/internship.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/lexicon/language.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/lexicon/locations.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/lexicon/roles.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/lexicon/terms.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/lexicon/workday_facets.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/data/seed_companies.yaml +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/dedup/__init__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/dedup/identity.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/dedup/resolve.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/company.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/coverage.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/custom_board.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/discovery.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/enums.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/events.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/filters.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/ids.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/job.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/matching.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/priority.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/quarantine.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/rate_state.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/retention.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/rotation.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/signals.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/sync_run.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/text.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/validator.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/visits.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/domain/workday.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/http/__init__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/http/breaker.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/http/cache.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/http/client.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/http/profiles.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/lexicon.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/normalize/__init__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/normalize/language.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/normalize/location.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/normalize/terms.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/normalize/urls.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/paths.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/py.typed +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/services/__init__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/services/canary.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/services/coverage.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/services/discover.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/services/export.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/services/health.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/services/maintenance.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/services/quarantine.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/services/query.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/services/sync.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/__init__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/_text.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/ashby.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/bamboohr.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/base.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/breezy.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/collage.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/community_feeds.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/curated_markdown.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/custom_json.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/espresso.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/feed.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/greenhouse.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/jobbank.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/jobvite.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/lever.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/oracle_cloud.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/platforms.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/quebec_emploi.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/recruitee.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/simplify.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/smartrecruiters.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/speedyapply.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/themuse.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/workable.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/workday.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/sources/zshah.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/storage/__init__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/storage/migrations/0001_initial.sql +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/storage/migrations/__init__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/storage/repository.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/storage/search.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/storage/writer.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/tui/__init__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/tui/app.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/tui/help.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/tui/safe.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/tui/screens/__init__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/tui/screens/boards.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/tui/screens/postings.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/tui/screens/review.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/tui/screens/splash.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/tui/screens/stats.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/tui/screens/sync.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/tui/state.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/tui/theme.tcss +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/src/stage/tui/widgets/__init__.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/conftest.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/bilingual_titles.json +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/blackrock_search.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/croesus_career.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/disney_search.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/ea_searchjobs.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/espresso_search.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/gildan_openpositions.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/greenhouse_jobs.json +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/jobbank_search.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/jobvite_agscareer.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/jobvite_resolver.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/l3harris_search.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/lightspeed_openings.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/loblaw_jobs.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/nbc_search.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/parsons_search.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/publicissapient_sitemap.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/rentec_careers.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/sega_vacancies.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/sfrmk_search.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/shopify_internships.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/fixtures/twosigma_openroles.html +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/golden/classification.json +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_adapter_salvage.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_adopt_unregistered.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_board_identity.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_board_rest.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_canary.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_canary_events.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_classify.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_cli_help.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_cli_options.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_collage_adapter.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_companies_registry.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_company_fold_key.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_coverage.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_curated_markdown.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_custom_json.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_custom_json_buckets.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_custom_json_formats.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_custom_json_pagination.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_dedup.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_degree_and_disciplines.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_detail_queue.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_discover.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_eligibility.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_export.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_feed_adapters.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_feeds.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_fetch_authority.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_fold_inclusive.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_golden.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_greenhouse_adapter.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_housekeeping.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_html_boards.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_html_text.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_http_akamai.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_http_client.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_http_conditional.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_http_resilience.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_integrity_repair.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_jobvite_adapter.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_language.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_layering.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_lever_adapter.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_lexicon.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_location.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_malformed_input.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_migrations.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_new_adapters.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_notify.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_openjobs_crossref.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_oracle_cloud_adapter.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_paths.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_permissions.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_platform_shapes.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_profiles.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_quarantine.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_quarantine_relabel.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_quebec_emploi.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_rate_state.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_recall_limits.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_recheck.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_registry_shards.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_render.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_request_log.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_rescreen.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_review_contradictions.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_rotation.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_run_budget.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_schedule.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_schedule_state.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_schema_baseline.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_search.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_serialize.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_show.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_signals_persist.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_smartrecruiters_adapter.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_speedyapply.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_startup_budget.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_sync_e2e.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_sync_shape.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_sync_step2.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_terms.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_tombstones.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_tui.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_unregistered_quarantine.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_untrusted_display.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_update_badges.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_verify_registry.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_workday_adapter.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_workday_urls.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/tests/test_writer.py +0 -0
- {stage_cli-1.1.2 → stage_cli-1.1.3}/uv.lock +0 -0
|
@@ -4,6 +4,20 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
5
|
follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.1.3] - 2026-09-09
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `doctor` said greenhouse stored nothing on a run that stored 138. A source
|
|
12
|
+
left out of a run writes a row of zeroes, and the volume check read those as
|
|
13
|
+
real while discarding every run that deferred a board. Greenhouse defers on
|
|
14
|
+
all of them. A run counts now only if the source ran, and a deferred run is
|
|
15
|
+
measured against other deferred runs.
|
|
16
|
+
- Haize Labs and Odin Dynamics are off. Both boards 404 with nowhere to move to.
|
|
17
|
+
The two internships they published were already quarantined as non-CS.
|
|
18
|
+
- The PyPI page showed no author. A name and an email in one metadata field get
|
|
19
|
+
folded into a single header, leaving the author line blank.
|
|
20
|
+
|
|
7
21
|
## [1.1.2] - 2026-09-09
|
|
8
22
|
|
|
9
23
|
### Fixed
|
|
@@ -107,6 +121,7 @@ them in a SQLite database on your own machine.
|
|
|
107
121
|
|
|
108
122
|
Python 3.12, 3.13 or 3.14 on macOS, Linux or Windows.
|
|
109
123
|
|
|
124
|
+
[1.1.3]: https://github.com/NicholasXydis/Stage/releases/tag/v1.1.3
|
|
110
125
|
[1.1.2]: https://github.com/NicholasXydis/Stage/releases/tag/v1.1.2
|
|
111
126
|
[1.1.1]: https://github.com/NicholasXydis/Stage/releases/tag/v1.1.1
|
|
112
127
|
[1.1.0]: https://github.com/NicholasXydis/Stage/releases/tag/v1.1.0
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: stage-cli
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.3
|
|
4
4
|
Summary: Aggregates CS internship postings (Canada/US, EN + FR) into a local SQLite database
|
|
5
5
|
Project-URL: Homepage, https://github.com/NicholasXydis/Stage
|
|
6
6
|
Project-URL: Repository, https://github.com/NicholasXydis/Stage
|
|
7
7
|
Project-URL: Issues, https://github.com/NicholasXydis/Stage/issues
|
|
8
8
|
Project-URL: Changelog, https://github.com/NicholasXydis/Stage/blob/main/CHANGELOG.md
|
|
9
|
-
Author
|
|
9
|
+
Author: Nicholas Xydis
|
|
10
|
+
Maintainer-email: Nicholas Xydis <NicholasXydis@outlook.com>
|
|
10
11
|
License: MIT
|
|
11
12
|
License-File: LICENSE
|
|
12
13
|
Keywords: cli,co-op,internship,python,stage,student,tui
|
|
@@ -59,7 +60,7 @@ Free and open source. No account, no server, no telemetry.
|
|
|
59
60
|
|
|
60
61
|

|
|
61
62
|
|
|
62
|
-
  [](https://github.com/NicholasXydis/Stage/actions/workflows/ci.yml)
|
|
63
64
|
|
|
64
65
|
<br>
|
|
65
66
|
|
|
@@ -90,7 +91,7 @@ Internships disappear fast. By the time you find the right posting, hundreds of
|
|
|
90
91
|
|
|
91
92
|
Stage scans 1,450+ employer career boards and finds internships CS undergrads in Canada and the U.S. can actually apply to. Every rejected posting is logged with the exact reason it was filtered out.
|
|
92
93
|
|
|
93
|
-
It has kept **
|
|
94
|
+
It has kept **4,024** postings out of **188,284** screened so far.
|
|
94
95
|
|
|
95
96
|
## A look at it
|
|
96
97
|
|
|
@@ -272,7 +273,7 @@ Stage/
|
|
|
272
273
|
│ ├─ http/ rate posture, circuit breaker, validator cache
|
|
273
274
|
│ ├─ storage/ SQLite repository, migrations, FTS
|
|
274
275
|
│ └─ data/ packaged registry (a directory) and lexicons
|
|
275
|
-
├─ tests/ 2,
|
|
276
|
+
├─ tests/ 2,420 tests, 89% branch coverage
|
|
276
277
|
└─ .github/workflows/ CI, CodeQL, scheduled canary, release
|
|
277
278
|
```
|
|
278
279
|
|
|
@@ -299,7 +300,7 @@ Stage/
|
|
|
299
300
|
▼ ▼
|
|
300
301
|
┌─────────────┐ ┌─────────────┐
|
|
301
302
|
│ jobs │ │ quarantine │
|
|
302
|
-
│
|
|
303
|
+
│ 6,219 │ │ 182,065 │
|
|
303
304
|
└──────┬──────┘ └─────────────┘
|
|
304
305
|
│ dedup collapses the same posting across sources
|
|
305
306
|
▼
|
|
@@ -318,7 +319,7 @@ Stage/
|
|
|
318
319
|
|
|
319
320
|
| Workflow | File | Purpose |
|
|
320
321
|
| --- | --- | --- |
|
|
321
|
-
| CI | `.github/workflows/ci.yml` | Ruff, format, mypy strict, 2,
|
|
322
|
+
| CI | `.github/workflows/ci.yml` | Ruff, format, mypy strict, 2,420 tests, coverage floor, badge drift |
|
|
322
323
|
| Supported Pythons | `.github/workflows/ci.yml` | The full suite again on 3.13 and 3.14 |
|
|
323
324
|
| Wheel | `.github/workflows/ci.yml` | Builds and smoke-tests the wheel on Ubuntu, Windows and macOS |
|
|
324
325
|
| CodeQL | `.github/workflows/codeql.yml` | Static analysis for Python and the workflows themselves |
|
|
@@ -20,7 +20,7 @@ Free and open source. No account, no server, no telemetry.
|
|
|
20
20
|
|
|
21
21
|

|
|
22
22
|
|
|
23
|
-
  [](https://github.com/NicholasXydis/Stage/actions/workflows/ci.yml)
|
|
24
24
|
|
|
25
25
|
<br>
|
|
26
26
|
|
|
@@ -51,7 +51,7 @@ Internships disappear fast. By the time you find the right posting, hundreds of
|
|
|
51
51
|
|
|
52
52
|
Stage scans 1,450+ employer career boards and finds internships CS undergrads in Canada and the U.S. can actually apply to. Every rejected posting is logged with the exact reason it was filtered out.
|
|
53
53
|
|
|
54
|
-
It has kept **
|
|
54
|
+
It has kept **4,024** postings out of **188,284** screened so far.
|
|
55
55
|
|
|
56
56
|
## A look at it
|
|
57
57
|
|
|
@@ -233,7 +233,7 @@ Stage/
|
|
|
233
233
|
│ ├─ http/ rate posture, circuit breaker, validator cache
|
|
234
234
|
│ ├─ storage/ SQLite repository, migrations, FTS
|
|
235
235
|
│ └─ data/ packaged registry (a directory) and lexicons
|
|
236
|
-
├─ tests/ 2,
|
|
236
|
+
├─ tests/ 2,420 tests, 89% branch coverage
|
|
237
237
|
└─ .github/workflows/ CI, CodeQL, scheduled canary, release
|
|
238
238
|
```
|
|
239
239
|
|
|
@@ -260,7 +260,7 @@ Stage/
|
|
|
260
260
|
▼ ▼
|
|
261
261
|
┌─────────────┐ ┌─────────────┐
|
|
262
262
|
│ jobs │ │ quarantine │
|
|
263
|
-
│
|
|
263
|
+
│ 6,219 │ │ 182,065 │
|
|
264
264
|
└──────┬──────┘ └─────────────┘
|
|
265
265
|
│ dedup collapses the same posting across sources
|
|
266
266
|
▼
|
|
@@ -279,7 +279,7 @@ Stage/
|
|
|
279
279
|
|
|
280
280
|
| Workflow | File | Purpose |
|
|
281
281
|
| --- | --- | --- |
|
|
282
|
-
| CI | `.github/workflows/ci.yml` | Ruff, format, mypy strict, 2,
|
|
282
|
+
| CI | `.github/workflows/ci.yml` | Ruff, format, mypy strict, 2,420 tests, coverage floor, badge drift |
|
|
283
283
|
| Supported Pythons | `.github/workflows/ci.yml` | The full suite again on 3.13 and 3.14 |
|
|
284
284
|
| Wheel | `.github/workflows/ci.yml` | Builds and smoke-tests the wheel on Ubuntu, Windows and macOS |
|
|
285
285
|
| CodeQL | `.github/workflows/codeql.yml` | Static analysis for Python and the workflows themselves |
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
<rect x="240" y="42" width="180" height="58" rx="12" fill="#111C30" stroke="#3B82F6" stroke-width="2"/>
|
|
24
24
|
<text x="330" y="66" fill="#F0F6FC" font-size="14">ruff, format, mypy</text>
|
|
25
|
-
<text x="330" y="86" fill="#F0F6FC" font-size="14">2,
|
|
25
|
+
<text x="330" y="86" fill="#F0F6FC" font-size="14">2,420 tests, 89%</text>
|
|
26
26
|
|
|
27
27
|
<path d="M420 71H470" stroke="#3B82F6" stroke-width="3" marker-end="url(#arrow)"/>
|
|
28
28
|
|
|
@@ -5,7 +5,8 @@ description = "Aggregates CS internship postings (Canada/US, EN + FR) into a loc
|
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12,<3.15"
|
|
7
7
|
license = { text = "MIT" }
|
|
8
|
-
authors = [{ name = "Nicholas Xydis"
|
|
8
|
+
authors = [{ name = "Nicholas Xydis" }]
|
|
9
|
+
maintainers = [{ name = "Nicholas Xydis", email = "NicholasXydis@outlook.com" }]
|
|
9
10
|
keywords = [
|
|
10
11
|
"internship",
|
|
11
12
|
"stage",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.1.3"
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
slug: haizelabs
|
|
12
12
|
source_of_record: discover
|
|
13
13
|
last_verified: &id004 2026-08-14
|
|
14
|
+
enabled: false
|
|
15
|
+
notes: '2026-09-09: greenhouse/haizelabs 404s and their board page 500s; no ashby or lever board exists'
|
|
14
16
|
- name: Halcyon
|
|
15
17
|
platform: greenhouse
|
|
16
18
|
slug: halcyon
|
|
@@ -43,9 +43,8 @@
|
|
|
43
43
|
source_of_record: discover
|
|
44
44
|
last_verified: &id004 2026-08-14
|
|
45
45
|
name_gate_exempt: true
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
name gate cannot confirm it and provenance is the evidence (12 job(s))'
|
|
46
|
+
enabled: false
|
|
47
|
+
notes: '2026-09-09: ashby/odin-dynamics 404s while the ashby API is healthy; the employer removed the board'
|
|
49
48
|
- name: Off world studios
|
|
50
49
|
platform: bamboohr
|
|
51
50
|
slug: owi
|
|
@@ -65,6 +65,7 @@ from stage.domain.filters import DEFAULT_LIMIT, DEFAULT_WINDOW_DAYS, JobFilters
|
|
|
65
65
|
from stage.domain.health import (
|
|
66
66
|
MIN_VOLUME_HISTORY,
|
|
67
67
|
STALE_AFTER_DAYS,
|
|
68
|
+
UNRECORDED_FETCH,
|
|
68
69
|
UNRECORDED_VOLUME,
|
|
69
70
|
VOLUME_DROP_RATIO,
|
|
70
71
|
IntegrityFinding,
|
|
@@ -210,6 +211,7 @@ __all__ = [
|
|
|
210
211
|
"rotate",
|
|
211
212
|
"MIN_VOLUME_HISTORY",
|
|
212
213
|
"STALE_AFTER_DAYS",
|
|
214
|
+
"UNRECORDED_FETCH",
|
|
213
215
|
"UNRECORDED_VOLUME",
|
|
214
216
|
"VOLUME_DROP_RATIO",
|
|
215
217
|
"IntegrityFinding",
|
|
@@ -7,6 +7,7 @@ MIN_VOLUME_HISTORY = 3
|
|
|
7
7
|
VOLUME_DROP_RATIO = 0.5
|
|
8
8
|
STALE_AFTER_DAYS = 14
|
|
9
9
|
UNRECORDED_VOLUME = -1
|
|
10
|
+
UNRECORDED_FETCH = -1
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class VolumeVerdict(StrEnum):
|
|
@@ -27,10 +28,21 @@ class VolumePoint:
|
|
|
27
28
|
stored: int
|
|
28
29
|
deferred: int = 0
|
|
29
30
|
blocked: bool = False
|
|
31
|
+
fetched: int = UNRECORDED_FETCH
|
|
32
|
+
requests: int = UNRECORDED_FETCH
|
|
33
|
+
not_modified: int = UNRECORDED_FETCH
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def ran(self) -> bool:
|
|
37
|
+
return self.fetched != 0 or self.requests != 0 or self.not_modified != 0
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def rotated(self) -> bool:
|
|
41
|
+
return self.deferred > 0
|
|
30
42
|
|
|
31
43
|
@property
|
|
32
44
|
def is_evidence(self) -> bool:
|
|
33
|
-
return self.stored > UNRECORDED_VOLUME and self.
|
|
45
|
+
return self.stored > UNRECORDED_VOLUME and not self.blocked and self.ran
|
|
34
46
|
|
|
35
47
|
|
|
36
48
|
@dataclass(frozen=True, slots=True)
|
|
@@ -59,8 +71,9 @@ def assess_volume(source: str, history: list[VolumePoint]) -> VolumeSignal:
|
|
|
59
71
|
detail=_unproven_reason(history),
|
|
60
72
|
)
|
|
61
73
|
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
newest = evidence[0]
|
|
75
|
+
latest = newest.stored
|
|
76
|
+
prior = [point.stored for point in evidence[1:] if point.rotated == newest.rotated]
|
|
64
77
|
if len(prior) < MIN_VOLUME_HISTORY:
|
|
65
78
|
return VolumeSignal(
|
|
66
79
|
source=source,
|
|
@@ -114,10 +127,10 @@ def assess_volume(source: str, history: list[VolumePoint]) -> VolumeSignal:
|
|
|
114
127
|
def _unproven_reason(history: list[VolumePoint]) -> str:
|
|
115
128
|
if not history:
|
|
116
129
|
return "no runs on record"
|
|
117
|
-
if any(point.
|
|
130
|
+
if not any(point.ran for point in history):
|
|
131
|
+
return "no run fetched this source, so none of them can show a drop"
|
|
132
|
+
if all(point.blocked for point in history if point.ran):
|
|
118
133
|
return "every run was blocked, which is a throttle not a drop"
|
|
119
|
-
if any(point.deferred for point in history):
|
|
120
|
-
return "every run deferred members, so rotation explains it"
|
|
121
134
|
return "no run has recorded a stored count yet"
|
|
122
135
|
|
|
123
136
|
|
|
@@ -157,6 +170,7 @@ class IntegrityFinding:
|
|
|
157
170
|
__all__ = [
|
|
158
171
|
"MIN_VOLUME_HISTORY",
|
|
159
172
|
"STALE_AFTER_DAYS",
|
|
173
|
+
"UNRECORDED_FETCH",
|
|
160
174
|
"UNRECORDED_VOLUME",
|
|
161
175
|
"VOLUME_DROP_RATIO",
|
|
162
176
|
"IntegrityFinding",
|
|
@@ -1381,7 +1381,8 @@ class SqliteRepository:
|
|
|
1381
1381
|
|
|
1382
1382
|
def volume_history(self, limit: int) -> Mapping[str, list[VolumePoint]]:
|
|
1383
1383
|
rows = self._conn.execute(
|
|
1384
|
-
"SELECT source, stored, deferred, blocked
|
|
1384
|
+
"SELECT source, stored, deferred, blocked, fetched, requests, not_modified "
|
|
1385
|
+
"FROM sync_run_sources "
|
|
1385
1386
|
"WHERE run_id IN (SELECT id FROM sync_runs ORDER BY id DESC LIMIT ?) "
|
|
1386
1387
|
"ORDER BY run_id DESC",
|
|
1387
1388
|
(limit,),
|
|
@@ -1393,6 +1394,9 @@ class SqliteRepository:
|
|
|
1393
1394
|
stored=int(row["stored"]),
|
|
1394
1395
|
deferred=int(row["deferred"]),
|
|
1395
1396
|
blocked=bool(row["blocked"]),
|
|
1397
|
+
fetched=int(row["fetched"]),
|
|
1398
|
+
requests=int(row["requests"]),
|
|
1399
|
+
not_modified=int(row["not_modified"]),
|
|
1396
1400
|
)
|
|
1397
1401
|
)
|
|
1398
1402
|
return history
|
|
@@ -58,6 +58,50 @@ def test_a_blocked_run_cannot_raise_a_volume_alert() -> None:
|
|
|
58
58
|
assert signal.verdict is not VolumeVerdict.COLLAPSED, "a throttle is not drift"
|
|
59
59
|
|
|
60
60
|
|
|
61
|
+
def _idle(count: int) -> list[VolumePoint]:
|
|
62
|
+
return [VolumePoint(stored=0, fetched=0, requests=0, not_modified=0)] * count
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _ran(*stored: int) -> list[VolumePoint]:
|
|
66
|
+
return [VolumePoint(stored=value, fetched=value, requests=4) for value in stored]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_a_source_left_out_of_a_run_is_not_a_source_that_collapsed() -> None:
|
|
70
|
+
signal = assess_volume("greenhouse", _idle(1) + _ran(74, 74, 74, 74))
|
|
71
|
+
assert signal.verdict is not VolumeVerdict.COLLAPSED
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_a_source_left_out_of_a_run_cannot_drag_the_baseline_down() -> None:
|
|
75
|
+
signal = assess_volume("greenhouse", _ran(138) + _idle(3) + _ran(231, 223, 219))
|
|
76
|
+
assert signal.verdict is VolumeVerdict.HEALTHY
|
|
77
|
+
assert signal.samples == 3
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_a_source_that_never_ran_proves_nothing() -> None:
|
|
81
|
+
signal = assess_volume("ashby", _idle(5))
|
|
82
|
+
assert signal.verdict is VolumeVerdict.UNPROVEN
|
|
83
|
+
assert "fetched" in signal.detail
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_a_source_that_fetched_rows_and_stored_none_still_collapses() -> None:
|
|
87
|
+
live = VolumePoint(stored=0, fetched=44, requests=1)
|
|
88
|
+
signal = assess_volume("quebec-emploi", [live] + _ran(40, 41, 40))
|
|
89
|
+
assert signal.verdict is VolumeVerdict.COLLAPSED
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_a_source_blocked_on_every_run_is_a_throttle_not_a_drop() -> None:
|
|
93
|
+
history = [VolumePoint(stored=0, blocked=True, requests=4)] * 4
|
|
94
|
+
signal = assess_volume("workday", history)
|
|
95
|
+
assert signal.verdict is VolumeVerdict.UNPROVEN
|
|
96
|
+
assert "throttle" in signal.detail
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_a_run_that_was_all_cache_hits_still_counts_as_having_run() -> None:
|
|
100
|
+
cached = VolumePoint(stored=0, fetched=0, requests=0, not_modified=12)
|
|
101
|
+
signal = assess_volume("lever", [cached] + _ran(40, 41, 40))
|
|
102
|
+
assert signal.verdict is VolumeVerdict.COLLAPSED
|
|
103
|
+
|
|
104
|
+
|
|
61
105
|
def test_a_run_predating_the_stored_column_is_not_a_baseline_of_zero() -> None:
|
|
62
106
|
history = [VolumePoint(stored=12)] + _points(*([UNRECORDED_VOLUME] * 6))
|
|
63
107
|
signal = assess_volume("greenhouse", history)
|
|
@@ -143,7 +187,7 @@ async def test_doctor_names_the_source_whose_volume_collapsed(db_path: Path) ->
|
|
|
143
187
|
started_at=NOW,
|
|
144
188
|
finished_at=NOW,
|
|
145
189
|
outcome=SyncOutcome.SUCCESS,
|
|
146
|
-
sources=(SourceRunStats(source="lever", stored=stored),),
|
|
190
|
+
sources=(SourceRunStats(source="lever", stored=stored, requests=4),),
|
|
147
191
|
)
|
|
148
192
|
)
|
|
149
193
|
report = await doctor(repository, now=NOW)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.1.2"
|
|
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
|
|
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
|