Sixty Impact Report
An InDesign print report turned into a live site and a local multimodal RAG
At a glance
- Problem
- For its 60th anniversary the Tzu Chi Foundation published the Sixty Impact Report, a long Traditional-Chinese print document laid out in InDesign: about 64 spreads and 682 text stories.
- What I built
- Two consumers of one source file.
- Outcome
- Live at 60impact.tzuchi.org.tw (18 pages), plus a local Q&A tool that cites the report page behind each answer.
- My role
- Solo
- Period
- 2026-05 → 2026-10
Abilities shown
Each links to the ability and its level on the Skills page.
The problem
For its 60th anniversary the Tzu Chi Foundation published the Sixty Impact Report, a long Traditional-Chinese print document laid out in InDesign: about 64 spreads and 682 text stories. The report needed a web version people could read on a phone. Its content was also worth querying: a question should get an answer that points back to the page it came from.
What I built
Two consumers of one source file. The first is the public website: 18 pages, built with Astro and deployed to Cloudflare Pages. The second is a RAG pipeline in Python that ingests the InDesign file (text, page renders and images) into a vector store on drust, my own BaaS. A small Flask app answers questions over it and cites each claim as (page N). The Q&A app runs locally only; it is not public.
How it works
- A parser reads the unzipped IDML directly: pages, exact text blocks with point sizes and positions, and placed images.
- A renderer rebuilds an approximate image of each page, places the real linked images, and tags every text block with an id.
- A local vision model looks at that render and returns only JSON: the reading order, role and heading level of each block id.
- Python assembles Markdown and chunks from the exact IDML text, embeds them with a 2,560-dimension model, and writes pages, chunks and images to drust.
- At query time: vector search on chunks, rerank, then expand strong hits to their whole page. Broad questions are answered from page summaries made at ingest.
Decisions
- The vision model never re-types text. It only decides order and structure; the words come from the IDML. This gives zero OCR drift on a complex print layout.
- Only local models. Chat, vision, embedding and rerank all run on self-hosted Qwen3 models behind an OpenAI-compatible proxy, so no external AI API is involved.
- Migrate the site page by page. The first version was zero-dependency HTML. I wrapped it in Astro so old and new pages could ship side by side, and wrote a verify script, because Astro silently prefers a legacy file with the same name.
- One content source. The repo JSON is the source of truth; drust is a mirror pushed by a script, with a local-JSON fallback if it fails.
Outcome
The site is live at 60impact.tzuchi.org.tw, with all 18 pages now migrated to Astro and deployed by CI on every push to main. The Q&A tool stays local, with cited answers. Built solo with Claude Code, using a spec → plan → implement → review loop.