sdsforge 0.5.0

CLI and desktop app for generating, converting, validating, and rendering chemical safety data sheets in MHLW/JIS Z 7253 structured formats.
Documentation

sdsforge

GUI + CLI tool for bidirectional conversion between Safety Data Sheet (SDS) documents (Word/PDF) and the Japanese Ministry of Health, Labour and Welfare (MHLW) standard JSON format.

Supports Japanese, English, Simplified Chinese, and Traditional Chinese.

日本語 | 中文

Embedding in your Rust project? Use sdsforge-core directly.

Migrating from sdsconv? See docs/migration-from-sdsconv.md. The old sdsconv binary still works during the deprecation window — it forwards to sdsforge with a warning.


Download

Platform Download
macOS (Universal — Apple Silicon + Intel) sdsforge-macos.zip
Windows (Portable .exe — no install required) sdsforge-windows-portable.zip
Rust / CLI cargo install sdsforge

All releases & changelogs


GUI Mode

Launch the graphical interface by running sdsforge without any arguments:

sdsforge

The GUI window (820×640) opens with five tabs:

Tab Function
Convert SDS document (PDF/DOCX/XLSX/HTML/URL) → MHLW standard JSON
Render Document MHLW JSON → DOCX / HTML / PDF (with optional DOCX template)
Validate Structural validation of MHLW JSON with colored OK/Warning/Error results
Extract Text Raw text extraction from documents — no LLM API required
Settings API key, model name, base URL, quality, language, UI language
Convert tab Render tab Extract Text tab
Convert tab Render tab Extract Text tab

Drag & drop files onto any tab to fill the input field automatically.

Settings are saved to your OS config directory under sdsforge/config.toml (~/Library/Application Support/sdsforge/config.toml on macOS, ~/.config/sdsforge/config.toml on Linux), migrated automatically from the pre-rename sdsconv/config.toml location the first time you launch this version if the new file doesn't exist yet — your saved API key and settings carry over, and the old file is left untouched. The GUI and CLI share the same conversion engine (tasks.rs), so results are identical.


Commands

to-json — Convert PDF/Word → MHLW standard JSON

# Single file (Anthropic Claude, default)
export ANTHROPIC_API_KEY=sk-ant-...
sdsforge to-json --input input.pdf --output output.json

# Specify source language
sdsforge to-json --input sds_en.pdf --output output.json --lang en

# Batch mode — process a whole directory
sdsforge to-json --input-dir ./pdfs/ --output-dir ./json/ --lang ja

# OpenAI GPT (defaults to gpt-4o-mini)
sdsforge to-json --input input.pdf --output output.json \
  --provider openai --api-key $OPENAI_API_KEY

# Google Gemini (defaults to gemini-2.0-flash)
sdsforge to-json --input input.pdf --output output.json \
  --provider gemini --api-key $GEMINI_API_KEY

# Local LLM via Ollama (any OpenAI-compatible endpoint)
sdsforge to-json --input input.pdf --output output.json \
  --provider local --base-url http://localhost:11434/v1 \
  --model llama3.2 --api-key dummy

# From pre-extracted text (skip PDF parsing)
sdsforge to-json --input extracted.txt --output output.json --lang ja
Flag Default Description
--input Input PDF, DOCX, XLSX, or TXT file
--input-dir Input directory (batch — processes all .pdf/.docx/.xlsx/.xls)
--output Output JSON file
--output-dir Output directory (batch — created if absent)
--provider anthropic LLM provider: anthropic, openai, gemini, mistral, groq, cohere, local
--api-key env var API key (see provider defaults below)
--model per-provider Model name override
--base-url Custom OpenAI-compatible endpoint (for --provider local)
--lang auto-detect Source document language: ja, en, zh-cn, zh-tw
--quality medium Preset: low (fast/cheap), medium, high (accurate)
--concurrency 4 Max parallel files in batch mode
--suggested-name Rename output to SDS_<IssueDate>_<ProductCode>.json (MHLW §2.1.2 recommended convention)

Provider defaults:

--provider Default model Environment variable
anthropic claude-haiku-4-5-20251001 (low/medium) · claude-sonnet-4-6 (high) ANTHROPIC_API_KEY
openai gpt-4o-mini OPENAI_API_KEY
gemini gemini-2.0-flash GEMINI_API_KEY
mistral mistral-small-latest MISTRAL_API_KEY
groq llama-3.3-70b-versatile GROQ_API_KEY
cohere command-r-plus COHERE_API_KEY
local llama3 LOCAL_LLM_API_KEY (optional; defaults to ollama)

render — Convert MHLW standard JSON → Word / HTML / PDF

# Single file (built-in layout)
sdsforge render --input output.json --to docx --output result.docx --lang ja

# Batch mode (built-in layout)
sdsforge render --input-dir ./json/ --output-dir ./docx/ --to docx --lang en

# HTML or PDF instead of DOCX
sdsforge render --input output.json --to html --output result.html --lang ja
sdsforge render --input output.json --to pdf  --output result.pdf  --lang ja

# Fill a Word template with {{Placeholder}} substitution
sdsforge render --input output.json --to docx --output result.docx \
  --template my_template.docx

# Batch mode with template
sdsforge render --input-dir ./json/ --output-dir ./docx/ --to docx \
  --template my_template.docx

to-docx, to-html, and to-pdf still work as deprecated aliases for render --to docx|html|pdf (same implementation, a deprecation warning is printed to stderr) — switch to render at your own pace.

Word template format

Prepare a .docx file with {{FieldName}} placeholders where FieldName is a leaf key from the MHLW JSON schema. The full dot-path is also accepted for disambiguation.

{{TradeNameJP}}          → 製品和名
{{CompanyName}}          → 会社名
{{Phone}}                → 電話番号
{{IssueDate}}            → 発行日
{{Identification.SupplierInformation.CompanyName}}  → フルパス指定

Placeholders can appear anywhere in the document — paragraphs, table cells, headers, and footers. Word sometimes splits typed text across internal runs; the tool automatically merges such splits before substitution.

Flag Default Description
--input Input JSON file
--input-dir Input directory (batch — processes all .json)
--output Output file
--output-dir Output directory (batch)
--to Output format: docx, html, or pdf
--lang ja Output language: ja, en, zh-cn, zh-tw (without --template)
--template Word template with {{FieldName}} placeholders (--to docx only)

extract-text — Extract raw text from PDF/DOCX

Extracts the text that the LLM would receive, without making any API call. Useful for inspecting extraction quality or running the LLM step separately.

# Save to file
sdsforge extract-text --input input.pdf --output extracted.txt

# Print to stdout
sdsforge extract-text --input input.pdf

# Then feed back into to-json
sdsforge to-json --input extracted.txt --output output.json --lang ja

validate — Check a JSON file for structural issues

# Human-readable output (exits 0 = OK, 1 = warnings found)
sdsforge validate --input output.json

# JSON array output for CI/scripting
sdsforge validate --input output.json --json

Checks that key sections (Identification, HazardIdentification, ToxicologicalInformation, etc.) are populated. Exits with code 1 if any issues are found.


Language Support

Language --lang Source documents Output DOCX headings
Japanese ja JIS Z 7253 compliant SDS JIS Z 7253
English en GHS/OSHA HazCom format GHS Rev.10 / ISO 11014
Simplified Chinese zh-cn GB/T 16483 format GB/T 16483-2012
Traditional Chinese zh-tw CNS 15030 format CNS 15030

Requirements

  • Rust 1.75+
  • An LLM API key (for to-json only) — set the provider's environment variable or pass --api-key
    • Anthropic: ANTHROPIC_API_KEY
    • OpenAI: OPENAI_API_KEY
    • Google Gemini: GEMINI_API_KEY
    • Mistral: MISTRAL_API_KEY
    • Groq: GROQ_API_KEY
    • Cohere: COHERE_API_KEY
    • Local LLM (Ollama etc.): use --provider local --base-url <url> (no API key required)
  • Input files must be text-based PDF or DOCX
    • Encrypted PDFs are not supported
    • CID font / Shift-JIS encoded PDFs (common in Japanese documents): handled by pdftotext (poppler) fallback
    • Scanned/image-only PDFs: automatically retried via pdftoppm + tesseract OCR (if installed), or via Claude Vision API (when using --provider anthropic)
    • Full 3-tier PDF fallback: pdf-extract -> pdftotext -> OCR/Vision

Rust Library

The conversion engine is available as a standalone library:

Crate crates.io Description
sdsforge-core sdsforge-core LLM-based extraction, DOCX/HTML generation, MHLW schema
[dependencies]
sdsforge-core = "0.4"

Changelog

Completed in 0.3.6 / 0.2.6

  • QC r24: 5 new rule-based checks (S1-ZH-NO-EMERGENCY, S7-FLAMMABLE-STORAGE-TEMP, S8-NO-ENG-CONTROLS, S10-NO-INCOMPATIBLE, CROSS-STALE-DATE)
  • QC r24: S8-OEL-NO-NUMERIC false-positive fixes — Chinese unit-before-value format, additional "no OEL" exemption phrases
  • QC r24: S5-EMPTY threshold 30→15 chars (reduces false positives for brief Chinese firefighting sections)
  • Round-trip test: JSONL parsing fix, validator string-array handling; r24 baseline 30/30 success, CRIT=0, HIGH=9, MED=176
  • QC r25: fix S2-EXPLOSIVE-NO-GHS01 / S2-ENV-NO-GHS09 false-negatives (substring "01"/"09" in dates/H-codes); new S3-NAME-IS-CAS (HIGH) and S16-REVISION-BEFORE-ISSUE (HIGH)
  • Round-trip test r25 baseline: 30/30 success, CRIT=0, HIGH=13, MED=175
  • QC r26: S2-FLAMMABLE-NO-GHS02, S2-CORROSIVE-NO-GHS05, S2-ACUTETOX-NO-GHS06 (all MED) — pictogram/H-code consistency for flammable, corrosive, and acute-tox Cat 1–3; S4-H314-NO-REMOVE-CLOTHING (MED) — P361 compliance
  • Round-trip test r26 baseline: 30/30 success, CRIT=0, HIGH=14, MED=181
  • LLM prompt: Section 1 Use fallback — source phrase captured when Section 1.2 exists but no specific use is listed (e.g. '无相关详细资料')
  • LLM prompt: Section 8 OEL "not required" detection — 不要求 / 无需监控 / 不适用 and similar phrases now stored in AdditionalInfo.FullText instead of being silently omitted
  • LLM prompt: Section 9 Densities always extracted; VapourPressure added for flammable/volatile products (H224/H225/H226/H330–H332)
  • LLM prompt: Section 12 PersistenceDegradability.BiologicalDegradability always populated when the source subsection exists

References


License

Licensed under either of:

  • Apache License, Version 2.0
  • MIT License

at your option.