The code is public
September 30, 2026. Il Radar can now be downloaded, installed with one command and customized from a menu.
The code is on GitHub, MIT licensed. The Docker image contains everything — the newspaper and the model that translates it — and starts with a single command. Profile, archive, editions and models live in a volume, and the first run starts on its own.
docker run -d --name radar -p 8765:8765 -v radar-dati:/dati
ghcr.io/matvitale6/il-radar:latest
To customize it there's a TUI, a keyboard-driven menu in the terminal: where you live, which sections to turn on or off and how many entries, the keywords and their weight, the GitHub topics, the lists of athletes and places. It edits the profile line by line, so the comments survive; before writing it checks the file is still valid and keeps a copy of the old one. Uses only Python's standard library.
I tested it in a real terminal, a pty inside the container, and it surfaced a flaw that tests with fake keystrokes couldn't see: holding an arrow key down dropped keystrokes, because the terminal reset to normal mode after every key. It now stays in "cbreak" mode for the whole session.
-
models
One translates, the other explains
Translation is now handled by TranslateGemma 4B (by Google, 3.3 GB, via Ollama), a model built specifically to translate. It replaced MiniCPM 4.1, a generic eight-billion-parameter model that wrote things like "Data centeri sono blind boxes", sometimes left the title empty, and once answered in Swedish. I compared them on fourteen real headlines: TranslateGemma is more faithful and, on my computer, faster — about four seconds per headline.
MiniCPM 4.1 stays for one job: summarizing each GitHub repository's README in English, which TranslateGemma then translates. Judging whether a story is relevant isn't something it can do — which is why selection stays with the rules — but it's fine at summarizing. It isn't downloaded by default in the Docker image, because it weighs 5 GB: it's added with an environment variable, and without it the repository description is translated instead.
On CPU alone, inside the container, thirty-two entries translate in about sixteen minutes, thirty-one seconds per entry: in the morning, while I'm doing something else, that's fine.
The problem
Every morning brings too much news to read it all. What you need is a few, the right ones, in a format that ends.
Every morning brings hundreds of articles on what I follow: AI news, cyberattacks, new GitHub repositories, research papers. A feed doesn't help, because it never ends: it's built to keep you in, not to let you stop. A newsletter does half the job, but someone else made the selection, for an average reader.
A newspaper has a property a feed doesn't: it ends. It has two sheets, a front page, an order of importance. The problem is reducing six hundred items to a page that reads in ten minutes, in a way that makes the reader trust even what's missing.
The solution isn't another feed or a generated summary: it's a system that acts as a newsroom. It collects, removes duplicates, picks with written, readable rules and composes the page. The work is architecture and layout: rules decide what gets in, and a model running locally steps in only to translate what's in English.
- It must end. Two A4 sheets and a fixed number of entries. The limit is the feature, not a flaw.
- It must print well. The same page reads on screen and prints on two sheets, with no browser headers and no manual touch-ups.
- It must say why. Every entry carries the words that got it in: a selection that can't explain itself can't be corrected.
- It must not stop. An unreachable source or a translation that doesn't respond can't block the morning run.
- Nothing leaves the computer. Translation runs locally: no news text goes to an outside AI service.
- Nothing to install. Python's standard library only: no package an update could break.
How it works
An archive at the center, one step at a time, and a page that ends on two sheets.
Three drawings of the same thing: the page as it comes out of the printer, the run that produces it, and what each step writes to the archive. The dashed boxes in the technical view are real files. In the run from 25/09/2026, 696 new items became 35 on the page: translation touched thirty of them, because the other five were already in Italian.
The run is a sequence of steps that each write to one column of the archive, and the next step reads that column. Nothing passes from memory to memory: if the run stops, the archive says where it had got to.
-
1 · collection
Different sources, one archive
News comes from RSS feeds of technology, security and digital-policy outlets, in English and Italian, plus targeted Google News searches; repositories from GitHub, by topic, among the most starred and updated in the past month; research papers from the arXiv feed, titles only. My profile also has sport, Italian and international current affairs, and a weekend-in-Rome agenda: which sections exist is decided by the reader's profile. Every item has an identifier and comes in once. An unreachable source gets skipped and logged: it doesn't stop the run.
-
2 · duplicates
The same story, in two languages
Ten outlets give the same story with different headlines, sometimes in two languages. I didn't use a model to recognize it, but a rough fingerprint: each headline keeps the first five letters of every word with at least four letters, minus stop words. That makes "governo" and "government", "attacco" and "attack" the same thing across Italian and English. Two headlines are the same story if they share at least three fingerprints and at least 60% of the shorter one's.
The duplicate doesn't disappear: it stays in the archive, hidden, so a later run won't count it again, and every extra outlet bumps up the story's score — with a bonus only for the first three, after which only the count matters.
fig. 4 — the fingerprint, on a constructed example -
3 · selection
A score you can read
Every item gets points from the profile's keywords, each with its own weight — negative ones too, to push noise down. News is split into three sections (governments and regulation, cybersecurity, AI and development) and only enters if it touches technology or security: a law that doesn't mention digital stays out. Then it works down from the strongest, skipping anything too close to one already picked: without this step, seven American county moratoriums would fill half the page. Repositories are picked by topic, among those gaining the most stars in the last seven days, one per topic, and one that's already appeared won't return for five days — otherwise the big, long-standing projects, which always score the same, would show up every morning. Sport, gaming and current affairs have their own rules, described below.
On the page every entry says "Why it's here" with the words that got it in. At the bottom, a section lists what scored points but didn't get in: the reader sees what's missing, not just what's there.
-
4 · translation
Only English goes through the model
Italian sources come out as they are. Whatever's in English, once selected, gets translated into Italian by a language model running on the computer: TranslateGemma, by Google, four billion parameters, via Ollama. It translates the headline and the first two sentences of the text. A second model, MiniCPM 4.1, eight billion parameters, reads each GitHub repository's README and writes a short summary, which TranslateGemma then translates; it's optional in the Docker image. These are replaceable steps, because selection doesn't depend on them. If a translation looks suspicious — empty, looping, with Chinese characters, too long or too short — it's retried once, and if it still fails the headline stays in English: the item comes out anyway.
-
5 · print
A page that prints
The edition is one HTML file per day, and at the end of the run also a PDF identical to the printout. It's laid out like the front page of a black-and-white newspaper: on the left the lead story and a two-column flow with the sections, repositories, sport and, on weekends, the Rome agenda; on the right the headlines rail; weather in the masthead. On screen, at the bottom, there's "left out" and the ▲ useful and ▼ not interested buttons, which don't print: they save the rating to the archive and are used to tune weights and thresholds. The server rebuilds the page on every request, so the rating shows up right away.
Every step leaves its own result in the archive. A run interrupted halfway picks up where it was: an item that already has a summary isn't retranslated, and one already seen isn't collected twice. It's what lets me rerun the whole thing without worrying, and it's the same idea that holds up ResGestae: the state lives outside the program, where it can be read.
The choices, and why
Almost all of them start from the final page and work backwards.
-
shape
The newspaper ends
Twelve full news stories and ten in brief, three repositories, two sport pieces in full and five in brief, three gaming articles and six in brief, five current affairs headlines per region and five research papers: these are numbers written in the profile, and they're the project. A finite container forces you to choose, and forces the selection to be honest: what's left out is listed at the bottom, with the points it scored, not hidden.
-
selection
Rules choose, the model doesn't
In early tests I'd ask it to judge: "is this story relevant, yes or no?", "which category is it?". It got it wrong too often, in ways that were hard to explain. I wrote the judgment into rules instead: keywords with a weight, in a readable file. The benefit isn't just reliability: every entry says "Why it's here", and a selection that explains itself can be corrected. The model is left with translation, where a mistake is obvious at a glance.
-
privacy
Translation runs on the computer, only when needed
Ollama lives in a project folder, with no system install, and gets switched on only for the duration of the translations and off afterward; if it's already running, it just uses it and leaves it alone. No text goes out to any external AI service: that's the constraint that decided this piece. I've changed the translation model twice: a one-billion-parameter one was fast but wrote poor Italian; a generic eight-billion one, MiniCPM, translated better but still poorly, with errors like "Data centeri sono blind boxes". Now TranslateGemma translates, four billion parameters, built specifically for translation, and so far it's the one that does it best. All three ran, and run, locally.
-
dependencies
Standard library only
Archive, configuration, requests, server and feed reading all use only standard Python modules: no package to install, no update that could break the morning run, no virtual environment to repair. The price is that the page is composed out of strings, with no template engine; for a single page that's a price I'm happy to pay.
Built to order: my profile, and yours
The filters you see on this page are mine. The system is the same for anyone: the profile is what changes.
Il Radar doesn't decide on its own what matters: a profile does, which is a configuration file plus a list of sources. Mine reflects what I follow — technology and security, repositories by topic, sport, current affairs, weekends in Rome — and that's a personal choice, not a limit of the system. The engine that collects, deduplicates, selects and lays out doesn't change.
For another person or a company, the work is building the right profile: which outlets to read, which words matter and how much, which sections are needed, how many entries per page, which city for the weather.
-
what changes
A file and a list of sources
Keywords with their weights, thresholds, how many entries per section, the weather's city, the outlets and the Google News searches. None of it touches the engine, and it's tested in minutes: rerun the pipeline and check the page, even in the print preview.
-
what needs code
The sections, and new ones
Changing the name and number of the news sections takes two lines of code. A new section, with its own source and rules, is more work but already has a template to follow: that's how sport and current affairs arrived.
-
how it's tuned
The system shows what's missing
At the bottom of the page, "left out" lists what scored points but didn't make it, and the ▲ ▼ buttons record what would have helped: they're the tool for adjusting weights and thresholds after the first few days.
What went wrong
The useful part. Real mistakes, with the diagnosis.
-
mistake 1
Paper isn't screen, and every browser prints its own way
The goal was for it to print well, and it took three rounds. First the browser headers: title, date and address showed up on every sheet. The page margin goes to zero, so the browser has nowhere to put them, and the ten-millimeter margin comes from the page itself. Then the number of sheets: from three to two, with columns that flow instead of fixed-height bands that stayed half empty. Then the unbalanced front page, fixed with a script that measured the columns in A4 dimensions and moved stories around so they'd end together.
Diagnosis: all of this worked in Edge and Chrome, not in Firefox, my default browser. Firefox doesn't split a grid or a column block across two pages: it moves the whole thing to the next one, and the first page came out with only the masthead. It wasn't a flaw to fix case by case: pagination belongs to the browser, and every browser does it its own way. The fix was taking it away from the browser entirely. A script builds two fixed-size sheets, 210 by 296 millimeters, measures every article and places it by hand into the columns, in order and without splitting it. If they don't all fit, the least important article becomes a headline in the rail — sport first, then news — and summaries never get shortened. With fixed-size sheets there's nothing to split, and the result is identical everywhere.
-
mistake 2
A call that never responds stops everything
On 25/09/2026, in the scheduled 7:30 run: six summaries, three to fifteen seconds each, then a call to the model never came back. After ten minutes the timeout stopped the entire run, and the news was left without summaries.
Diagnosis: probably an endless loop — without a cap on response length, a local model occasionally writes forever. The problem wasn't a wrong response but the absence of one, and a chain with no deadlines stops on its slowest link. The fix is three limits: a two-hundred-fifty-token cap on the response, a three-minute maximum per call, and an error that only touches that one item, which stays in English while the run continues.
Same day, a second symptom from the same family: an entry with "immediately immediately immediately…" filling half a column. A small model, left unchecked, repeats itself: there's now a repetition penalty.
-
mistake 3
JSON asked for in words doesn't hold up
With MiniCPM I wanted structured responses: title and summary in JSON. Asked for in words, it got it wrong about half the time — a bracket, a quote mark, an extra comment. I tried Ollama's constrained output: with MiniCPM it errors out ("Failed to initialize samplers").
Diagnosis: I was asking a small model to follow a syntax, which is exactly what it gets wrong. The fix was lowering the bar: two lines of text, TITLE and SUMMARY, read with a regular expression. It always writes the line-based text; if a line is missing, the item comes out with the original text. With TranslateGemma the question doesn't even come up anymore: it answers directly with the translation, no lines to parse.
-
mistake 4
MiniCPM falls back into Chinese
An ideogram would show up in the middle of an Italian sentence: "la trasparenza dei 数据中心". MiniCPM is trained heavily on Chinese and occasionally slips back into it.
Diagnosis: it's not an error a better instruction can prevent, and it's invisible until you actually read it. The fix is a check in code: a regular expression for Chinese and Korean characters; if it fires, it retries once; if it fires again, it gives up and keeps the English original. The check is still there, and it's the same one that rejects suspicious translations.
-
mistake 5
It wouldn't open from my phone
At home I wanted to read it from my phone, but the server only listened on the computer itself. To open it to the home network I made it listen on every interface, which means exposing it: what keeps it closed from the outside is the firewall, with the port open only on "private" networks and the local network, configured by a project script and not by hand.
Diagnosis: opening a service to the network is a security decision, not a convenience one, and it needs to be written down where someone will find it. With a VPN active, on top of that, you need the option that keeps local-network devices visible.
Outcome, and what's missing
Il Radar has been running since 24/09/2026: every morning at 7:30 it collects, selects, translates and lays out, and the edition is ready on the PC, on the phone at home, or on paper. The part that's held up best isn't any single step: it's that each one writes its own result to the archive, where an interrupted run leaves a readable state and rerunning costs nothing.
If I had to name the transferable lesson: the final format decides the architecture. A newspaper that has to fit on two sheets forces you to choose, to say what was left out, and to measure in the format the page will actually come out in. The language model, in all this, is a small, replaceable component: it translates. The work is in everything else.
The ▲ ▼ ratings already pile up in the archive, but they don't do anything on their own yet: weights and thresholds are still tuned by hand. Closing the loop — using the ratings to correct the profile, and showing which keywords caused a mistake — is the next step, and I'll do it once there are enough of them to make it statistics instead of anecdote.
Get in touch for a consultation ↑ All projects
The code is public, MIT licensed, on github.com/MatVitale6/il-radar. If you need a profile of your own — other sources, other topics, another paper format — get in touch.