news: auto-tag headlines + market-aware cadence + filter UI
- Move news_job from hourly to 3x/hour (cron 10,30,50), with a CadencePolicy gate that throttles to active hours (07-21 UTC weekdays at 20 min), off-hours (3 h), weekends (6 h). Keeps the daytime feed fresh without spamming RSS sources overnight. - Tag each headline on ingestion via DeepSeek (BATCH_SIZE=25, max_tokens=4000, json.JSONDecoder().raw_decode + per-row regex recovery for resilient parsing). Vocabulary: 16 tags including new EU / USA / AI / Conflict. NULL tags are picked up automatically on the next news_job run, so back-tagging is implicit rather than a separate migration step. - Tag UI: pill bar above the feed with off → include → exclude cycle on click; shift-click jumps straight to exclude. State persists in localStorage and is injected into /api/news requests via htmx:configRequest. Per-row chips sit to the right of the headline (new 5-column grid: age | source | title | tags | UTC) so vertical density stays high. - Strategic log header bug: model was hallucinating "(Updated 21:30 UTC)" in future tense. Bumped PROMPT_VERSION 6→7, added explicit ban on time-of-day clauses, and supply the actual current UTC time in the user prompt so the model has no need to invent one. Migration 0012 adds headlines.tags (JSON, nullable). Tests cover vocabulary integrity, validation/normalisation, and the JSON-recovery parser (17 tests).
This commit is contained in:
parent
6e7f57c6b2
commit
2013bfa8cc
15 changed files with 745 additions and 25 deletions
|
|
@ -1143,15 +1143,17 @@ details[open] .pf-analysis__head-left::before { content: "▾ "; }
|
|||
.news-row {
|
||||
padding: 4px 12px;
|
||||
display: grid;
|
||||
grid-template-columns: 50px 130px 1fr 110px;
|
||||
/* age | source | title | tags-on-right | utc-time */
|
||||
grid-template-columns: 50px 130px minmax(0, 1fr) minmax(0, auto) 110px;
|
||||
gap: 12px;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid var(--surface-2);
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.news-row { grid-template-columns: 50px 100px 1fr; }
|
||||
.news-row .local { display: none; }
|
||||
.news-row .local,
|
||||
.news-row__tags { display: none; }
|
||||
}
|
||||
.news-row:hover { background: color-mix(in srgb, var(--accent) 5%, transparent); }
|
||||
.news-row .age { color: var(--dim); text-align: right; }
|
||||
|
|
@ -1166,6 +1168,61 @@ details[open] .pf-analysis__head-left::before { content: "▾ "; }
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* News tag chips on each row + the top-bar pill toggles */
|
||||
.news-row__tags {
|
||||
display: inline-flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 3px;
|
||||
justify-content: flex-end;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
}
|
||||
.tag-chip {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 9px;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--muted);
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0 4px;
|
||||
white-space: nowrap;
|
||||
text-transform: uppercase;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.news-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--surface-2);
|
||||
}
|
||||
.news-tag {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10.5px;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
padding: 3px 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.news-tag:hover { color: var(--accent); border-color: var(--accent); }
|
||||
.news-tag[data-state="include"] {
|
||||
background: var(--accent);
|
||||
color: var(--bg);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.news-tag[data-state="exclude"] {
|
||||
color: var(--negative);
|
||||
border-color: var(--negative);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.news-tag--clear { color: var(--dim); border-style: dashed; }
|
||||
.news-tag--clear:hover { color: var(--negative); border-color: var(--negative); }
|
||||
|
||||
/* --- Empty / loading state ------------------------------------------- */
|
||||
|
||||
.empty {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue