• v1.1.0 b0230e6847

    bwilliams released this 2026-10-22 13:13:36 +01:00 | 0 commits to main since this release

    A general search replaces the web app's Title filter.

    Search

    • One search box for words, phrases and prompts. Words search the title
      and the description; title:, buyer:, buyer_id: and cpv: search
      only their own field. cpv: takes the start of a code (cpv:72) or
      words of a CPV description (cpv:food).
    • "quoted phrases", and ! in front of a term to leave out what it
      matches. Repeats of one prompt match any of them
      (buyer:leeds buyer:bradford); everything else must all match.
    • CPV terms are checked per code: cpv:72 !cpv:7221 needs a code starting
      72 that doesn't start 7221.
    • Words match the start of longer words and other forms of themselves
      ("migr" and "migrating" find "migration"), ignoring case and accents.
      The old filter matched any part of a word ("oad" found "road"); the
      search doesn't.
    • A "?" beside the box explains the syntax, and an unknown prompt is
      pointed out. A saved Title filter carries over as search words.
    • The search runs on the server: GET /api/opportunities/search returns
      the matching UUIDs (see /api/docs).

    Other changes

    • A CPV code is stored once, keeping its longest description: EU Supply
      cuts descriptions to 64 characters, which made a second copy of each
      code it sent.
    • job-search config init leaves out the test sources (fts_mock,
      sleeper) unless given --all.

    Upgrading

    Two new migrations (00010 and 00011): stop the service, migrate, then
    start it again.

    sudo -u job-search job-search --config /etc/job-search/config.yaml migrate up
    

    00011 rebuilds the search index; on a database of about 21,000
    opportunities it took under a second.

    Downloads
  • v1.0.0 71eca2f194

    bwilliams released this 2026-10-21 15:59:23 +01:00 | 4 commits to main since this release

    The first release of Job Search: it gathers UK public procurement
    opportunities from Find a Tender and EU Supply into one SQLite database,
    and serves them in a web app for working through them.

    Sources

    • Find a Tender, through its OCDS API. Each source carries on from the
      newest release it has stored, or from --since. A start more than 7 days
      ago is backfilled in 7-day windows, each saved before the next, and a
      failed fetch keeps what it got. Rate limits are waited out, up to
      rate_limit_retries times (default 5).
    • EU Supply's UK portal, scraped from its public pages, with its own
      rate limit (a random 2–10s between requests). A tender that repeats an
      opportunity from another source (title 90% alike, buyer or description
      80% alike, within 30 days) is stored hidden and skipped.
    • Skip rules: a JSON file of filters by buyer, title words or CPV
      codes that mark new opportunities Skipped as they arrive. It is reloaded
      when it changes, and job-search skip-rules apply applies it to those
      already stored.

    Web app

    • A sortable table of opportunities: published, contract start and end,
      closing date, value, buyer and more.
    • Filters by status, classification (a CPV tree), marks, SME suitability,
      title and "published in the last X days", remembered between visits.
    • Marks for each opportunity (hidden, skipped, interested, bid submitted,
      won, lost), and a "Hide all visible" button.
    • A details dialog with every release, and links to the notice on Find a
      Tender or the tender on EU Supply.
    • Live updates over a WebSocket: new and changed opportunities arrive
      without reloading, and a reconnect catches up on what changed.
    • Light and dark themes.

    Server

    • job-search serve runs the web app, the REST API (described in OpenAPI
      3.1 at /api/openapi.json, browsable at /api/docs) and every enabled
      source.
    • CSRF protection (job-search config generate-csrf-key keeps the key
      across restarts), rate limits, security headers, optional TLS and proxy
      protocol, and an access log.

    Installing

    The release tarball holds job-search, a systemd unit
    (job-search.service) and a logrotate configuration
    (job-search.logrotate). The unit's comments give the install steps: a
    job-search system user, /etc/job-search/config.yaml, data in
    /var/lib/job-search, and logs in the journal or, with logging.file, in
    /var/log/job-search.

    Create and migrate the database before the first start, and migrate after
    each upgrade:

    sudo -u job-search job-search --config /etc/job-search/config.yaml migrate up --create
    

    job-search config init writes a config file to start from; see
    internal/config/README.md for every setting.

    Known limitations

    • Find a Tender pages its results newest first. If a backfill is stopped
      partway through a window, a restart without --since skips the rest of
      that window: restart with --since set to the window's start (logged as
      backfilling).
    • EU Supply's pages give no contract period, so its opportunities have a
      closing date but no start or end.
    • An EU Supply tender bought by an agent, with no description, isn't
      recognised as a repeat, and one buyer's name inside another's counts as
      the same buyer.
    Downloads