Ask natural-language questions about Singapore Workplace Safety and Health PDFs, with citations.
Project description
wsh-rag
Ask natural-language questions about Singapore Workplace Safety and Health (WSH) PDFs — locally indexed, Claude-powered, with inline citations.
wsh-rag is a small command-line tool that points at a folder of PDFs — the WSH Act, MOM Codes of Practice, bizSAFE materials, your collected Risk Assessments — builds a local vector index, and answers natural-language questions about them with inline citations back to the source file and page.
It's built for safety officers, employers, and bizSAFE candidates who routinely need to look up obligations under Singapore's Workplace Safety and Health regime and want answers traced back to the underlying paragraph.
Install
pip install wsh-rag
# or, recommended for CLI tools:
pipx install wsh-rag
Requires Python 3.11+.
Quickstart
export ANTHROPIC_API_KEY=sk-ant-...
wsh-rag index ./my-wsh-pdfs
wsh-rag ask "What are an employer's duties under the WSH Act?" \
--folder ./my-wsh-pdfs
That's it. The first index run will download the embedding model (~130 MB, one-time). Subsequent indexes and asks are fast and run entirely on your machine — only the final answering step calls the Claude API.
What you'll see
╭──────────────────────── ANSWER ────────────────────────╮
│ Under the Workplace Safety and Health Act, every │
│ employer must take reasonably practicable measures to │
│ ensure the safety and health of employees at work [F1].│
│ This includes conducting risk assessments before any │
│ work activity that may pose a risk [F2][F3], and │
│ maintaining records of those assessments for at least │
│ three years [F2]. │
╰────────────────────────────────────────────────────────╯
┏━━━━━━━━━━━━━━━━ SOURCES ━━━━━━━━━━━━━━━━━━━━━┓
┃ Tag │ File │ Page ┃
┠───────┼─────────────────────────────┼────────┨
┃ F1 │ WSH_Act_2006.pdf │ 14 ┃
┃ F2 │ COP_Risk_Management.pdf │ 8 ┃
┃ F3 │ COP_Risk_Management.pdf │ 12 ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
Only chunks the model actually cited appear in the Sources table. The [F1] markers stay in the answer so you can trace each claim back to a row.
How it works
PDFs ─▶ pymupdf ─▶ page-bounded chunker ─▶ bge-small embeddings
│
▼
LanceDB (file-based)
│
question ─▶ query embedding ─▶ top-k retrieval ─────┘
│
▼
Claude Sonnet 4.6 (with prompt caching)
│
▼
cited answer + Sources table
Notable choices, briefly:
- Page-bounded chunks — each chunk has exactly one source page, so citations stay precise.
- Local embeddings —
BAAI/bge-small-en-v1.5runs on your machine, no embedding API cost. - Prompt caching — the system prompt and retrieved context are cached on the Anthropic side, so re-asking against the same context within ~5 minutes runs at a heavy discount.
- Sources show only what was cited — if the model invented an
[F9], the marker stays in the answer (so you can spot it) but the table only lists the real ones.
Configuration
| Env var | Purpose |
|---|---|
ANTHROPIC_API_KEY |
Required for wsh-rag ask. Get one at https://console.anthropic.com/. |
wsh-rag index <folder>
| Flag | Default | Notes |
|---|---|---|
--chunk-tokens |
800 | Tokens per chunk. |
--overlap |
100 | Token overlap between adjacent chunks (within a page). |
--embed-model |
BAAI/bge-small-en-v1.5 |
Any sentence-transformers model. Changing this requires re-indexing. |
wsh-rag ask "<question>"
| Flag | Default | Notes |
|---|---|---|
--folder / -f |
. |
Folder containing the .wsh-rag/ index. |
--top-k / -k |
5 | Chunks retrieved as context. |
--model |
claude-sonnet-4-6 |
Anthropic model id. |
--max-tokens |
1500 | Max tokens in the answer. |
--embed-model |
BAAI/bge-small-en-v1.5 |
Must match what you used at index time. |
Where the index lives
wsh-rag writes its index to <folder>/.wsh-rag/ — sibling to the PDFs you indexed. It's safe to delete (rm -rf .wsh-rag) and trivially re-buildable. Add this to your .gitignore:
.wsh-rag/
Singapore WSH context
This tool is built around the kinds of documents the Singapore Ministry of Manpower (MOM) and the Workplace Safety and Health Council publish:
- The Workplace Safety and Health Act (and its subsidiary regulations)
- Codes of Practice (e.g. CP on Risk Management, CP on Working Safely at Heights)
- bizSAFE training materials and audit checklists
- MOM advisories and circulars
- Your own Risk Assessments and Safe Work Procedures
The system prompt instructs Claude to refuse to speculate when the provided context doesn't cover a question — important because WSH guidance is precise and small wording differences matter legally.
Disclaimer:
wsh-ragis not a substitute for legal advice. Always cite the source PDF, not this tool's output, when reporting or making compliance decisions.
Limitations (v0.1.0)
- No incremental re-indexing —
wsh-rag indexalways does a full rebuild. - No interactive chat REPL — every
askis one-shot. - English text only.
- Scanned PDFs need OCR (not bundled). If
wsh-rag indexwarns that a PDF produced no extractable text, it's likely scanned. - One vector backend (LanceDB), one LLM provider (Anthropic Claude).
Roadmap
- Hash-based incremental re-indexing (only re-embed changed PDFs)
- Hybrid retrieval (BM25 + vector) for better recall on specific clause numbers
- Interactive
wsh-rag chatREPL with multi-turn context - Optional OCR via tesseract for scanned PDFs
Development
git clone https://github.com/LogiqlyAI/wsh-rag
cd wsh-rag
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
ruff check . && ruff format --check .
pytest
The integration test in tests/test_cli.py mocks the Anthropic client and uses a tiny reportlab-generated PDF, so the suite runs offline and without an API key.
License
MIT.
wsh-rag itself is MIT-licensed. Note that PyMuPDF, used for PDF parsing, is AGPL-licensed — wsh-rag only depends on it at runtime, so distributing wsh-rag under MIT is fine, but downstream users embedding wsh-rag should be aware of PyMuPDF's terms.
Acknowledgements
- Anthropic Claude for answer synthesis and prompt caching
- BAAI's
bge-small-en-v1.5for local retrieval embeddings - LanceDB for the file-based vector store
- PyMuPDF for fast PDF text extraction
- The Singapore Workplace Safety and Health Council and MOM for publishing the underlying guidance documents this tool is designed for
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wsh_rag-0.1.0.tar.gz.
File metadata
- Download URL: wsh_rag-0.1.0.tar.gz
- Upload date:
- Size: 19.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd25a5da7d6cc35b228e499ff5e54bccdf3f5867fbaf6f04d80051a4490a7f93
|
|
| MD5 |
4b76e667f838118d6d57bbe0f32c44dc
|
|
| BLAKE2b-256 |
a6ab81823469a5d6eb768a1c09e777fdba1b8f63acb9dc41f208d2f776cd4316
|
Provenance
The following attestation bundles were made for wsh_rag-0.1.0.tar.gz:
Publisher:
publish.yml on LogiqlyAI/wsh-rag
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wsh_rag-0.1.0.tar.gz -
Subject digest:
bd25a5da7d6cc35b228e499ff5e54bccdf3f5867fbaf6f04d80051a4490a7f93 - Sigstore transparency entry: 1384703284
- Sigstore integration time:
-
Permalink:
LogiqlyAI/wsh-rag@ee3a9f9bcf792a5ac351905b7cb140e5cc852e14 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/LogiqlyAI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ee3a9f9bcf792a5ac351905b7cb140e5cc852e14 -
Trigger Event:
push
-
Statement type:
File details
Details for the file wsh_rag-0.1.0-py3-none-any.whl.
File metadata
- Download URL: wsh_rag-0.1.0-py3-none-any.whl
- Upload date:
- Size: 18.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
711ec91a3798e4217dd75bd981a2e6c9e76d89281de9e93753d92ee46b849a7a
|
|
| MD5 |
aaf7eb3f286d913b093a6d9ca8674cef
|
|
| BLAKE2b-256 |
46c6608fe130ff2bb3de233b1897516c237f38a5ec8cb90e8df33de28a387ca9
|
Provenance
The following attestation bundles were made for wsh_rag-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on LogiqlyAI/wsh-rag
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wsh_rag-0.1.0-py3-none-any.whl -
Subject digest:
711ec91a3798e4217dd75bd981a2e6c9e76d89281de9e93753d92ee46b849a7a - Sigstore transparency entry: 1384703313
- Sigstore integration time:
-
Permalink:
LogiqlyAI/wsh-rag@ee3a9f9bcf792a5ac351905b7cb140e5cc852e14 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/LogiqlyAI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ee3a9f9bcf792a5ac351905b7cb140e5cc852e14 -
Trigger Event:
push
-
Statement type: