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. This hosted copy runs Analysis against a model on this machine only — your model, your hardware, nothing sent to this site and no API key to hand over.

What each tab needs

TabNeeds
Map, Table, Ground detail, LicenseNothing. They are already working.
AnalysisAn OpenAI-compatible server on this machine — LM Studio, Ollama, llama.cpp, vLLM
Dynamic SearchThe vector index plus scripts/serve.py

The quick way: point the browser at a model on this machine

The page calls the model directly from your browser, so the model has to be running on the same machine you are reading this on. Nothing you type here is sent to this site.

Open the Analysis tab

Click Analysis, then expand Model endpoint at the top.

Fill in three fields, then test

Base URL — a localhost or 127.0.0.1 address ending in /v1, for example http://localhost:1234/v1.
Model — the model id your server reports, for example qwen2.5-7b-instruct; LM Studio shows it beside the loaded model, and ollama list prints it.
API key — leave blank. Local servers rarely use one: LM Studio and Ollama ignore it, and vLLM only wants one if you started it with --api-key.

Then press Test connection. It sends a one-token request and says whether the endpoint answered, so a wrong port or a stopped server shows up here rather than halfway through an analysis. A ✓ appears beside Model endpoint once a call has gone through.

Have the decision PDF or its text ready. The page will ask you to drop it in or paste it — this copy carries no decision text of its own.

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. It asks for the decision text first, then streams the analysis into the Analysis tab.

Loopback only, on purpose. This build will not call a hosted provider such as OpenAI or Anthropic, and will tell you so if you try. Sending a provider key from a page you did not build means trusting that page with a credential that can spend your account — the key would sit in this browser's local storage, where any script on this origin could read it. A model on this machine needs no key, so the problem disappears rather than being managed. It will not reach another machine on your network either: that is a network hop to hardware this page cannot vouch for, and browsers block plain http to it from an https page regardless. To use either, take the server route below.

Reaching a local server from a hosted page is the fiddly part. CORS is only the first hurdle — llama.cpp and vLLM may need a flag to send the headers at all, while LM Studio and Ollama do by default. Even with CORS correct, Chrome requires a private-network preflight before a public page may touch a local address, and Safari may refuse the request outright. If Test connection reports it could not reach your server, that is usually why, and the fix is a browser flag, a tunnel, or the server route below — which sidesteps all of it by having the server make the call.

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

Anything else speaking /v1/chat/completions works too, as long as it answers on localhost or 127.0.0.1 on this machine. A model on another machine — a LAN box, a Tailscale address, a hosted provider — is reachable only through the server route below.

The other way: run it yourself, and use any endpoint you like

Run the project's own server and the local-only restriction no longer applies — the server calls the endpoint, so any OpenAI-compatible provider works, hosted or local, and your key lives in the server's 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. Build it once, into vector/chunks.jsonl and vector/embeddings.npy:

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. This copy on acqagent.ai also uses Google Analytics, which records visits, which tabs and protest grounds are opened, how many decisions are selected, and when an analysis starts. It never receives decision text, your question, your settings, or which decisions you open or pick.

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