feat(linkedin-studio): N6 — forslags-lag i trends (angle/targetLevel/rationale/relatedIds + selected + --ids + F7/F9-felt actionability/verdict/readerQuestion/painPoint/saturation) [skip-docs]
Artikkelforslaget blir en persistert entitet (steg 2, A1-4) og godkjenningen får
et hjem (steg 3, A1-7) — før dette genererte agenten vinkel/begrunnelse som ble kastet.
Åtte additivt-valgfrie felt på TrendRecord/TrendInput/TrendItem:
- Forslag (A1-4/A1-5): angle · targetLevel (fritekst, brukerdefinert spenn — aldri enum) ·
rationale · relatedIds[] (flerkilde).
- F7 leser-side: actionability {formulated, note?} (N7-bånd-cap-gaten leser `formulated`) ·
verdict BÆRENDE/STØTTE/NYHET (lukket mekanisme-vokab).
- F9 leser-side: readerQuestion · painPoint · saturation (N7.5-sveipet fyller dem).
Ingen schema-bump: feltene er additivt-valgfrie, ingen record trenger migrering →
SCHEMA_VERSION forblir 4 (loadStore Math.max håndterer det). First-sight-persistering
(re-capture unionerer topics + re-scorer kun; klobrer aldri en triagert vinkel).
Validering: typede felt (verdict/actionability) feiler hardt ved malformert input;
fritekst/id-liste normaliseres lempelig (summary/topics-idiomet).
Livssyklus: TrendStatus += "selected" → new→selected→acted|skipped. Ny select-verb +
--ids-batch (act/skip/reset/select); partiell suksess = exit 0 + miss-rapport, all-miss = exit 2.
setStatusMany(): ren batch-mutasjon, per-id found/notFound.
Brief: forslagsfeltene rendres per kandidat (detaljert i topp-treff, kompakt token i bullets) +
ny «🚧 I produksjon»-seksjon (selected=valgt + acted=skrevet), pillar-uavhengig, deterministisk
sortert. selected/acted forlater arbeidskøen men vises i produksjons-boardet.
commands/trends.md Step 5 oppgradert: triage → Velg (select) / Skip, batchet per verb (--ids).
Trends-suite 245→266 (ny floor, +21 N6-tester). To eksisterende tester oppdatert for den
endrede brief-kontrakten (acted vises nå i I produksjon; ranking-descriptoren ekskluderer selected).
tsc rent. Roundtrip bevist: capture m/ alle felt → query → brief rendrer feltene → select → I produksjon.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S7SQpXJpBSNvpWaTNq1kWZ
This commit is contained in:
parent
bb0725af66
commit
65b60337fe
9 changed files with 732 additions and 33 deletions
|
|
@ -7,7 +7,7 @@
|
|||
* node --import tsx src/cli.ts query --topics <a,b> [--store <path>] [--json]
|
||||
* node --import tsx src/cli.ts list [--since <YYYY-MM-DD>] [--limit <n>] [--store <path>] [--json]
|
||||
* node --import tsx src/cli.ts status [--store <path>] [--json]
|
||||
* node --import tsx src/cli.ts act|skip|reset --id <id> [--store <path>]
|
||||
* node --import tsx src/cli.ts act|skip|reset|select (--id <id> | --ids <a,b,c>) [--store <path>]
|
||||
* echo '<raw item|batch>' | node --import tsx src/cli.ts normalize
|
||||
* echo '<scored candidates>' | node --import tsx src/cli.ts score [--mode kortform|long-form] [--threshold N]
|
||||
* echo '<raw item|batch>' | node --import tsx src/cli.ts capture [--store <path>] [--json]
|
||||
|
|
@ -26,8 +26,10 @@
|
|||
* SessionStart hook surfaces; RE-R3d adds a DERIVED temporal overlay (first-mover up / saturated
|
||||
* down) as a within-tier ranking refinement, tunable via `--first-mover-days`/`--saturation-at`.
|
||||
* `add` is the MANUAL single-trend path (raw flags, no
|
||||
* normalization, publish-date-free). `act`/`skip`/`reset --id` set a trend's lifecycle status
|
||||
* (RE-R3b): the morning brief EXCLUDES acted/skipped and records each surfacing (per-day-idempotent
|
||||
* normalization, publish-date-free). `act`/`skip`/`reset`/`select` set a trend's lifecycle status
|
||||
* (RE-R3b + N6 `selected`), one id (`--id`) or a batch (`--ids a,b,c`, A1-9): the morning brief
|
||||
* EXCLUDES acted/skipped/selected from the work queue (selected+acted show in "I produksjon"
|
||||
* instead) and records each surfacing (per-day-idempotent
|
||||
* `surfacedCount`) so the loop stops re-surfacing handled work; a re-capture refreshes the score
|
||||
* (timing decays). The polling + relevance-scoring itself lives upstream; this is the deterministic store.
|
||||
*
|
||||
|
|
@ -63,6 +65,7 @@ import {
|
|||
queryByTopic,
|
||||
saveStore,
|
||||
setStatus,
|
||||
setStatusMany,
|
||||
} from "./store.js";
|
||||
import type { TrendStatus } from "./types.js";
|
||||
import { normalizeItem, normalizeItems, itemToInput } from "./item.js";
|
||||
|
|
@ -115,7 +118,7 @@ function usage(msg: string): never {
|
|||
" query --topics <a,b> [--store <path>] [--json]\n" +
|
||||
" list [--since <YYYY-MM-DD>] [--limit <n>] [--store <path>] [--json]\n" +
|
||||
" status [--store <path>] [--json]\n" +
|
||||
" act|skip|reset --id <id> [--store <path>]\n" +
|
||||
" act|skip|reset|select (--id <id> | --ids <a,b,c>) [--store <path>]\n" +
|
||||
" normalize < raw-item-or-batch.json\n" +
|
||||
" score [--mode kortform|long-form] [--threshold N] < scored-candidates.json\n" +
|
||||
" capture [--store <path>] [--json] < raw-item-or-batch.json\n" +
|
||||
|
|
@ -244,18 +247,28 @@ function main(): void {
|
|||
return;
|
||||
}
|
||||
|
||||
if (command === "act" || command === "skip" || command === "reset") {
|
||||
const id = flags.id;
|
||||
if (!id || id === "true") usage(`${command} needs --id <id>`);
|
||||
const status: TrendStatus = command === "act" ? "acted" : command === "skip" ? "skipped" : "new";
|
||||
if (command === "act" || command === "skip" || command === "reset" || command === "select") {
|
||||
// Ids from --ids <a,b,c> (batch, A1-9) or a single --id <id>; --ids wins when both are given.
|
||||
// splitTopics is the generic comma-split+trim+drop-blank helper (ids are case-sensitive, not lowercased).
|
||||
const ids =
|
||||
flags.ids && flags.ids !== "true"
|
||||
? splitTopics(flags.ids)
|
||||
: flags.id && flags.id !== "true"
|
||||
? [flags.id]
|
||||
: [];
|
||||
if (ids.length === 0) usage(`${command} needs --id <id> or --ids <a,b,c>`);
|
||||
const status: TrendStatus =
|
||||
command === "act" ? "acted" : command === "skip" ? "skipped" : command === "select" ? "selected" : "new";
|
||||
const store = loadStore(storePath);
|
||||
const res = setStatus(store, id, status);
|
||||
if (!res.found) {
|
||||
console.error(`error: no trend with id: ${id}`);
|
||||
const res = setStatusMany(store, ids, status);
|
||||
// All-miss is an argument-class error (exit 2, store untouched); any hit saves + reports the misses (exit 0).
|
||||
if (res.found.length === 0) {
|
||||
console.error(`error: no trend with id: ${res.notFound.join(", ")}`);
|
||||
process.exit(2);
|
||||
}
|
||||
saveStore(storePath, store);
|
||||
console.log(`Marked ${id} ${status}`);
|
||||
const miss = res.notFound.length > 0 ? ` (${res.notFound.length} not found: ${res.notFound.join(", ")})` : "";
|
||||
console.log(`Marked ${res.found.length} ${status}${miss}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue