Files

5.9 KiB
Raw Permalink Blame History


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):

  1. Migration migrations/0002_curation_v2.sql (§7). Create rating_events, article_embeddings, interest_embeddings, article_assessments, candidate_runs with the exact columns/indexes in §7; add runs.config_json, runs.provider_costs_json, issue_articles.why. Copy ratings rows into rating_events per §6.2 (vote=1 → 'loved',1.0; vote=-1 → 'not_for_me',-1.0; source='migration'; kind='explicit'; event_at=rated_at), then DROP TABLE ratings; DROP TABLE feed_priors;. Orchestrator decision: do NOT drop scores in this step. Stage A scoring (db::upsert_score, recently_low_scored_ids) still writes/reads it until step 4 replaces it with article_assessments; step 4 will add a 0003 migration dropping it. Do not edit 0001_init.sql. Migration is plain SQL, no Rust bootstrap.
  2. Three-way Vote (§6.1) in src/types.rs: Loved | Good | NotForMe, as_str"loved" | "good" | "down", parse also 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). Remove Vote::as_i64, Rating, FeedPrior, and everything that depended on ratings/feed_priors (db::upsert_rating, ratings_with_feed, upsert_feed_prior, feed_priors, profile::rebuild_feed_priors, the feed-prior term in prefilter.rs and ScoredArticle.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}.
  3. Footer + confirmation page (§6.1): RatingLinks in src/epub/chapters.rs and src/epub/templates/chapter.xhtml render 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_rating appends one rating_events row (kind='explicit', source='epub', label loved|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.
  4. rating_events access (§6.2) in src/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, cleared removes the article from the learned set), joining articles, the best entry's feed title, and the most recent issue_articles.summary. Move RatedArticle to src/types.rs with summary: Option<String>, facets: Option<Facets> (define Facets per §12.1 now, all Options, unused until step 5), note: Option<String>, plus RatingEvent. Also define RatedArticle.value: f64 and label.
  5. 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/clear append rows with source='cli' and issue_date from the latest issue_articles row for the article if any; --url canonicalizes with dedupe's canonical URL function then db::article_id_for_url. Print something useful.
  6. data/profile.md (§8.2): create the file with the initial content shown in §8.2 verbatim; add profile_path config (default data/profile.md); loader that parses any ## Interests section (one per line, leading - stripped) and unions it case-insensitively with the OPML interests, passing everything else through verbatim. Remove STATED_PREFERENCES and the reader/wants/does-not-want/how-to-judge parts of PROFILE_PREAMBLE from 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.
  7. 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 to verdicts_in_prompt most recent explicit ratings, newest first, LOVED | title | feed | one-line summary (labels LOVED/GOOD/NOT FOR ME), cleared and duplicate articles removed. Byte-identical within a run. profile_version bumps only on the weekly rebuild, as today. kv keeps ingest_watermark, taste_profile, taste_profile_learned, profile_version (§7.8).
  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.
  9. Pipeline: remove the rebuild_feed_priors call and every feed_priors reference; the paper must still build. handle_rating and profile rebuild must 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.