Evidence-based search and analytics for a bounded Thai public procurement snapshot, with a separate deterministic synthetic demo.
This portfolio project demonstrates CSV ingestion, normalization, search/filtering, analytics, deterministic evidence summaries, semantic-style retrieval, and source-cited Q&A.
The primary review path is local, deterministic, and zero-cost. It requires no inference account or API key. Follow docs/local_review.md.
Hosted demo: https://thai-procurement-intelligence.vercel.app
After local startup, review these areas:
- Home: confirm English/Thai UI, loaded records, budget metrics, and top projects.
- Search: filter records, switch keyword/semantic/hybrid modes, and open record details.
- Dashboard: inspect province, category, monthly, agency, and top-project aggregates.
- Assistant: ask a procurement question and verify cited evidence.
- Data Status: confirm readiness, ingestion state, source identity, and record count.
Dataset boundary: synthetic remains the default demo mode. Local official_snapshot mode uses a separately ingested 250-record DGA/data.go.th snapshot retrieved on 2026-06-21. The modes are never aggregated, and the snapshot is not complete, representative, or real-time.
Additional search, dashboard, assistant-citation, methodology, and Thai mobile evidence is under docs/screenshots/.
- Next.js TypeScript frontend with search, record detail, dashboard, assistant, data status, and methodology views.
- English/Thai UI switch using
?lang=en|th. - FastAPI backend with health, records, analytics, ingestion, summary, assistant, semantic search, similar-record, and CSV-export endpoints.
- SQLAlchemy schema for procurement records, ingestion runs/errors, summaries, embeddings, and Q&A logs.
- CSV ingestion with validation, normalization, deduplication, and import counters.
- 120 deterministic synthetic records in
data/sample/procurement_sample.csv. - Approved 250-record bounded public snapshot with checksum, mapping, quality reports, record-level provenance, and idempotent import.
- Visible bilingual dataset identity, source attribution, freshness, and data-quality status.
- Deterministic evidence summaries and cited answers that require no network inference.
- Local deterministic embeddings for semantic/hybrid retrieval demos.
- Docker Compose with PostgreSQL JSON vector storage, API, and web services; similarity runs in application code.
flowchart LR
CSV["CSV / public source"] --> Import["FastAPI ingestion"]
Import --> DB[("PostgreSQL JSON vector storage")]
DB --> API["FastAPI REST API"]
API --> Web["Next.js frontend"]
API --> Answer["Deterministic evidence summaries"]
API --> Search["Keyword + semantic retrieval"]
Answer --> Evidence["Citations and retrieved records"]
Full Windows PowerShell steps, smoke checks, expected results, and troubleshooting: docs/local_review.md.
Prerequisites:
- Node.js 24+
uv- Docker Desktop for the PostgreSQL path
Frontend:
cd apps/web
npm install
npm run devBackend:
cd apps/api
uv syncRun PostgreSQL and API:
docker compose up db
cd apps/api
$env:DATABASE_URL="postgresql+psycopg://postgres:postgres@localhost:5432/thai_procurement"
uv run alembic upgrade head
uv run uvicorn app.main:app --reload --port 8000Seed deterministic sample records:
cd apps/api
uv run python -m app.jobs.import_csv --file ../../data/sample/procurement_sample.csv --source sample
uv run python -m app.jobs.generate_embeddings --limit 1000Frontend API configuration:
cd apps/web
$env:NEXT_PUBLIC_API_BASE_URL="http://localhost:8000/api"
npm run devLocal endpoints:
- Web app: http://localhost:3000
- API docs: http://localhost:8000/api/docs
docker compose up --buildThen seed data:
docker compose exec api uv run python -m app.jobs.import_csv --file /data/sample/procurement_sample.csv --source sampleBackend:
DATABASE_URLENABLE_EMBEDDINGSCORS_ORIGINSDATASET_MODE=synthetic|official_snapshotADMIN_INGESTION_TOKENOFFICIAL_SNAPSHOT_METADATAOFFICIAL_QUALITY_REPORT
Frontend:
NEXT_PUBLIC_API_BASE_URLNEXT_PUBLIC_SITE_URLNEXT_PUBLIC_DEMO_MODE
Search, dashboard, details, export, ingestion, summaries, and evidence retrieval all work without external inference. Summary output is cached in ai_summaries; assistant answers are generated from retrieved records and return citations alongside the answer. The public answer path is designed for reproducible portfolio review rather than unconstrained generation.
- Publisher: Digital Government Development Agency (Public Organization), with source-data cooperation stated by the portal.
- Dataset: fiscal-year 2568 EGP contract data.
- Retrieved: 2026-06-21T14:02:45.343910Z.
- Coverage in this subset: 2024-10-04 through 2025-09-29.
- Records: 250 unique source project IDs.
- License label:
Creative Commons Attributions(the portal does not supply a version or URL). - SHA-256:
413f70c0ef17c17233b99aa42a7f1e25284644948c37bd109c21e9cc0678618b.
Source governance, mapping, acquisition, and limitations: source review, mapping, snapshot, provenance, and limitations.
The deterministic evaluation on 2026-06-22 measured 250/250 valid rows, zero rejected/duplicate/warning rows, an idempotent second import with 250 unchanged rows, keyword precision@5 of 1.0, hybrid precision@5 of 0.5, citation/source-link completeness of 1.0, and unsupported-claim rate of 0.0 across four labeled queries. These are bounded-fixture results, not production-scale claims. See quality and evaluation.
cd apps/api
$env:DATASET_MODE="official_snapshot"
uv run alembic upgrade head
uv run python -m app.jobs.import_official_snapshot --file ../../data/official/raw/dga-egp-contract-2568-250.csv --metadata ../../data/official/metadata/dga-egp-contract-2568-250.json
uv run uvicorn app.main:app --reload --port 8000Synthetic mode remains DATASET_MODE=synthetic; use a separate database when switching modes for the clearest local review.
cd apps/api
uv run pytest
cd ../..
npm run web:test
npm run web:lint
npm run web:buildGitHub Actions runs API tests, migration checks, official-snapshot evaluation, repository guardrails, web tests, lint, and production build on every push and pull request.
The hosted deployment is optional. The local review path remains the canonical zero-key path. See docs/deployment.md and docs/security.md.
- Excel ingestion is an extension point, not implemented in the MVP.
- Deterministic local embeddings are a no-cost semantic demo, not production-grade embeddings.
- The official fixture is a small non-random subset from one source resource part.
- The portal's attribution license label does not specify a version.
- Public ingestion is disabled unless a server-side admin token is explicitly configured.
- The hosted deployment remains synthetic until separately migrated and verified.
- Public data is not proof of fraud, corruption, misconduct, or suspicious behavior.
Built a zero-cost Next.js/FastAPI procurement intelligence case study with a checksummed 250-record official DGA snapshot, versioned mapping, provenance-aware PostgreSQL ingestion, deterministic quality/retrieval evaluation, bilingual evidence UI, and source-cited answers while preserving an isolated synthetic demo.


