Metadata-Version: 2.5
Name: leichtfacile
Version: 0.7.0
Summary: Drafts easy-language versions of Swiss official texts with a large language model.
Project-URL: Homepage, https://github.com/bobrovsky420/leicht-facile
Project-URL: Documentation, https://github.com/bobrovsky420/leicht-facile/blob/main/docs/quickstart.md
Project-URL: Source, https://github.com/bobrovsky420/leicht-facile
Project-URL: Issues, https://github.com/bobrovsky420/leicht-facile/issues
Author-email: Alexander Bobrovsky <bobrovsky@seznam.cz>, Ivan Kuzmych <i.kuzmych@hotmail.com>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: Apertus,FALC,LLM,Leichte Sprache,easy language,plain language
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: End Users/Desktop
Classifier: Natural Language :: French
Classifier: Natural Language :: German
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Text Processing :: Linguistic
Requires-Python: >=3.12
Requires-Dist: fastapi>=0.142
Requires-Dist: httpx>=0.28
Requires-Dist: jinja2>=3.1.6
Requires-Dist: openai>=3.0
Requires-Dist: pdfplumber>=0.11.10
Requires-Dist: pydantic-settings>=2.11
Requires-Dist: pydantic>=2.12
Requires-Dist: pyphen>=0.18
Requires-Dist: python-multipart>=0.0.32
Requires-Dist: spacy<3.9,>=3.8.16
Requires-Dist: trafilatura>=2.3
Requires-Dist: uvicorn[standard]>=0.54
Requires-Dist: wordfreq>=3.1.1
Description-Content-Type: text/markdown

# Leicht·Facile

[![CI](https://github.com/bobrovsky420/leicht-facile/actions/workflows/ci.yml/badge.svg)](https://github.com/bobrovsky420/leicht-facile/actions/workflows/ci.yml)
[![Release](https://github.com/bobrovsky420/leicht-facile/actions/workflows/release.yml/badge.svg)](https://github.com/bobrovsky420/leicht-facile/actions/workflows/release.yml)

Leicht·Facile drafts easy-language versions of Swiss official texts (German Leichte
Sprache and Einfache Sprache, French FALC; Italian follows) with a large language model,
and proves with code that no deadline, amount or phone number got lost. It works with any
OpenAI-compatible endpoint (a provider's API, a gateway or a model server of your own)
and was built and evaluated with Apertus, the open Swiss model. It is a drafting
assistant for the people who write official texts, from trained easy-language writers to
case workers, not a replacement for their review or for the test by readers from the
target group.

> About 1.25 million adults in Switzerland aged 16 to 65 (22 percent) have low reading
> skills (Swiss Federal Statistical Office, PIAAC 2022/23). None of them should be excluded from the official
> letters that decide their money, deadlines and rights.

Two use cases: a canton's letters and web pages
([use-case-canton.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/use-case-canton.md)), and easy-language letters that
accompany decisions of child and adult protection authorities (KESB) and courts, texts
that may never leave the authority (decision mode v1;
[use-case-decisions.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/use-case-decisions.md)). Further use cases, from patient letters and lease terminations to
penal orders, payment orders and tax letters, are sketched in
[use-cases.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/use-cases.md). Most of them are under official, medical or
tax secrecy and may not go to a chatbot on a foreign cloud: for them, the model has to
run where the organisation controls it, and **Apertus fits best**
([why](https://github.com/bobrovsky420/leicht-facile#why-apertus-for-confidential-texts)).

Authors: Alexander Bobrovsky (bobrovsky@seznam.cz) and Ivan Kuzmych
(i.kuzmych@hotmail.com). Demo video: <https://youtu.be/5el7q6Xtgik>. Maturity: a
deployable prototype for German and French drafts of public texts and letters, a proof
of concept for decisions. Built with AI coding assistants (Claude Code), directed and
reviewed by the authors.

> The language model does everything that needs language understanding. Plain code
> does everything that can be counted.

1. **Understand** (the model): the document becomes a fact sheet: the reader's core
   questions, every fact with its exact value, hard words, passages that can go.
   Code adds every date, amount and contact the model missed.
2. **Write** (the model): a draft in easy language, every sentence linked to the source
   sentences it comes from, every omission explained.
3. **Check** (code): 45 deterministic rules for German and 41 for French from the
   published Swiss guidelines (sentence length, passive, genitive, numbers, Swiss
   spelling, Mediopunkt, ...) and an exact check of every fact value in both
   directions (no value lost, no value invented), in milliseconds, without a model.
4. **Judge** (a second model, ideally a larger one): meaning only: distorted facts, invented
   statements, metaphors, unclear references.
5. **Repair** (the model): only the flagged sentences, until no blocking finding is left
   (at most 3 rounds); then one sentence for each must-keep value still missing (at
   most 10), checked like the rest.
6. **Review** (human): source and draft side by side, provenance, fact checklist,
   approve, edit or reject each block, export with an audit trail.

![The review page: the source on the left, the easy-language draft in the middle, the must-keep facts on the right](https://raw.githubusercontent.com/bobrovsky420/leicht-facile/main/docs/images/review-page.png)

*The review page with the German example letter (team-written): the source with what
was left out and why, the draft with its versions and findings, and the must-keep
facts.*

### Why not one chatbot prompt?

Ask a chatbot to "rewrite this in Leichte Sprache" and you get a text, nothing more:

- **no fact contract:** nothing says which deadlines, amounts and contacts must survive;
- **no provenance:** no sentence points back to the source sentence it comes from;
- **no omission log:** what was left out, and why, is not recorded;
- **no independent check:** nothing but the model itself looks at the result.

The chatbots people reach for also run on foreign clouds. Leicht·Facile adds those four
things, a review workflow and an audit trail, and runs on infrastructure the
organisation controls.

**How we measure the difference.** The evaluation compares the full pipeline (P2) with
single prompts on the same model (P as in pipeline, not a language level;
[use-case-canton.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/use-case-canton.md) §4). P0 is the plain chatbot prompt. The
fairer baseline is a strong single prompt: a published Leichte Sprache prompt from a
Swiss public administration
([simply-simplify-language](https://github.com/machinelearningZH/simply-simplify-language),
MIT), here in its "complete" mode. Final run: 20 held-out pairs of real German texts,
published by Swiss cantons and federal offices with their easy-language versions, on the
CSCS endpoint, 12 October 2026 (all numbers: [evaluation.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/evaluation.md)).

| | A strong single prompt | Leicht·Facile |
|---|---|---|
| Must-keep facts found | 82 % | 100 % |
| Hard facts lost without a trace | 15 % (33 % in its default mode) | none: it lists every one it misses |
| Invented values, per 100 blocks | 0.0 | 0.0 |
| Rule errors, per 100 blocks | 15.0 | 3.7 |

### Why Apertus for confidential texts

For a public web page, any good model would do, and Leicht·Facile works with any of
them. A letter that names a person is under official secrecy (StGB art. 320), a
patient letter under medical secrecy (StGB art. 321), a tax letter under tax secrecy
(DBG art. 110). Four conditions follow, and of the models we know, only Apertus meets
all four:

| Condition | Rules out | Apertus |
|-----------|-----------|---------|
| The text stays inside the organisation (secrecy law; the cantonal data protection commissioners' [resolution of November 2025](https://www.privatim.ch/wp-content/uploads/2025/11/20251124_privatim_Resolution-Hyperscaler_DE.pdf)) | Chatbots and model APIs on foreign clouds | Runs on-premise or air-gapped: the 8B on one 24 GB GPU with the official vLLM image (run by the team on an NVIDIA L4); Swisscom and Infomaniak serve the 70B in Switzerland |
| The organisation runs the model itself | Closed models | Open weights |
| The organisation can account for what the model learned | Open-weight models that keep their training data secret (Llama, Gemma, Qwen, Mistral) | Open training data and code; website opt-outs honoured, also for older crawls |
| The model is Swiss | Fully open models built and trained abroad (EuroLLM, SmolLM3) | Built by EPFL, ETH Zurich and CSCS, trained in Lugano; German, French and Italian, with Swiss German and Romansh data |

The Canton of Ticino already runs Apertus in-house to translate sensitive government
documents. Tokens are nearly free (about 1.6 US cents per page for the full pipeline at
Public AI's list prices, [evaluation.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/evaluation.md)); on own hardware the GPU is the cost. The legal
basis, the sources and the use cases:
[use-cases.md §2](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/use-cases.md#why-apertus-fits-best).

## Quick start

Requirements: Python 3.12 or newer with [uv](https://docs.astral.sh/uv/) (or pip), and
an OpenAI-compatible model endpoint with its key: Apertus on the CSCS inference API or
the Public AI gateway, a model server of your own (vLLM, llama.cpp, Ollama) or a
provider's API.

```bash
uv tool install leichtfacile  # or: pip install leichtfacile
leichtfacile setup            # the endpoint, the key and the model; then the language models
leichtfacile serve            # starts the app and opens http://127.0.0.1:8000
```

`setup` writes the user's config file (`.env` in the current folder and the environment
override it) and offers the spaCy models for German and French, about 90 MB from
GitHub (`leichtfacile models` gets them later). A checkout for development, the command line alone, a server for an
organisation, an offline install, running a model yourself, reaching the app from
other machines and what to do when it does not start: [quickstart.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/quickstart.md).

Model tiers with Apertus: by default the 8B reads, writes and repairs, and the 70B
judges (when the endpoint serves it; otherwise `LLM_NAME` judges).
`LLM_NAME=swiss-ai/Apertus-v1.5-70B` runs every stage on the 70B;
`LLM_UNDERSTAND_NAME=swiss-ai/Apertus-v1.5-70B` lets the 70B read (the fact sheet) and
the 8B write. [evaluation.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/evaluation.md) compares the three.

### Your first draft

The interface speaks English, German or French (switch at the top right).

**1. Start a draft.** Before the first draft, the start page offers the example
letters: a municipality's letter about premium reduction in German and in French, and a
decision of a child and adult protection authority (team-written, described in the
examples' [README.md](https://github.com/bobrovsky420/leicht-facile/blob/main/data/examples/README.md)). Click one to open the New draft form
with it filled in, or click **New draft** and paste a text, enter a web address or
upload a file (PDF, HTML, text or Markdown). Choose the language (or let it be
detected), the level (A2 Leichte Sprache or FALC, B1 Einfache Sprache or langage clair;
the level names the rule set, not a measured reader level) and the pipeline (P2 full by
default), and click **Create draft**. The model answers live: a run takes a few minutes.

![The start page before the first draft: the three example letters and New draft](https://raw.githubusercontent.com/bobrovsky420/leicht-facile/main/docs/images/first-draft-start.png)

![The New draft form with the German example letter pasted, and the settings: language, level and pipeline](https://raw.githubusercontent.com/bobrovsky420/leicht-facile/main/docs/images/first-draft-new.png)

**2. Follow the stages.** The page updates by itself: Read, Write, Check, Judge and
Repair, each with its result as soon as it is done (the fact sheet, then each draft
version). When the pipeline is done, the review page opens.

**3. Review and export.** The review page (screenshot above, below "How it works")
shows the source, the draft and the must-keep facts. Click a block or a fact to see
where it comes from. Approve, edit or reject each block (or **Approve all undecided**),
tick **Review complete** and click **Save review**. **Reader view** shows the text as a
reader gets it; **Export** writes it as Markdown, HTML or Word, or the whole job as an
audit bundle (JSON).

**Check my text** runs the rule checker on any text you paste, without a model and
without storing it.

## Sovereign deployment

| Where the model runs | How |
|----------------------|-----|
| a) On-premise | A model server in the organisation's network, for example Apertus 1.5 8B on vLLM with the official image on one GPU with 24 GB or more (measured on an NVIDIA L4: [aws-l4-2026-10-10/](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/hardware/aws-l4-2026-10-10/)); point `LLM_BASE_URL` at it |
| b) Air-gapped | As (a), and the app installed offline from a folder of wheels collected on a connected machine, the spaCy models unpacked from the same folder ([quickstart.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/quickstart.md)). The UI assets are in the package: no CDN, no telemetry, no installs at runtime |
| c) Sovereign Swiss cloud | Point `LLM_BASE_URL` at a Swiss-operated endpoint (Swisscom and Infomaniak serve Apertus 70B); the reported numbers come from CSCS in Lugano |

Documents and review records stay in a SQLite database in a folder the organisation
controls (`DATA_DIR`). At runtime the app contacts only the model endpoint, and a web
page's server when a user enters a URL: no telemetry, no CDN, no login provider
([architecture.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/architecture.md) §10). Which texts need this, and why Apertus
fits them best: [Why Apertus for confidential texts](https://github.com/bobrovsky420/leicht-facile#why-apertus-for-confidential-texts).

## Documentation

- [quickstart.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/quickstart.md): installing and starting the app, the model endpoints, servers, offline installs, development
- [architecture.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/architecture.md): design and architecture
- [ui-design.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/ui-design.md): the web UI's specification: tokens, components, screens, accessibility
- [rules.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/rules.md): the rule catalogue the checker implements
- [prompts.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/prompts.md): prompt design and change log
- [sources.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/sources.md): reference pairs, demo documents, licences
- [use-case-canton.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/use-case-canton.md): a walkthrough for a cantonal administration
- [use-case-decisions.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/use-case-decisions.md): letters that accompany decisions of child and adult protection authorities and courts (partly planned)
- [use-cases.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/use-cases.md): all use cases at a glance; the further ones sketched with the checks they need
- [image-input.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/image-input.md): what Apertus 1.5 reads from a scanned letter, by kind of value
- [hard-texts.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/hard-texts.md): hard words and sentences for tests
- [evaluation.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/evaluation.md): the evaluation tables, per language and setup

## Licence

Code: Apache License 2.0, see [LICENSE](https://github.com/bobrovsky420/leicht-facile/blob/main/LICENSE). Texts from cantonal and federal web
pages are not part of the repository; the evaluation fetches them from their URLs
([sources.md](https://github.com/bobrovsky420/leicht-facile/blob/main/docs/sources.md) §4).
