Danish statute + EUR-Lex RAG pipeline with clickable inline citations

marklok adds a full legal-retrieval stack over four commits: pgvector-backed ingestion for Retsinformation statutes and EUR-Lex regulations, section-reference-aware retrieval, and a slide-in citation panel in the chat UI. The fork is clearly targeting Danish legal practice.

searchchat-ui

The retrieval logic in lawRetrieval.ts goes beyond vanilla embedding search. It detects structured references like "§ 42, stk. 3" or "artikel 5 DORA" via regex (backed by a LAW_ALIASES map), runs a metadata lookup for that exact section in parallel with the cosine similarity search, then merges and deduplicates the results. That hybrid pattern - metadata fallback alongside embedding search - is the most reusable piece here, applicable to any domain with structured citation syntax.

Ingestion targets Danish primary-source statutes (hvidvaskloven, funktionærloven, selskabsloven) and EU regulations (DORA 32022R2554, PRIIPs 32014R1286), driven by env vars (RETSINFORMATION_LAWS, EURLEX_REGULATIONS). The backend migration adds a document_chunks table with an IVFFlat cosine index. There's a minor inconsistency: 001_document_chunks.sql creates the index with lists=100, then 002_reindex_chunks.sql immediately rebuilds it at lists=20 to reduce memory pressure.

Two other fixes come bundled in this commit set and are worth pulling on their own. normalizeCitation was silently dropping citations with page:null - the fix treats null as page=0. And the stop button was showing while the invisible <CITATIONS> SSE block was still streaming; showStopButton = isLoading && !isLoadingCitations fixes that so users can't accidentally cancel before citations arrive.

The law context is appended unconditionally to every chat turn, which will eat tokens on non-legal queries. The ingestion scripts call live endpoints with no visible rate-limiting.

So what Worth a look if you serve a Danish or EU regulatory audience and want a reference implementation of jurisdiction-specific RAG. The section-ref detection logic and the `page:null` citation fix are both worth pulling regardless of whether you need Danish law. Skip the ingestion scripts unless you're actually indexing Retsinformation or EUR-Lex. The full pgvector dependency is the main cost.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

4 commits from marklok/danishmike, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
130ce0ae Add Danish law & EUR-Lex retrieval pipeline Markus 2026-05-08 ↗ GitHub
commit body
- Add lawRetrieval.ts: vector search + metadata-based fallback for specific
  § and Artikel references, merges results and dedupes
- Add ingestion scripts for Retsinformation and EUR-Lex (DORA, PRIIPs)
- Add document_chunks table migration and IVFFlat index (lists=20)
- Wire law context into chat and project-chat routes
- Add RETSINFORMATION_LAWS and EURLEX_REGULATIONS env vars to .env.example
- Ignore backend/data/ (local EUR-Lex HTML files) in .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
20f4a079 Add inline law citation panel Markus 2026-05-08 ↗ GitHub
commit body
Law citations (Retsinformation / EUR-Lex) now open in a slide-in panel
on the same page instead of navigating to an external URL. Shows source
badge, full excerpt text, and a link to the original source.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
90a57483 Fix law citations not rendering as clickable pills Markus 2026-05-08 ↗ GitHub
commit body
- normalizeCitation: treat page:null as page=0 instead of dropping the
  citation (law chunks have no page number)
- Emit loading_citations SSE event when <CITATIONS> block starts so the
  frontend can track citation generation state
- Strengthen formatLawContext prompt to reliably produce <CITATIONS> blocks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3f95ec9d Fix streaming UX: hide stop button during citation generation Markus 2026-05-08 ↗ GitHub
commit body
When visible text finishes but the invisible <CITATIONS> block is still
generating, the stop button now switches to the send arrow so the user
can't accidentally cancel and lose citations. The streaming indicator on
the last message also stops during this phase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-206.md from inside the repo you want the changes in.

⬇ Download capture-thread-206.md