iT邦幫忙

2026 iThome 鐵人賽

DAY 12
0
AI Engineering

它說得頭頭是道系列 第 19 篇

# Day 19 — 5 MB 的事實檔:每一條都要能指回它從哪裡來

  • 分享至 

  • xImage
  •  

昨天講了資料從哪裡來。今天講它被整理成什麼。

結果是一份大約 5 MB 的檔案,裡面有 1,183 張票卡,來自兩套不同的系統。

但這篇不是要講「我把資料存成 JSON」——那沒什麼好說的。

要講的是每一條事實上面掛了哪些欄位,以及為什麼。因為那份欄位清單,基本上就是前面十天所有教訓的總和。


先看一條長什麼樣

拿掉真實內容之後,一條紀錄大概是這樣:

id              T:6a82c54bcbdb8e46e07a9573
source          trello
board           某某看板
key             gUCZ1m0s
title           [某人][某產品][前端] 某某欄位在某情況下沒有帶到
list            [DEV] Code Review
status          active
url             https://…/c/gUCZ1m0s/…
summary         8/17 某次同步會的結論。某功能在某情況下……
pages           []
keywords        []
owners          某人, 某人
commentCount    2
month           2026-08
created         2026-08-17T08:24:43Z
lastActivity    2026-08-23T13:01:11Z
comments        [{date, author, text}, …]
links           [外部連結, …]
sourceRaw       fetch/trello/(cards.json + actions/6a82c…)

看起來很平常。但其中有五個欄位是踩過坑之後才加上去的,那五個才是這篇的重點。


欄位一:sourceRaw —— 這條東西是從哪個檔案來的

這是整份結構裡我最在意的一欄。

它記的不是網址,是這條紀錄是從我本機的哪一個原始檔案、哪一段解析出來的。

為什麼需要這個?因為 Day 11 那五個追問:

你是怎麼認定的?這是最新的資訊嗎?有沒有其他票卡有附註?

那五個問題有一個前提——要能翻回去看原文。

如果一條事實只留下「結論」而沒有留下「它從哪一段長出來的」,那它就變成了一個沒辦法被質疑的東西。你只能選擇相信或不相信,不能查。

而 Day 17 我寫過,這正是我不走 fine-tune 的關鍵理由:焊進去的知識沒有來源,追問就失效了。

所以 sourceRaw 存在的意義只有一個:

任何一條事實,都必須能夠一路指回它的原文。

它不是為了 debug 方便。它是為了讓「不相信」這個動作有地方可去。


欄位二:pages —— Day 9 那個缺掉的維度

這一欄是空的。

我指的是上面那個例子裡,pages 是 []——那張卡沒有被判定屬於任何頁面。

而這一欄之所以存在,整個原因就是 Day 9 和 Day 10 那兩篇:

原本在 A 頁面的甲功能,被寫進了 B 頁面。

因為那張卡上,根本沒有「這是哪一頁」這個資訊。

所以我在資料結構裡硬生生開了這一欄。

而有意思的是:它常常是空的,而這正是它的價值。

如果我沒有這一欄,那個資訊的缺席是看不見的——資料讀起來完整、通順、每一句都成立(Day 10 那本「只記幾點沒記哪一天」的行事曆)。

有了這一欄之後,缺席變成了一個可以被數的東西:我可以直接問「有多少張卡沒有頁面歸屬?」然後得到一個數字。

而我後來真的去數了。在這篇之後又重建過幾輪的那份語料裡(單位是合併過的變更單元,不是原始票卡數):

787 個單元裡,有 381 個沒有任何頁面歸屬——48%。

將近一半。

我想強調這個數字本身沒有好壞:它不代表那些卡開得不好。它代表的是一件更具體的事——這份資料回答不了將近一半的「這是哪一頁」。

而在有這一欄之前,那件事是完全看不見的。我不會知道是一半,還是百分之五。

把「不知道」變成一個欄位,它才會從隱形變成可見。

這是我做這份結構時學到最重要的一件事。一個缺漏如果沒有位置可以放,它就會被當成不存在。

(而 keywords 和 gap 也是同樣的設計——前者是檢索用的抓手,後者記的是「這張卡自己講了什麼還沒做」。兩個都是寧可空著,也要有位置。)


欄位三:comments —— 完整留著,不摘要

每一則留言都留:日期、作者、原文。

這一條看起來理所當然,但它其實是一個取捨。留言是整份資料裡最肥的部分——1,183 張卡的留言加起來,佔掉這 5 MB 的絕大部分。

摘要一下不行嗎?省空間、也好讀。

不行,而理由是 Day 2 那篇:

留言區是理由的所在地。 卡片的標題和描述講的是「要做什麼」,而「為什麼這樣做、試過什麼、為什麼放棄另一個方案」全部在留言裡。

而摘要這個動作本身,就是在做判斷——決定哪些重要、哪些可以丟。

那個判斷如果在這個階段做,就是不可逆的。 被摘掉的東西再也回不來了,而我當下沒有辦法知道下個月誰會需要哪一句。

在建置的最前面那一段,能不壓縮就不壓縮。

壓縮要留到最後、留到我知道要回答什麼問題的時候再做。


欄位四:lastActivity 和 month —— 讓時間可以被比

Day 8 最嚴重的那個錯誤,是「卡片說還沒修,但東西早就修好了」——在時間上不可能成立的一句話。

而當時要抓出那個錯,我得自己去比日期。

所以這份結構裡,每一條都帶著自己的時間座標:什麼時候建立的、最後一次有動靜是什麼時候、歸在哪一個月。

這不是為了排序好看,是為了讓「哪一個是現在的說法」這個問題有辦法用算的。

Day 10 講過,兩個例子缺的維度不一樣:一個缺空間(哪一頁),一個缺時間(哪個版本是現在)。

pages 補的是前者,時間欄位補的是後者。


欄位五:status 和 owners —— 誰還記得這件事

最後這兩欄比較樸素,但它們是 Day 18 那篇的直接後果。

status 告訴我這件事現在是不是還活著。owners 記的是當時誰在上面。

而 owners 的用途,不是為了究責。是為了回答一個很現實的問題:

如果這一條有疑問,我現在還能去問誰?

Day 18 寫的那張已經流失的架構圖,教訓就在這裡:知識的存續,跟「還有沒有人記得」綁在一起。 那份資訊在系統裡有價值,前提是還有一個人可以確認它。

所以這一欄其實是一個保存期限的提示。當上面的名字都不在了,那條紀錄就從「可以查證的」變成「只能相信的」。


另外還有三個索引

除了票卡本身,這份檔案還帶著三張索引表:

索引 大小 做什麼
頁面 52 個 從一個頁面,找到所有跟它有關的卡
月份 4 個 從一個時間範圍,找到那段期間動過的東西
關鍵字 12 組 從一個詞,找到相關的卡

它們存在的理由只有一個,就是 Day 16 那句話:「先不要全讀,需要時再查。」

要能「需要時再查」,就得先知道去哪裡找。這三張表就是那個「哪裡」。

而注意它們的規模——52、4、12。這不是什麼精密的向量索引,就是三張很土的對照表。

但它們夠用,而且它們不用花錢、不用模型、查詢時間是零。


所以這份結構其實是一張錯誤清單

寫到這裡我才意識到,可以換一個角度看這整份設計:

欄位 它擋的是哪一種錯
sourceRaw 講了一句沒辦法查證的話(Day 11)
pages 屬性被放到錯的地方(Day 9、10)
comments 完整保留 理由在壓縮的時候被丟掉(Day 2)
時間欄位 拿舊的說法當成現在的(Day 8)
owners 沒有人可以確認了(Day 18)

每一欄都對應一次我踩過的坑。

我一開始以為設計資料結構是一件工程的事——想清楚要存什麼,然後存下去。

現在我覺得它比較像在寫一份防禦工事:你不是在設計「要放什麼進去」,你是在設計「哪些錯誤要讓它沒有地方躲」。


明天:但這份東西,還沒辦法直接餵給模型

這 5 MB 是完整的、可追溯的、有索引的。

而它有一個很致命的問題:它太大,而且它的單位是錯的。

一張票卡,不是一個適合被引用的單位。有些卡上面有五個決定,有些卡只有一句話。

明天要講的就是這件事——要把事實切成可以引用的顆粒,而最難的部分不是決定切多細。


上一篇
# Day 18 — 知識散在八個地方,而其中一份已經沒了
下一篇
# Day 20 — 顆粒大小的難題,不是「切多細」,是「一張票到底多大」
系列文
它說得頭頭是道 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言