Two-pass wizard for drafting to NAV, Barnevernet, schools, Bufdir, kommune,
Statsforvalter, Trygderetten. Pass 1 (gpt-4o-mini) classifies the situation
and emits clarify questions if facts are missing; user answers inline and
resubmits without losing context. Pass 2 retrieves law passages via hard-RAG
(ClientRagPipeline with body-specific slice presets), drafts in Norwegian
bokmål with gpt-4o using [CITE:N] tokens, self-checks that every citation
maps to a real corpus passage, then translates to the working language.
Result is side-by-side Norwegian + EN/PL/UK with copy/download per side
and an expandable Cited Law panel.
Credit deducts only when Pass 2 actually runs, not on a clarify cycle.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- api/user-docs.php: GET/DELETE shared dbn_user_docs table (SSO users only)
connects to dobetternorge DB via DBN_DB_* env vars
- workbench.php: My Documents panel (section 05) for SSO/free-tier users;
shows docs uploaded from either AI chat or tools, links to AI Chat for upload
- workbench.js: fetch + render doc list, delete with Qdrant cleanup
- tools.css: workbench-docs panel + item styles
- i18n.php: my_docs_* strings in all 4 languages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8-step NDJSON-streaming pipeline that compares two Barnevernet documents:
classifies each doc, extracts parties and timelines, cross-references both
for contradictions/deletions/additions, retrieves corpus legal context, and
synthesises a full discrepancy report with tabbed UI.
New files: DiscrepancyAgent.php, api/discrepancy.php, discrepancy.php,
discrepancy.js. Modified: FreeTier.php (cost=4), i18n.php (all 4 langs),
tool-svgs.php (DC icon), tools.css (dc-* component styles).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Search: category filter pills scope results to a legal domain
- Search: full chunk text returned; click to expand inline beyond 600-char excerpt
- Drill panel: total count label ("Showing X of Y"), sort dropdown, title filter (300ms debounce)
- URL hash: preserves query/mode/lang/category/drill state for bookmarking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Live search/filter bar: filters events by keyword across event, actor, source_excerpt, date
- Actor filter chips: click to filter by actor, multi-select, teal active state
- Year/month group headers when sorted chronologically (── 2023 ──, Mar 2024 ──)
- Per-event copy button (hover-revealed 📋): copies "date · actor · event" to clipboard
- "Hide/show sources" toggle: collapses all source excerpts without re-rendering
- Count badge: "23 events · 3 actors · 2022–2025" above the list
- applyTimelineFilters() unifies sort + actor + text filters in one re-render pass
- CSV export now includes end_date column
- Reset all filter state on each new run
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds optional post-transcription cleanup via GPT-4o/GPT-4o-mini to fix
mishearing errors, punctuation, and domain terms. Speaker role labelling
now accepts a deployment param. Adds i18n strings for advanced options
panel (task, VAD filter, Whisper model, AI cleanup) in all four languages.
Updates BvjAnalyzerAgent and DeepResearchAgent.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copies GcpSpeechClient into the tools repo so it's deployed with the code;
removes the broken dbnToolsAiPortalRoot() path that resolved to a nonexistent
/home/dobetternorge/ai-portal directory. Also restarted the CPU Whisper
service which had a stuck CLOSE_WAIT socket causing silent fetch failures.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes user-facing engine/model/key/beam controls. The server now picks
the best available engine automatically:
1. Microsoft Azure Speech — short clips (≤1MB, no diarization, audio/*)
2. Google Cloud Speech v2 — long audio, diarization, all languages
3. OpenAI Whisper GPU — local fallback
Results display which provider was used (e.g. "Transcribed with Google
Cloud Speech") via transcript-engine-badge and traceMeta.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7-step agent pipeline: document classification, party extraction, timeline
extraction, corpus RAG (child_welfare/echr/family_core/bufdir_guidance),
and synthesis using the user's chosen engine (including dbn-legal-agent).
Progressive NDJSON streaming renders doc_meta, parties, and timeline cards
before the final advocacy brief and procedural red flags arrive.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Source modal now shows LLM-generated document summary (lazy-gen + cached
in documents.summary) instead of raw chunk text; toggle reveals matched
chunk; "View all chunks" button fetches every chunk of the document via
new api/document-chunks.php endpoint
- Each sub-question card gets a "Branch ↓" button that pre-fills the query
with that sub-question and shows a context panel with the prior brief
summary; prior_context + branch_notes are injected into interpretSeed()
and synthesise() so the LLM knows where the research is coming from
- Upload document summaries generated at synthesis time and attached to
upload sources alongside corpus summaries
- DB: documents.summary TEXT column added to bnl_corpus on chloe
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
health.php: Add azure_search check — calls /$count endpoint and
reports doc count in the index. Reads DBN_AZURE_SEARCH_{ENDPOINT,KEY,INDEX}.
corpus-search.php: Add azure mode — semantic + vector hybrid search
via Azure AI Search bnl-legal-v2. Embeds query with LiteLLM
nomic-embed-text; expands keepCats to include government-policy,
health-law, social-services, labour-law, immigration (previously
blocked by contamination workaround, now safe to include).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MariaDB rejects ? placeholders for LIMIT/OFFSET when emulate_prepares=false.
Interpolate $limit and $offset as ints directly into SQL strings in both
corpus-documents.php and corpus-search.php BM25 paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New /advocate.php tab: user selects who they represent (biological
father, mother, foster carer, CWS, etc.) and the agent takes their
side entirely. Adversarial sub-questions target supporting Lovdata
statutes + ECHR precedents; synthesis returns client_strengths[] and
opposing_weaknesses[] alongside the advocate brief.
- DeepResearchAgent: add advocateRole param to run(), interpretSeed(),
expandQueries(), synthesise(). Neutral path unchanged (empty string).
- api/deep-research.php: extract + validate advocate_role from payload;
telemetry logs tool='advocate' vs 'deep_research'.
- advocate.php: new page with role dropdown (presets + custom), same
corpus slices/engine/controls/upload zone as deep research.
- assets/js/advocate.js: page-scoped JS; renders advocate banner,
client strengths card (teal), advocate brief, opposing weaknesses
card (amber), sub-Q cards, sources, uncertainty, next step.
- assets/css/tools.css: append .adv-* rules (~120 lines).
- includes/layout.php: add Advocate nav tab between Deep research and
Summarize.
- index.php: add Advocate cap-card tile.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BM25: adds NOT LIKE filter to SQL WHERE in both FULLTEXT and LIKE paths.
Hybrid + Vector: post-filter hits array by source_url after results return.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- api/corpus-search.php: new endpoint with three search modes (hybrid RAG, BM25 keyword, Qdrant vector)
- api/corpus-documents.php: paginated document browser by category or source name
- corpus.php: search bar with mode+language pills, Browse docs button on each category card with drill-down panel, expand toggle on each source row showing doc count and scraper class
- tools.css: all new corpus interactive styles appended
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds /corpus.php — a data transparency page showing what powers the
legal tools: 9 coverage categories with live doc counts, a full
sources table pulled from the corpus DB, the AI stack (LLMs, Whisper,
Qdrant, Azure AI Search, embeddings, chunking), and a pipeline flow
diagram. Stats are live via a new /api/corpus-stats.php endpoint
(queries dobetter_rag + bnl_admin). The reasoning sidebar is repurposed
as a Corpus health panel on this page.
Also ships the in-progress timeline background events toggle:
API and UI wired together via include_background param.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously the endpoint returned a single JSON object at the end. Apache+
PHP-FPM buffers the entire body until PHP exits, so a 160s azure_full run
caused the browser to drop the fetch as "Failed to fetch" while the server
was still synthesising — the response then arrived to a dead socket.
Switch to application/x-ndjson with one event per line. The endpoint emits
'progress', 'start', 'step' (running/complete/warning/error), 'subq', and a
final 'final' event carrying the full result payload. Output buffering is
explicitly disabled so each line flushes through Apache as soon as the
agent emits it.
DbnDeepResearchAgent::run() now accepts an optional ?callable $emit and
fires step:running before each step + step:complete after, plus a subq
event per sub-question retrieval round.
JS reads response.body as a stream, splits on newlines, updates the
trace panel live, and renders the final result when the final event
arrives. Status pill shows live progress detail (e.g. "Synthesising with
Azure gpt-4o — this is the slowest step…").
Engine row in the form now shows expected duration per engine
(~15-45s mini, ~60-180s full, ~30-90s GPU) so users know what they're in
for before clicking Run.
New surface at /deep-research.php where the user pastes a question or
uploads PDF/DOCX/TXT case files and a LLM-orchestrated agent researches
the Do Better Norge legal corpus from 3-5 angles, with hybrid retrieval,
cross-encoder rerank, and synthesis that emits an inline-[n]-cited
markdown brief plus a numbered sources panel.
Uploaded documents are chunked + embedded in memory only (nomic-embed-text
via LiteLLM) and searched alongside the shared corpus during the same
request — never persisted to disk, DB, or Qdrant.
Reuses ClientRagPipeline::searchAll (hybrid + rerank), dbnV6 slice
helpers, and the existing extract.php text-extraction logic via a new
dbnToolsExtractUploadedFile() helper. Also adds dbnToolsCallGpuLlm()
helper in bootstrap.php — fixes a latent bug where LegalTools.php
was already calling that name with no definition.
Search.php is unchanged.
New api/feedback.php stores rating + correction text to tool_feedback
table in bnl_admin. renderFeedbackWidget() appended to all tool results
(timeline, redact, transcribe, ask, summarize, search). Thumbs reveal
a textarea for missed/wrong items on click; submit POSTs asynchronously.
Engine from last run is stored alongside the rating.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add 4-language switcher (EN/NO/UK/PL), engine choice (Azure mini/full,
GPU/cuttlefish), and expandable Advanced panel (Focus, Confidence filter,
Date types) to timeline.php. Wire new params through api/timeline.php and
LegalTools::timeline() with engine routing, focus-aware prompt injection,
and confidence/date-type post-filters. Add TIMELINE_I18N to tools.js with
improved renderTimeline() confidence colour-coding and new CSS classes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Wrap Mode/Region/Entities/Officials/Output/Exempt/Aliases in a
<details> toggle so the form opens clean with only engine + input visible
- After redaction: Copy, Download .txt, Download .docx buttons appear
below the redacted output (all four languages translated)
- New api/redact-download.php: returns plain text or a minimal valid
DOCX built from scratch with ZipArchive (no external dependencies)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Custom inline form (EN/NO/UK/PL lang switcher) replacing generic stub
- Engine selector: Azure gpt-4o-mini (default), gpt-4o, GPU cuttlefish, regex-only
- Entity type toggles: names, organisations, places, dates of birth
- Output formats: contextual role tags, generic [PERSON], Norwegian pseudonyms
- Keep officials mode: judges/experts kept as [JUDGE: Andersen] format
- Exempt names list: specific names excluded from redaction
- Hint paragraphs explaining each option in all four languages
- Backend: engine routing, callGpuLlm(), applyGenericTags(), applyPseudonymization()
- AzureOpenAiGateway: withDeployment() clone pattern for per-call model override
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- api/transcribe.php falls back to DBN_AZURE_SPEECH_KEY/REGION env vars so BYOK not required
- JS hides Azure key input when DBN_AZURE_SPEECH_CONFIGURED is true
- Remove Translate to English task option from Advanced settings
- Add explanatory hint text for Beam size and VAD filter in all 4 languages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Default UI language to English; lang switcher (EN/NO/UK/PL) persisted in localStorage
- Rename 'rettssak/tingrett' preset to 'Mediation / legal meeting' — court recording is illegal
- Add Ukrainian (uk) and Polish (pl) as selectable audio transcription languages
- TRANSCRIBE_I18N translation object drives all status messages, labels, and trace text
- Apache ProxyTimeout raised to 1800s on server (was 300s — caused 504 on large files)
- set_time_limit(0) + ignore_user_abort(true) in api/transcribe.php
- applyTranscribeI18n() patches data-i18n / data-i18n-placeholder / data-i18n-aria attrs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Default language → nb (Bokmål); auto-detect demoted with warning note
- Default model → large-v3; VAD filter on by default
- Vocabulary prompt promoted to main form with 4 preset buttons
(Barnerett/CPS, Rettssak/tingrett, Generell norsk, Egendefinert)
- Multi-file upload queue: drop/select multiple clips, numbered list UI
- Sequential queue processing with cumulative time_offset per clip
- Backend shifts segment timestamps so SRT/VTT covers full court day
- Merged transcript + segments across all clips for single download
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Split monolithic index.php into per-tool pages (ask, search, summarize,
timeline, redact, transcribe), each with its own URL and bookmarkable state
- Shared shell: includes/layout.php + layout_footer.php; shared form:
includes/tool_form.php used by all text-tool pages
- index.php now redirects authenticated users to ask.php; unauthenticated
users see the login gate only
- transcribe.php: engine selector (GPU/OpenAI/Azure), model size (small/
medium/large-v3), diarize, language, expert settings (beam, VAD, task,
initial prompt)
- api/transcribe.php: engine routing — GPU (cuttlefish), OpenAI BYOK,
Azure AI Speech; passes model/beam/task/vad/prompt to Whisper server
- tools.js: data-active-tool body attr drives setTool() on load; <a> nav
tabs skip click listeners; null guards on form/passcodeForm; engine radio
toggle shows/hides BYOK key inputs and model selector; RTF shown in status
- tools.css: styles for BYOK inputs, expert settings panel, prompt textarea
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New sixth tool in the hub. Accepts MP3/WAV/OGG/M4A/FLAC/WEBM up to 200 MB,
proxies to Whisper on cuttlefish GPU. Optional speaker separation with LLM
role labelling (dommer, advokat, forelder, sakkyndig, etc. via GPT-4o-mini).
Client-side TXT / SRT / VTT download from segment data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract limit raised from 32K to 128K chars per file (long legal docs now fit)
- Redact API body/text limits raised (400KB / 128K chars) to match
- Upload zone accepts multiple files (up to 5); extracted text concatenated with
doc separator and combined before redaction; shows per-file char counts
- LLM redact pass now infers contextual person roles (FATHER, MOTHER, CHILD,
ATTORNEY, JUDGE, etc.) instead of generic [PERSON] for all names; same
individual gets consistent tag throughout the document
- Tag validation widened to allow any [A-Za-z0-9_- ] pattern (not just the
five hardcoded tags), supporting contextual and alias tags
- Alias UI added to Redact mode: user maps real names to bracketed aliases
(e.g. "David Jr" -> [Junior]); aliases injected into LLM system prompt as
override instructions; max 20 aliases, 100 chars each
- max_tokens raised from 2000 to 4000; timeout from 60s to 90s for larger docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
api/extract.php — new endpoint accepting .pdf/.docx/.txt up to 4 MB;
pdftotext for PDFs, ZipArchive+DOMXPath for DOCX, mb_convert_encoding
for TXT; truncates to 32 000 chars to stay within redact limit.
index.php — drop/browse upload zone above the textarea, visible only
in Redact mode.
tools.js — setupUpload(), handleFileUpload(), resetUpload(); drag-and-drop
and file picker both call the extract endpoint then populate the textarea.
tools.css — upload zone, drag-over, file-info, clear button styles.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pass 1: deterministic regex with Nordic/European/ECHR/Global packs
covering fødselsnummer, Swedish personnummer, Danish/Finnish CPR,
UK NI, French INSEE, IBAN, EU phones, ECHR application numbers, DOB,
and national ID label patterns.
Pass 2: LLM semantic scan (Azure OpenAI) finds names, orgs, places
and identifying descriptions missed by regex. Runs on pre-redacted
text so no raw PII reaches the LLM.
Adds region selector (Nordic/European/ECHR/Global) to the Redact UI.
Falls back gracefully when Azure is not yet configured.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>