<div class="docs-content" data-render>
# NewsService
`name` `news` - enabled by default - 3600s interval (minimum 60s). It fetches developer news from a configured feed, grades each article with an AI model, and stores every article, auto-publishing those at or above the grade threshold and saving the rest as drafts. Source: `services/news.py`. See also [Reading service data](/docs/services-data.html).
> Audience: administrators and maintainers.
## What run_once does
1. Reads the source URL, grading URL, model, and threshold from configuration.
2. Fetches the feed over HTTP (30s timeout) and reads its `articles` list. On error it logs and returns.
3. Loads the set of already-synced external ids from `news_sync` so seen articles are skipped.
4. For each new article it grades the article, decides published versus draft, upserts the `news` row, records the sync state, and stores any extracted images.
5. Logs a summary: new, updated, draft, grading-failed, and skipped counts.
**Grading:** the article title, description, and content (each length-capped) are sent to the grading endpoint with a content-strategy prompt that asks for a single integer from 1 to 10. The first integer in the response is parsed. An article graded at or above `news_grade_threshold` is published; below threshold it is a draft; if grading fails it is stored as a draft with grade 0 and sync status `grading_failed`. Nothing is silently discarded.
## Configuration fields
- `news_api_url` (url, default `https://news.app.molodetz.nl/api`) - the source feed, group Source.
- `news_ai_url` (url, default the internal gateway) - the chat-completions endpoint used to grade, group AI grading.
- `news_ai_model` (str, default the internal model) - the model sent to the grading endpoint.
- `news_grade_threshold` (int, default 7, range 1 to 10) - at or above publishes, below drafts.
- `news_ai_key` (secret) - defaults to the `NEWS_AI_KEY` env var, then the gateway internal key.
Plus the inherited Enabled, Run interval, and Log buffer size fields.
## Tables it writes
| Table | Purpose |
|-------|---------|
| `news` | The article rows: `uid`, `slug`, `external_id`, `title`, `description` (stripped, capped 5000), `url`, `image_url`, `source_name`, `grade`, `status` (`published` or `draft`), `content` (stripped, capped 10000), `author`, `article_published`, `synced_at`. Existing rows are updated in place by `external_id`. |
| `news_sync` | One row per external id tracking `status` (`graded` or `grading_failed`) and `synced_at`, so an article is graded only once. |
| `news_images` | Images extracted per article, keyed by `news_uid`; replaced wholesale when the article is updated. |
## Data it offers
NewsService does not override `collect_metrics()`, so its `metrics` block is empty. Its health is read from the framework fields surfaced at `GET /admin/services/data`: `status` (`running` / `stopped` / `stalled`), `last_run`, `next_run`, `heartbeat`, and the `log_buffer`, where each run records its new/updated/draft/failed/skipped summary. The graded articles themselves are the product, visible under `/news` and in the `news` table.
</div>