昨天講完 AI 助教的知識來源,兩邊的資料終於都結構化完成:
plans.md(Day 13 讀圖進來的)sessions.json(Day 8 從紀錄解析出來的)今天要把它們擺在一起,來看達成率怎麼樣,
而寫到一半才發現,這個系統裡達標原本就已經有一個基準,但似乎不太夠用。
跑班分成好幾組,每一組一張配速區間表。我跨在 1A 和 1B 之間,所以只列這兩張:
| 區 | 1A | 1B |
|---|---|---|
| 一區 E | 5:10/km|2:04 | 5:30/km|2:12 |
| 二區 M | 4:40/km|1:52 | 5:00/km|2:00 |
| 三區 T | 4:20/km|1:44 | 4:40/km|1:52 |
| 四區 A | 4:00/km|1:36 | 4:20/km|1:44 |
| 五區 I | 3:40/km|1:28 | 4:00/km|1:36 |
| 六區 R | 3:20/km|1:20 | 3:40/km|1:28 |
相鄰兩組每一區差 20 秒/km。三區以下剛好是往下挪一格—1B 的三區就是 1A 的二區。
自己之前是在 1A,但最近剛回歸,狀態還沒回到 1A,所以有時候跑 1B 的配速。
hit 這個欄位早就在了sessions.json 的每一組本來就帶著一個 hit,儀表板上達標印綠色、沒達標印黃色。
它是這樣算的:
_TARGET = re.compile(r"(\d):(\d{2})(?:\.\d+)?\s*/\s*400\s*m")
TOLERANCE_SEC = 2.0
hit = secs <= target + TOLERANCE_SEC
其實有兩個問題,寫的時候一個都沒察覺:
容錯率在 ±2 秒。 但教練的區間表每一區不一樣—四區 ±2,三區是 ±4。
而且只罰慢。 secs <= target + 2 —跑再快都算達標。
至於目標來自 Garmin 活動名稱,當時是因為 plans.md 還不存在,那是手邊唯一有目標配速的內容。
Day 13 之後教練的課表進來了。所以我做的第一件事,是把 _TARGET 整段刪掉,改成查課表的區間帶—教練開幾區,就用那一區的基準。
但這造成了下一個問題。
把八月的每一場品質課表用新舊兩套判準各算一次:
| 日期 | 課表 | 改判前 | 改判後 |
|---|---|---|---|
| 08-11 | 四區 | 2/4 | 2/4 |
| 08-20 | 三區 | 1/4 | 3/4 |
| 08-25 | 四區 | 5/5 | 5/5 |
| 08-27 | 三區 | 1/3 | 3/3 |
| 08-29 | 二區 | 2/2 | 0/2 |
8/29 那天課表開二區。1A 的二區是 1:52 ±4,帶寬 1:48–1:56,而我跑了 2:00.0 和 1:59.2,整整慢 4 秒。
看起來就是沒達標。
但 2:00 正是 1B 的二區。
那天我跑的是 1B—兩趟 1:59.2 和 2:00.0,誤差不到一秒,自己精準地跑在 1B 的二區裡。
問題是:「這天我跑 1B」這件事記在哪?
課表沒有—它寫的是「二區」,那個字在哪一組都成立。訓練紀錄的表格也沒有—那裡只有距離、時間、心率。
它唯一的存放位置,是我出門前打在錶上的 2:00/400m。
前面被我拿掉的 _TARGET,讀的就是這串字。
當時我以為那只是換一個更準的來源—教練的課表,當然比我自己打在錶上的字可靠。
所以正確的做法不是二選一:
_ZONES = plans.load_zones("1A") # 我掛的組
_1B = plans.load_zones("1B") # 狀態還沒回來時跑的
判斷跑哪一組不用猜—同一個區,1A 和 1B 差 20 秒/km,不會認錯:
for g, table in (("1A", _ZONES), ("1B", _1B)):
z = table.get(zone_num) if zone_num else None
if z and abs(z.per400_sec - secs) <= 0.5:
return secs, g
每一組判兩次,8/29 現在是:
對 1A 0/2 | 對當天配速 2/2
兩個數字都對,而且必須並排才講得完整:我沒有跑到 1A 的強度(0/2 是真的),但也不是失控掉速—我完整執行了 1B 的二區(2/2 也是真的)。
只報前者,看起來像練壞了。只報後者,教練會以為我照 1A 跑完了。
查了八月六場品質課表,只有 8/29 跑 1B,其他都是 1A。畫面上跑 1B 的日子多一行說明:
這天跑的是 1B 組的 2 區(2:00 / 400m)。課表開的區沒變,換的是配速表。
換成區間帶之後,還冒出一個原本看不到的東西。教練的表寫的是 ±4,那個 ± 是雙向的,而舊的 secs <= target + 2 只罰慢。
8/13 和 8/20 這兩場,唯一沒達標的都是第一趟,而且是太快:
| 日期 | 第一趟 | 三區帶 | |
|---|---|---|---|
| 08-13 | 1:37.0 | 1:40.0–1:48.0 | 快 3 秒 |
| 08-20 | 1:38.2 | 1:40.0–1:48.0 | 快 1.8 秒 |
三區是節奏區,跑進四區的配速就不是在練同一件事了,而且會吃掉後面幾趟。8/13 那天正是 1:37.0 之後掉到 1:45.6、1:45.0。
畫面上因此要分開標:▲ 慢於上緣、▼ 快於下緣。
寫這篇之前,以為今天的工作是「算出一個百分比」。
做完之後比較實際的講法是:達成率至少要兩個數字,因為「沒做到」和「做了別的」被判成一樣,但他們不是同一個指標。
今天有兩個例子,形狀一模一樣:
| 課表 | 實際 | 那個決定 | |
|---|---|---|---|
| 08-25 | 1000m ×5~6 | 1000m×2 + 800m×3 | 跑到一半吃不消,後三趟縮短 |
| 08-29 | 二區 | 1B 的二區 | 狀態還沒回來,跑 1B |
兩次都不是失誤,是當下的評估,而兩次都差點被壓成一個數字—8/25 被壓成「趟數 5/5、配速 5/5」,8/29 被壓成「0/2」。
一個看起來像滿分,一個看起來像狀況慘烈,而其中兩次都看不出我當下改了什麼。
Day 11 對帳那篇的結論是:兩邊對不上的時候,只能說對不上,不能自己挑一邊來信。而今天一看到對不上,就先把 _TARGET 移除,因為目標隨狀態滾動調整。
sessions.json 有欄位放距離、配速、心率,沒有欄位放「今天狀態沒有很好」。
所以明天做對話式紀錄:在 Telegram 打一句「今天 12K 有點喘」,讓它變成一筆紀錄。讓那種當下臨時改課表的評估,能有跡可循。