Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1rCLQeKBgnBo3oTgHuTMe
5.9 KiB
5.9 KiB
YOUR STEP: 1 — Feedback and profile (plan §21 step 1)
Scope (plan §6, §7, §8; tests in §20 under Vote, Rating events, Migration, and the prompt):
- Migration
migrations/0002_curation_v2.sql(§7). Createrating_events,article_embeddings,interest_embeddings,article_assessments,candidate_runswith the exact columns/indexes in §7; addruns.config_json,runs.provider_costs_json,issue_articles.why. Copyratingsrows intorating_eventsper §6.2 (vote=1 → 'loved',1.0;vote=-1 → 'not_for_me',-1.0;source='migration';kind='explicit';event_at=rated_at), thenDROP TABLE ratings; DROP TABLE feed_priors;. Orchestrator decision: do NOT dropscoresin this step. Stage A scoring (db::upsert_score,recently_low_scored_ids) still writes/reads it until step 4 replaces it witharticle_assessments; step 4 will add a0003migration dropping it. Do not edit0001_init.sql. Migration is plain SQL, no Rust bootstrap. - Three-way
Vote(§6.1) insrc/types.rs:Loved | Good | NotForMe,as_str→"loved" | "good" | "down",parsealso accepts legacy"up"→Loved,value(&FeedbackConfig)→ configured 1.0 / 0.35 / −1.0. Add[curation.feedback]config (loved_value,good_value,not_for_me_value,verdicts_in_prompt = 60). RemoveVote::as_i64,Rating,FeedPrior, and everything that depended onratings/feed_priors(db::upsert_rating,ratings_with_feed,upsert_feed_prior,feed_priors,profile::rebuild_feed_priors, the feed-prior term inprefilter.rsandScoredArticle.feed_prior— set the prefilter's feed-prior contribution to nothing; the field can go). HMAC links (src/auth.rs) must verify for all three votes; message stays{issue_date}/{article_id}/{vote}. - Footer + confirmation page (§6.1):
RatingLinksinsrc/epub/chapters.rsandsrc/epub/templates/chapter.xhtmlrender three links on one line sized for e-ink:Was this a good pick? [ Loved it ] [ Good ] [ Not for me ] Read online ↗. X4 edition still renders no links.server::handle_ratingappends onerating_eventsrow (kind='explicit',source='epub', labelloved|good|not_for_me, value from config) and returns "Recorded: Loved it — thanks." (etc.) plus the other two links so a mis-tap can be corrected. No feed-prior rebuild, no provider call. rating_eventsaccess (§6.2) insrc/db.rs:append_rating_event(...),db::current_ratings(lookback_days) -> Vec<RatedArticle>implementing the latest-explicit-event rule (ORDER BY event_at DESC, id DESC,clearedremoves the article from the learned set), joiningarticles, the best entry's feed title, and the most recentissue_articles.summary. MoveRatedArticletosrc/types.rswithsummary: Option<String>,facets: Option<Facets>(defineFacetsper §12.1 now, allOptions, unused until step 5),note: Option<String>, plusRatingEvent. Also defineRatedArticle.value: f64andlabel.- Ratings CLI (§6.3) in
src/main.rs:ratings list [--days 90] [--label loved|good|down|cleared],ratings set --article ID|--url URL --label loved|good|down [--note "..."],ratings clear --article ID|--url URL.set/clearappend rows withsource='cli'andissue_datefrom the latestissue_articlesrow for the article if any;--urlcanonicalizes withdedupe's canonical URL function thendb::article_id_for_url. Print something useful. data/profile.md(§8.2): create the file with the initial content shown in §8.2 verbatim; addprofile_pathconfig (defaultdata/profile.md); loader that parses any## Interestssection (one per line, leading-stripped) and unions it case-insensitively with the OPML interests, passing everything else through verbatim. RemoveSTATED_PREFERENCESand the reader/wants/does-not-want/how-to-judge parts ofPROFILE_PREAMBLEfrom code (they now live in the file); the editor-in-chief framing paragraph stays in code. If the file is missing, warn and continue with the OPML interests only.- System prompt (§8.4): rebuilt every run in this exact order: framing → profile.md verbatim
(minus Interests) → standing interests grouped by
profile::themes::group_into_themes→ learned adjustments → recent verdicts: up toverdicts_in_promptmost recent explicit ratings, newest first,LOVED | title | feed | one-line summary(labelsLOVED/GOOD/NOT FOR ME), cleared and duplicate articles removed. Byte-identical within a run.profile_versionbumps only on the weekly rebuild, as today.kvkeepsingest_watermark,taste_profile,taste_profile_learned,profile_version(§7.8). - Weekly rebuild (§8.3): still on the DeepSeek client this step (the Claude editor client
arrives in step 2). Each rated line carries
LOVED | title | feed | summary | facets: format/depth/evidence/technicality/topic_group | note: …(omit empty parts), up to 200 most recent explicit ratings, cleared excluded; the softened "strong prior, not a rule" instruction; the required "Diversity check:" bullet. - Pipeline: remove the
rebuild_feed_priorscall and everyfeed_priorsreference; the paper must still build.handle_ratingandprofile rebuildmust not touch dropped tables.
Tests to add/adapt (§20): Vote parse/serialize incl. up→Loved; HMAC for all three; template
renders three links standard / none X4; latest explicit event wins; cleared removes from the
learned set; migration on a temp DB through 0001 then 0002 copies old ratings with the right
labels/values and ratings/feed_priors are gone; CLI set/clear append source='cli' rows
(test the db-level function); profile.md Interests parsing and union; system prompt section order
and verdict block content. Update tests/m7_server.rs and tests/m4_epub.rs as needed.