Skip to content

Repository files navigation

Thai Public Procurement Intelligence

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.

Portfolio review path

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:

  1. Home: confirm English/Thai UI, loaded records, budget metrics, and top projects.
  2. Search: filter records, switch keyword/semantic/hybrid modes, and open record details.
  3. Dashboard: inspect province, category, monthly, agency, and top-project aggregates.
  4. Assistant: ask a procurement question and verify cited evidence.
  5. 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.

Screenshots

Official snapshot home

Official record provenance

Official data quality status

Additional search, dashboard, assistant-citation, methodology, and Thai mobile evidence is under docs/screenshots/.

Features

  • 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.

Architecture

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"]
Loading

Local setup

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 dev

Backend:

cd apps/api
uv sync

Run 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 8000

Seed 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 1000

Frontend API configuration:

cd apps/web
$env:NEXT_PUBLIC_API_BASE_URL="http://localhost:8000/api"
npm run dev

Local endpoints:

Docker Compose

docker compose up --build

Then seed data:

docker compose exec api uv run python -m app.jobs.import_csv --file /data/sample/procurement_sample.csv --source sample

Environment variables

Backend:

  • DATABASE_URL
  • ENABLE_EMBEDDINGS
  • CORS_ORIGINS
  • DATASET_MODE=synthetic|official_snapshot
  • ADMIN_INGESTION_TOKEN
  • OFFICIAL_SNAPSHOT_METADATA
  • OFFICIAL_QUALITY_REPORT

Frontend:

  • NEXT_PUBLIC_API_BASE_URL
  • NEXT_PUBLIC_SITE_URL
  • NEXT_PUBLIC_DEMO_MODE

Deterministic answer design

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.

Official bounded snapshot

  • 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.

Measured evidence

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.

Official snapshot local mode

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 8000

Synthetic mode remains DATASET_MODE=synthetic; use a separate database when switching modes for the clearest local review.

Tests

cd apps/api
uv run pytest

cd ../..
npm run web:test
npm run web:lint
npm run web:build

GitHub Actions runs API tests, migration checks, official-snapshot evaluation, repository guardrails, web tests, lint, and production build on every push and pull request.

Optional deployment

The hosted deployment is optional. The local review path remains the canonical zero-key path. See docs/deployment.md and docs/security.md.

Known limitations

  • 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.

Portfolio bullet

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.

About

Thai procurement intelligence with official/local snapshot evidence and a synthetic hosted demo.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages