@@ -2110,6 +2110,40 @@ def recent(name: str, sources: dict, limit: int = 5) -> list[dict]:
21102110 ]
21112111
21122112
2113+ def changed_here (name : str , sources : dict , sweep : str , limit : int = 100 ) -> list [dict ]:
2114+ """その回が最後に動かしたものを、新しい順に(`_stamped` の印で引く)。
2115+
2116+ **変更履歴(`app/collect_log.py`)では答えられない問いのほう。** あちらは回ごとに
2117+ 1 行で、動いた見出しは頭の 20 件までしか残らず、しかも回ごとの間隔は桁違いなので
2118+ 短い回の行が長い回の行を押し流す。ここは**いま手元にあるものを文書の側から**引くので、
2119+ その回が何回前に走っていようが読める。
2120+
2121+ **読めるのは最後に動かした回だけ。** 後の回が同じ 1 件に触れば印はそちらに移る
2122+ (印は 1 回分しか持たない)—— 「整理が直したあと見出しが触った」ものは
2123+ 見出しのほうに出る。
2124+ """
2125+ src = sources .get (name )
2126+ if src is None or not sweep or limit <= 0 :
2127+ return []
2128+ rows = db .query (
2129+ src .path ,
2130+ "SELECT title, opening, tags, updated_at, extra FROM docs"
2131+ f" WHERE json_extract(extra, '$.{ CHANGED_BY_KEY } ') = ?"
2132+ " ORDER BY updated_at DESC LIMIT ?" ,
2133+ (sweep , limit ),
2134+ )
2135+ return [
2136+ {
2137+ "title" : row ["title" ],
2138+ "opening" : row ["opening" ],
2139+ "tags" : load_tags (row ["tags" ]),
2140+ "updated_at" : row ["updated_at" ],
2141+ "extra" : load_json (row ["extra" ]),
2142+ }
2143+ for row in rows
2144+ ]
2145+
2146+
21132147def doc_versions (name : str , sources : dict , title : str ) -> dict :
21142148 """1 件の見出しについて、**いまの世代と 1 つ前の世代**の中身を返す。
21152149
@@ -2375,6 +2409,7 @@ def stream_docs(
23752409 only_new : bool = False ,
23762410 edits : bool = False ,
23772411 diff : dict | None = None ,
2412+ sweep : str = "" ,
23782413):
23792414 """焼く素材を 1 件ずつ返す。`material` の中身で、**丸ごとは持たない**。
23802415
@@ -2387,6 +2422,8 @@ def stream_docs(
23872422 同じ並びになる(新しいぶんは採番の順で後ろに付く)。
23882423
23892424 `diff` を渡すと、数えた結果をそこへ書く(戻り値にできないため)。
2425+
2426+ **動かした 1 件には、どの回が動かしたかを脇書きに残す**(`_stamped`)。
23902427 """
23912428 counts = diff if diff is not None else {}
23922429 now = _iso (_now ())
@@ -2419,7 +2456,7 @@ def stream_docs(
24192456 # 墓標。**消さずに印を付けて残す**
24202457 removed_titles .append (title )
24212458 updated += 1
2422- yield _buried (before , raw , now )
2459+ yield _stamped ( _buried (before , raw , now ), sweep , "removed" )
24232460 continue
24242461 doc = _to_doc (raw , now , item .web )
24252462 if doc is None :
@@ -2436,7 +2473,9 @@ def stream_docs(
24362473 else :
24372474 # 集めるほうで同じ見出しが来るのは「もう持っている」の意味
24382475 skipped += 1
2439- yield {** doc , "doc_id" : before ["doc_id" ]}
2476+ yield {** doc , "doc_id" : before ["doc_id" ]}
2477+ continue
2478+ yield _stamped ({** doc , "doc_id" : before ["doc_id" ]}, sweep , "updated" )
24402479
24412480 next_id += 1
24422481 for raw in edits_of .rest ():
@@ -2455,7 +2494,7 @@ def stream_docs(
24552494 doc ["extra" ] = _merge_extra ({}, doc ["extra" ])
24562495 added += 1
24572496 added_titles .append (doc ["title" ])
2458- yield {** doc , "doc_id" : next_id }
2497+ yield _stamped ( {** doc , "doc_id" : next_id }, sweep , "added" )
24592498 next_id += 1
24602499
24612500 counts .update ({
@@ -2479,6 +2518,7 @@ def material(
24792518 collected : list [dict ],
24802519 only_new : bool = False ,
24812520 edits : bool = False ,
2521+ sweep : str = "" ,
24822522) -> tuple [list [dict ], dict ]:
24832523 """焼く素材と、前世代との差分を組み立てる。
24842524
@@ -2514,7 +2554,7 @@ def material(
25142554 """
25152555 counts : dict = {}
25162556 rows = sorted (previous .values (), key = lambda d : d ["doc_id" ])
2517- docs = list (stream_docs (item , rows , collected , only_new , edits , counts ))
2557+ docs = list (stream_docs (item , rows , collected , only_new , edits , counts , sweep ))
25182558 return docs , counts
25192559
25202560
@@ -2618,6 +2658,50 @@ def _buried(doc: dict, raw: dict, now: str) -> dict:
26182658 }
26192659
26202660
2661+ CHANGE_ADDED = "added"
2662+ CHANGE_UPDATED = "updated"
2663+ CHANGE_REMOVED = "removed"
2664+
2665+ # 脇書きに残す「最後に動かした回」の鍵。
2666+ CHANGED_BY_KEY = "changed_by"
2667+ CHANGE_KEY = "change"
2668+
2669+
2670+ def _stamped (doc : dict , sweep : str , change : str ) -> dict :
2671+ """動かした 1 件に、**どの回が・どう動かしたか**を脇書きとして押す。
2672+
2673+ **変更履歴を別に持つだけでは、読みたいほうが読めない。** 控え
2674+ (`app/collect_log.py`)は回ごとに 1 行で、動いた見出しは頭の 20 件しか
2675+ 残らない。しかも回ごとの間隔は桁違いなので、短い回の行が長い回の行を押し流す
2676+ —— 「整理が何を直したのか」を引きに来ると、たいてい流れた後になる。
2677+ 文書の側に押しておけば、**いま手元にあるものについては回の間隔と関係なく読める**。
2678+
2679+ **残すのは 1 回分だけ**(上書きする)。履歴を溜める場所ではない ——
2680+ 溜めると 1 件ごとに際限なく伸び、焼き直すたびに全件がその分だけ重くなる。
2681+ 「いつ」は既にある `updated_at` 列が持っているので、ここには持たない。
2682+
2683+ **脇書きに置くのは、検索と本文精査の邪魔をしないため。** 全文検索が索引するのは
2684+ 見出しと本文だけ(`ingest/core.py` の `docs_fts`)、AI へ差し込む一覧に載るのも
2685+ 配信日と出典だけ(`_current_line`)なので、ここへ入れたものはどちらにも出てこない。
2686+ 本文やタグへ書くと、読む人にも AI にも「文書の中身」として見える。
2687+
2688+ **鍵の数の天井(`MAX_EXTRA_KEYS`)より後に押す。** あの天井は AI が運んでくる
2689+ 事実が際限なく増えないためのもので、こちらが押す印まで落とすと、
2690+ 脇書きの多い 1 件だけ印が付かないことになる(いちばん読みたい 1 件がそれになる)。
2691+ """
2692+ extra = doc .get ("extra" ) if isinstance (doc .get ("extra" ), dict ) else {}
2693+ stamp = {CHANGE_KEY : change }
2694+ if sweep :
2695+ stamp [CHANGED_BY_KEY ] = sweep
2696+ return {** doc , "extra" : {** extra , ** stamp }}
2697+
2698+
2699+ def changed_by (doc : dict ) -> str :
2700+ """その 1 件を最後に動かした回。持っていなければ空。"""
2701+ extra = doc .get ("extra" )
2702+ return str ((extra or {}).get (CHANGED_BY_KEY ) or "" ) if isinstance (extra , dict ) else ""
2703+
2704+
26212705def removed_reason (doc : dict ) -> str :
26222706 """その 1 件を消した理由。持っていなければ空。"""
26232707 extra = doc .get ("extra" )
@@ -2912,8 +2996,12 @@ def _count_dropped(item, plan, previous, collected, only_new, edits) -> int:
29122996
29132997
29142998def bake_lines (item , sources : dict , previous , collected , only_new = False , edits = False ,
2915- survey : dict | None = None ):
2916- """焼く素材を 1 行ずつ返す。**2 周目**(数えるのは `bake_survey`)。"""
2999+ survey : dict | None = None , sweep : str = "" ):
3000+ """焼く素材を 1 行ずつ返す。**2 周目**(数えるのは `bake_survey`)。
3001+
3002+ **どの回が焼いたかはここで渡す。** 押すのは実際に焼かれる素材のほうで、
3003+ 数える 1 周目ではない(あちらは件数しか使わない)。
3004+ """
29173005 plan = survey or bake_survey (item , sources , previous , collected , only_new , edits )
29183006 rows = _rows_of (previous )
29193007 limit = _expiry_limit (item )
@@ -2926,7 +3014,7 @@ def bake_lines(item, sources: dict, previous, collected, only_new=False, edits=F
29263014 }
29273015 }, ensure_ascii = False )
29283016
2929- for doc in stream_docs (item , rows (), collected , only_new , edits ):
3017+ for doc in stream_docs (item , rows (), collected , only_new , edits , None , sweep ):
29303018 if limit is not None and _doc_time (doc ) < limit :
29313019 continue
29323020 for n , rule in enumerate (plan ["rules" ]):
0 commit comments