GAO Bid Protest Nexus v2.1

GAO Bid Protest Nexus · Copyright 2026 Tommy Kim (AcqAgent)
Code Apache 2.0 · data CC BY 4.0
Corpus by Kevin Misener (MIT) · decisions public domain (GAO)
Coding is AI-derived and unverified. Not legal advice — check anything you rely on against the original decision.
Full license and credits →
About
Eight filters — Grounds, decade, outcome, procurement authority, protest posture, PDF source, text source and digest — drive all three views. Map: click any node to see its connections and the decisions behind it. Table: B-number, short description and PDF links. Dynamic Search: full-text search over the decision corpus. Analysis: pick decisions anywhere in the dashboard, then summarize them one by one or compare them as a set.
B-numberShort description YearCase GroundOutcomeAuthorityPostureLinks
Dynamic Search — full-text search across all 33,136 GAO decisions. Returns the passages that match your wording, ranked, each linked to its source decision. Decade, outcome, PDF source and text status narrow the search to a slice of the corpus. Expand query (off by default) rewrites the question into a few legal-terminology variants with the local model and fuses their ranked results — best for "when will GAO…" style questions; leave it off for exact-phrase lookups (fusion dilutes rare phrases).

Ground detail

How often this ground is sustained, against the corpus base rate, with the 95% confidence interval its sample size supports. Computed over all 33,136 decisions — 27,232 of whose grounds are model-predicted (marked † elsewhere) — so small n's now appear on grounds that were rare in the hand-labeled subset. The side-panel filters do not apply here. Most of the apparent spread between grounds is sampling noise: the confidence band shows which differences are real.

Decision analysis

Key-element summaries of GAO bid protest decisions, written by an OpenAI-compatible model from the decision text. Pick decisions anywhere in the dashboard — the Map popups, the Table, or Dynamic Search results — then read them one at a time or compare the set. Each analysis opens with the nearest related decisions from the corpus index (vector retrieval) — click one to analyze it too. Output lands here, never in the view you picked from.

Model endpoint

Connect a model endpoint

Map, Table and Ground detail work with nothing attached. Analysis needs a model; Dynamic Search needs the search index. Both are optional, and both are yours to point wherever you like — including at a model running on your own machine.

What each tab needs

TabNeeds
Map, Table, Ground detail, LicenseNothing. They are already working.
AnalysisAny OpenAI-compatible endpoint — hosted or local
Dynamic SearchThe vector index plus scripts/serve.py

The quick way: point the browser at an endpoint

Nothing to install. The page calls the endpoint directly from your browser.

Open the Analysis tab

Click Analysis, then expand Model endpoint at the top.

Fill in three fields

Base URL — ends in /v1, for example https://api.openai.com/v1 or http://localhost:1234/v1.
Model — the name your endpoint answers to.
API key — leave empty for most local servers.

Pick a decision and run it

Use Select on any decision in the Table or a Map popup, then ✦ Run analysis. Choose Compare as a set for two or more.

Give it the decision text

Without the local server there is no decision text on this page, so it will ask you to drop the PDF in or paste the text. That is expected.

Two things to know. Your key is kept in this browser's local storage and is never sent anywhere but the endpoint you name. And the endpoint has to allow browser requests (CORS) — most hosted providers and local servers do; if one does not, use the server route below.

Endpoints that work

Running locallyBase URL
LM Studiohttp://localhost:1234/v1
Ollamahttp://localhost:11434/v1
llama.cpp serverhttp://localhost:8080/v1
vLLMhttp://localhost:8000/v1

Hosted providers work the same way — OpenAI, OpenRouter, Together, Groq, Fireworks, LiteLLM and anything else speaking /v1/chat/completions. Only the base URL, the model name and the key change.

The tidier way: let the server hold the key

Run the project's own server and your key lives in its environment instead of in a browser. The server also reads the decision text for you, so you are not asked for PDFs, and it is the only way to turn on Dynamic Search.

git clone https://github.com/acqagent/GAO-Bid-Protest-Nexus.git
cd GAO-Bid-Protest-Nexus
python -m venv .venv && .venv/bin/pip install -r requirements.txt

export OPENAI_BASE_URL=http://localhost:1234/v1   # or any compatible endpoint
export OPENAI_MODEL=your-model
export OPENAI_API_KEY=sk-...                      # omit for a local server

.venv/bin/python scripts/serve.py
# then open http://127.0.0.1:8765/

The dashboard notices the server on its own and routes through it. The Model endpoint panel will say key on the server when it has one.

Turning on Dynamic Search

Full-text search runs over a vector index of the decision text, which is far too large to ship inside this page. Either drop vector/chunks.jsonl and vector/embeddings.npy from the full bundle into vector/, or build the index yourself:

python3 scripts/vectorize.py --links pdf-links.csv \
        --embedder api --embed-base-url http://localhost:8080/v1 \
        --embed-model your-embedding-model

Use a model trained for retrieval — Qwen3-Embedding, BAAI/bge-* or intfloat/e5-*. A general chat model will produce vectors, but not ones that put a question near the passage answering it.

No browser at all

Every analysis the dashboard can run is also a command:

python3 scripts/analyze.py B-417327 --out reports/
python3 scripts/analyze.py --filter ground=oci,disposition=sustained \
        --limit 8 --compare --out reports/

When it does not work

What you seeUsually means
"could not reach … CORS"The endpoint refuses browser requests. Use the server route.
401 or 403Missing or wrong API key.
404 on the modelModel name does not match what the endpoint serves.
"could not get the decision text"No local corpus and no PDF reachable — drop the PDF in or paste the text.
Search says it is unavailableThe vector index is not loaded. See above.
What leaves your machine. The decision text and your question go to whichever endpoint you configure, and nowhere else. The decisions are public record, but check that against your own rules — and note that a local endpoint keeps everything in-house.

License and credits

Use it, change it, build on it, at work or commercially. Just keep the credit. Two licenses apply: one for the code, one for the data.

WhatLicense
Dashboard code, search backend, scripts Apache 2.0
Protest taxonomy, per-decision coding, decision metadata, vector index CC BY 4.0
The upstream decision corpus (Kevin Misener) MIT / CC BY, per source
The GAO decisions themselves Public domain (17 U.S.C. 105)

This page embeds the decision data inside the application code. In this file the code is Apache 2.0 and the embedded data is CC BY 4.0.

How to attribute

This citation satisfies the attribution requirement:

GAO Bid Protest Nexus, by Tommy Kim (AcqAgent, https://acqagent.ai), licensed under CC BY 4.0. Built on the GAO bid protest corpora by Kevin Misener (https://huggingface.co/datasets/Kmisener/GAO-Bid-Protest, MIT; https://github.com/kmisener90/GAO-Bid-Protest-Dataset, CC BY). Underlying decisions are works of the U.S. Government Accountability Office and are in the public domain.

If you modified the data, say so — for example, “Coding modified from the original.”

Credits

The Nexus is built on someone else’s work, and the corpus came first.

  • The decision corpus came from Kevin Misener, in two collections: GAO-Bid-Protest on Hugging Face — the larger corpus, under the MIT License — and the earlier GAO-Bid-Protest-Dataset of roughly 5,700 decisions, under a Creative Commons Attribution license. Both are used the same way here: the PDF URLs, the 33-ground taxonomy and the per-decision coding were derived from them using AI. If you reuse the corpus, credit that source too.
  • The decisions themselves are works of the U.S. federal government and are not protected by U.S. copyright (17 U.S.C. 105), so they are in the public domain. Cite GAO as the source.

What this data can and cannot tell you

The corpus is broad but uneven, and the gaps are not random. Read the numbers with these in mind.

    None of this makes the corpus unusable — it makes it a starting point. Every decision links back to its page on gao.gov, and that is the authority. Use this to find decisions and see shape; use the decision itself to be sure.

    Accuracy

    The coding was derived using AI and has not been verified decision by decision. This is not legal advice. Verify anything you rely on against the original GAO decision. Found a miscoded decision, or want a ground split differently? Open an issue.

    Name and marks

    The code and data are yours to reuse. The AcqAgent name and logo are not. Say your work is based on the GAO Bid Protest Nexus, but do not present a fork as an official AcqAgent release.

    Earlier releases

    Releases before v1.0 were published under CC BY 4.0 in full, including the code. Those releases stay available under that license. From v1.0 forward, code is Apache 2.0 and data is CC BY 4.0.

    Not affiliated with GAO

    AcqAgent is an independent, open source project. It is not affiliated with, sponsored by, or endorsed by the U.S. Government Accountability Office or any other federal agency. Content is provided for informational purposes only and is not legal, financial, or contracting advice.

    Full terms: Apache 2.0 · CC BY 4.0