feat(assistant): 逾期判定接上來,逾期與連續失敗每一輪都提
逾期本來沒實作,監控頁上就寫著「上面的判定只講到期,不講逾期」。 到期與逾期分兩欄,不合成一個判定:到期是「該跑了」,逾期是「due 欄那個 截止時間過了」。一筆七天一次的檢查項天天都可能到期,卻永遠不會逾期; 一筆有截止時間的交辦可能早就逾期,卻因為排定點還沒到而判成還沒到。 合成一個值就把兩種情況混成同一格。 due.sh 多第十二欄 overdue,值是減號(沒有截止時間)、no、逾期的秒數, 或 bad。bad 算成欄位不合法回 2——一個解不開的截止時間跟沒有截止時間 在畫面上長得一模一樣,而前者是登錄的時候就填錯了。done 那幾筆不算逾期; 被暫停的那幾筆照算,因為暫停中止的是到期判定,不是日曆,而一筆被暫停 又逾期的交辦正好是最容易被忘掉的那一種。 另加唯讀子命令 due.sh overdue 給狀態查詢那一路走。走 scan 會推進快照, 那一輪之後的事件判定會全部落空;走 events 又判不到逾期;讓呼叫端自己 拿 due 欄去比更糟,那是第二套判定,漂移的那一天兩邊都說自己對。 patrol.sh 在逾期或連續失敗筆數大於 0 時各列一筆待人處理,每一輪都列。 兩種都不會自己好:截止時間過了不會因為過更久就不逾期,連續失敗的項目 每一輪都會再試一次然後再失敗一次。「提過了」不是「處理過了」,而助理 不替人按暫停,那個狀態留給人設。 監控頁那一節逐筆點名連續失敗的那幾筆——id、失敗幾次、最後一次執行、 標題。原本只印一個總數,而失敗次數這個欄位存在的理由是指出「有一筆壞掉 的項目每輪重試而沒人知道」,不說出是哪一筆,那個理由只完成一半。 目錄頁刻意不加欄位。那一頁一台機器一個區塊、各自那一輪寫,新加一條只有 跑到新版的機器寫得出來,讀的人分不出「這台沒有逾期」與「這台還沒寫這 一條」——理由與當初把「本輪非 ok 事件數」擋在那一頁外面的一樣。 欄位數從 11 變 12,run-due.sh 那道欄位數檢核跟著改成 12。那道檢核擋的 不是第 12 欄本身,是「兩支不是同一版」——版本不同的時候,前 11 欄的 順序也不能假設還是原來那樣。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-assist",
|
"name": "jsc-assist",
|
||||||
"version": "0.3.3",
|
"version": "0.3.4",
|
||||||
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_{HASH} wiki 頁)",
|
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_{HASH} wiki 頁)",
|
||||||
"skills": "./skills",
|
"skills": "./skills",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-assist",
|
"name": "jsc-assist",
|
||||||
"version": "0.3.3",
|
"version": "0.3.4",
|
||||||
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_{HASH} wiki 頁)",
|
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_{HASH} wiki 頁)",
|
||||||
"skills": "./skills",
|
"skills": "./skills",
|
||||||
"jsc": {
|
"jsc": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jsc-assist",
|
"name": "jsc-assist",
|
||||||
"version": "0.3.3",
|
"version": "0.3.4",
|
||||||
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_{HASH} wiki 頁)",
|
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_{HASH} wiki 頁)",
|
||||||
"skills": "./skills/",
|
"skills": "./skills/",
|
||||||
"jsc": {
|
"jsc": {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -348,7 +348,9 @@ That property holds only while nothing fakes a heartbeat. **`write` is called by
|
|||||||
|
|
||||||
One round: read five sources, record the result, then beat. Everything before the heartbeat is read-only except the round's own scratch files. Ask nobody anything.
|
One round: read five sources, record the result, then beat. Everything before the heartbeat is read-only except the round's own scratch files. Ask nobody anything.
|
||||||
|
|
||||||
1. **Collect.** Run `{CURRENT}/jsc-assist/tools/patrol.sh collect --trigger 排程` (use `--trigger 手動` when a person asked for this round). That is the same split step 0 branched on: 排程 is the unattended round that read its root out of the invocation text, 手動 the round somebody asked for. Judge the exit code by the patrol.sh table. Exit 4 stands the round down — report the holder and its age from the printed `lock=busy` line, and stop; write no page and no heartbeat. Exit 5 and 6 stop the round the same way, with the code and the stderr text. Exit 0, 1 and 3 all carry on to step 2. Record `round=`, `lock_broken=`, `hash=`, `page=`, `verdict=`, `failed_sources=`, `warn_sources=`, `pending=`, every `item=` line, and the file paths `latest_file=`, `summary_file=`, `summary_row_file=`, `newpage_file=`, `contents_file=` and `due_rows_file=` — that last one is what step 5 has to be given, and an empty value there means the judging step produced no list, so step 5 has nothing to act on and says so rather than falling back to anything. Completion condition: the round id, the page name and the five file paths are recorded, or the stand-down or the failure was reported and the round stopped.
|
1. **Collect.** Run `{CURRENT}/jsc-assist/tools/patrol.sh collect --trigger 排程` (use `--trigger 手動` when a person asked for this round). That is the same split step 0 branched on: 排程 is the unattended round that read its root out of the invocation text, 手動 the round somebody asked for. Judge the exit code by the patrol.sh table. Exit 4 stands the round down — report the holder and its age from the printed `lock=busy` line, and stop; write no page and no heartbeat. Exit 5 and 6 stop the round the same way, with the code and the stderr text. Exit 0, 1 and 3 all carry on to step 2. Record `round=`, `lock_broken=`, `hash=`, `page=`, `verdict=`, `failed_sources=`, `warn_sources=`, `pending=`, `tasks_total=`, `tasks_failing=`, `tasks_due=`, `tasks_overdue=`, every `item=` line, and the file paths `latest_file=`, `summary_file=`, `summary_row_file=`, `newpage_file=`, `contents_file=` and `due_rows_file=` — that last one is what step 5 has to be given, and an empty value there means the judging step produced no list, so step 5 has nothing to act on and says so rather than falling back to anything. Completion condition: the round id, the page name and the five file paths are recorded, or the stand-down or the failure was reported and the round stopped.
|
||||||
|
|
||||||
|
**`tasks_overdue=` and `tasks_failing=` earn a 待人處理 row every round, and `collect` writes both of them itself.** Neither condition heals on its own: a deadline that has passed does not become un-passed, and an entry that fails retries next round and fails again. So the row is repeated every round rather than suppressed after the first — **"already reported" is not "already handled"**, and the assistant does not pause an entry on anybody's behalf; `paused` is a state a person sets and only a person clears. The overdue count comes from the judging step and is a dash when that step could not judge, which is not the same as zero. The monitor page carries the two named tables under 待辦簿到期與逾期: which entries are overdue and by how long, and which ones have been failing and how many times. **The directory page gains no field for either.** That page holds one block per machine, each written by that machine's own round, so a new field would only appear for machines already on the new version, and a reader could not tell "nothing overdue here" from "this machine has not written that field yet" — the same reason the 本輪非 ok 事件數 field was kept off it.
|
||||||
|
|
||||||
**The status event lines come out of the same call.** `collect` drained the stream and rotated it (see 「The status event stream」 above), so record `events_total=`, `events_bad=`, `events_unpaired=`, `events_running=`, `events_rotated=` and `events_file=` alongside the rest, and read `item=D-11` for whether that source was readable at all. The 執行狀態事件 subsection of `latest_file` already carries the two detail tables — the non-`ok` events and the starts with no matching end — so never rebuild either by hand and never call `report-status.sh` yourself: a second `drain` this round would either return exit 3 or eat events that then reach no page at all.
|
**The status event lines come out of the same call.** `collect` drained the stream and rotated it (see 「The status event stream」 above), so record `events_total=`, `events_bad=`, `events_unpaired=`, `events_running=`, `events_rotated=` and `events_file=` alongside the rest, and read `item=D-11` for whether that source was readable at all. The 執行狀態事件 subsection of `latest_file` already carries the two detail tables — the non-`ok` events and the starts with no matching end — so never rebuild either by hand and never call `report-status.sh` yourself: a second `drain` this round would either return exit 3 or eat events that then reach no page at all.
|
||||||
|
|
||||||
@@ -442,7 +444,7 @@ Read-only throughout. This operation creates, modifies and deletes nothing under
|
|||||||
|
|
||||||
Completion condition: every matching sentence is printed, or none of the four combinations applied.
|
Completion condition: every matching sentence is printed, or none of the four combinations applied.
|
||||||
|
|
||||||
6. **Flag the repeatedly failing tasks.** Append 已連續失敗 N 次 to every row whose `fail_count` is above 0, with `N` taken verbatim from the file. A broken entry that retries every round with nobody noticing is the reason this field exists, so let no such row leave the table unmarked. Completion condition: every row with `fail_count` above 0 carries the marker and its number matches the file.
|
6. **Flag the repeatedly failing and the overdue tasks.** Append 已連續失敗 N 次 to every row whose `fail_count` is above 0, with `N` taken verbatim from the file. A broken entry that retries every round with nobody noticing is the reason this field exists, so let no such row leave the table unmarked. Then run `{CURRENT}/jsc-assist/tools/due.sh overdue` and append 逾期 {human} to every row it names, with the wording taken from that tool's `human=` field. **Do not compare the `due` field against the clock yourself**: that would be a second judgement of the same thing, and the day the two disagree both will look right. That subcommand is read-only by construction — it advances no snapshot and compares no events, which is why `status` may call it while `scan` may not be called here at all. Exit 2 means at least one `due` field could not be parsed: report those as entries whose deadline was mistyped at registration, because **an unparseable deadline and no deadline look identical on screen**. A paused entry past its deadline is still overdue and is still named; being paused suspends the due judgement, not the calendar. Completion condition: every row with `fail_count` above 0 carries its marker, every row the overdue judgement named carries its own, and the numbers match what the two tools printed.
|
||||||
|
|
||||||
7. **Check the built-in items against the delegation list, read-only.** Run `{CURRENT}/jsc-assist/tools/seed-tasks.sh plan --root {CURRENT}`. `plan` writes nothing at all — it prints what a reconcile would do and stops — which is what makes it safe here, and `apply` must never be run from `status`. Judge the code by the seed-tasks.sh table and report the difference: the count of items the list expects but the task book lacks, the count of orphans the task book still holds, every `held=` row by name, every `drift=` row with the change the list asks for, every `pending=` row as a slice with an entry point still unwired — this is where a human finds out which reminders are waiting on plumbing rather than on them — and every `probe_bad=` row as an item that fell back to reminding. Say plainly that `start` is what applies any of it. On exit 1, 2 or 3 report that the comparison could not be made and why, and never present that as an aligned task book. Completion condition: the difference is reported with its counts and the held rows named, or the reason it could not be computed is reported, and nothing under `$JSC_HOME` was written.
|
7. **Check the built-in items against the delegation list, read-only.** Run `{CURRENT}/jsc-assist/tools/seed-tasks.sh plan --root {CURRENT}`. `plan` writes nothing at all — it prints what a reconcile would do and stops — which is what makes it safe here, and `apply` must never be run from `status`. Judge the code by the seed-tasks.sh table and report the difference: the count of items the list expects but the task book lacks, the count of orphans the task book still holds, every `held=` row by name, every `drift=` row with the change the list asks for, every `pending=` row as a slice with an entry point still unwired — this is where a human finds out which reminders are waiting on plumbing rather than on them — and every `probe_bad=` row as an item that fell back to reminding. Say plainly that `start` is what applies any of it. On exit 1, 2 or 3 report that the comparison could not be made and why, and never present that as an aligned task book. Completion condition: the difference is reported with its counts and the held rows named, or the reason it could not be computed is reported, and nothing under `$JSC_HOME` was written.
|
||||||
|
|
||||||
|
|||||||
+162
-23
@@ -8,8 +8,9 @@
|
|||||||
# [--created {ISO 時間}] [--last-run {ISO 時間}] [--fail-count {數字}]
|
# [--created {ISO 時間}] [--last-run {ISO 時間}] [--fail-count {數字}]
|
||||||
# [--now {epoch 秒}]
|
# [--now {epoch 秒}]
|
||||||
#
|
#
|
||||||
# scan 一輪一次:比對狀態快照算出本輪新事件、推進快照與事件計數,再逐筆判到期。
|
# scan 一輪一次:比對狀態快照算出本輪新事件、推進快照與事件計數,再逐筆判到期與逾期。
|
||||||
# events 唯讀預覽:只比對、只印,一律不推進快照,也不判到期。
|
# events 唯讀預覽:只比對、只印,一律不推進快照,也不判到期。
|
||||||
|
# overdue 只判逾期,唯讀:一份快照都不推進,一個事件都不比。狀態查詢那一路走這一個。
|
||||||
# next 純算:給一組欄位算出 next_run,不讀待辦簿、不碰快照、不發事件。
|
# next 純算:給一組欄位算出 next_run,不讀待辦簿、不碰快照、不發事件。
|
||||||
#
|
#
|
||||||
# 結束碼:
|
# 結束碼:
|
||||||
@@ -17,7 +18,8 @@
|
|||||||
# 1 至少一個狀態來源存在卻讀不到。**結果照樣印得出來**,只是那一個來源本輪不判事件、
|
# 1 至少一個狀態來源存在卻讀不到。**結果照樣印得出來**,只是那一個來源本輪不判事件、
|
||||||
# 快照那一段原樣留著,呼叫端要把它標成警示
|
# 快照那一段原樣留著,呼叫端要把它標成警示
|
||||||
# 2 欄位值不合法或算不出來:trigger 或 recur 解不開、時間字串解不出 epoch、間隔寫錯、
|
# 2 欄位值不合法或算不出來:trigger 或 recur 解不開、時間字串解不出 epoch、間隔寫錯、
|
||||||
# --now 不是非負整數。scan 遇到這種待辦只把那一筆標成判不了,其餘各筆照判
|
# due 欄的截止時間解不開、--now 不是非負整數。scan 遇到這種待辦只把那一筆標成判不了,
|
||||||
|
# 其餘各筆照判
|
||||||
# 3 快照或事件計數換不上去。算是算出來了,但下一輪會拿同一份舊快照再比一次,於是同一批
|
# 3 快照或事件計數換不上去。算是算出來了,但下一輪會拿同一份舊快照再比一次,於是同一批
|
||||||
# 事件會被判第二次。這一碼要吵出來,不能當成成功
|
# 事件會被判第二次。這一碼要吵出來,不能當成成功
|
||||||
# 4 待辦簿目錄不存在或零筆,這一輪沒有任何一筆可判。不是失敗,但呼叫端要知道「沒判過」
|
# 4 待辦簿目錄不存在或零筆,這一輪沒有任何一筆可判。不是失敗,但呼叫端要知道「沒判過」
|
||||||
@@ -139,8 +141,13 @@
|
|||||||
#
|
#
|
||||||
# --- 這一支不判什麼 ---
|
# --- 這一支不判什麼 ---
|
||||||
#
|
#
|
||||||
# 不判逾期(due 欄有沒有過),不執行任何一筆待辦,不改任何一筆的 state,不送提醒到前景,不碰 wiki。
|
# 不執行任何一筆待辦,不改任何一筆的 state,不送提醒到前景,不碰 wiki。
|
||||||
# 它只回答兩個問題:本輪有哪些新事件、哪幾筆現在到期。做不做、怎麼做、失敗怎麼記,都在呼叫端。
|
# 它只回答三個問題:本輪有哪些新事件、哪幾筆現在到期、哪幾筆逾期了。做不做、怎麼做、失敗怎麼記,
|
||||||
|
# 都在呼叫端。
|
||||||
|
#
|
||||||
|
# 到期與逾期分兩欄,不合成一個判定:到期是「該跑了」,逾期是「due 欄那個截止時間過了」。
|
||||||
|
# 一筆七天一次的檢查項天天都可能到期,卻永遠不會逾期(它沒有截止時間);一筆有截止時間的交辦
|
||||||
|
# 可能早就逾期,卻因為排定點還沒到而判成「還沒到」。合成一個值就會把這兩種情況混成同一格。
|
||||||
#
|
#
|
||||||
# 環境變數:
|
# 環境變數:
|
||||||
# JSC_HOME 助理狀態檔的根目錄,預設 ~/.jsc。要是連 HOME 也沒有就回 6,不猜
|
# JSC_HOME 助理狀態檔的根目錄,預設 ~/.jsc。要是連 HOME 也沒有就回 6,不猜
|
||||||
@@ -195,8 +202,9 @@ note() { printf '[jsc][助理到期判定]:%s\n' "$1" >&2; }
|
|||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat >&2 <<'EOF'
|
cat >&2 <<'EOF'
|
||||||
usage: due.sh scan [--out 目錄] [--now epoch 秒] [--dry-run]
|
usage: due.sh scan [--out 目錄] [--now epoch 秒] [--dry-run]
|
||||||
due.sh events [--now epoch 秒]
|
due.sh events [--now epoch 秒]
|
||||||
|
due.sh overdue [--now epoch 秒]
|
||||||
due.sh next --trigger at:...|after:... --recur once|every:間隔
|
due.sh next --trigger at:...|after:... --recur once|every:間隔
|
||||||
[--created ISO 時間] [--last-run ISO 時間] [--fail-count 數字]
|
[--created ISO 時間] [--last-run ISO 時間] [--fail-count 數字]
|
||||||
[--now epoch 秒]
|
[--now epoch 秒]
|
||||||
@@ -869,15 +877,51 @@ kv_get() { # $1=檔案 $2=鍵
|
|||||||
# 不寫成 key=value:cron 式子與標題裡有空白,key=value 的那一行沒辦法只靠空白切回來,切錯就是
|
# 不寫成 key=value:cron 式子與標題裡有空白,key=value 的那一行沒辦法只靠空白切回來,切錯就是
|
||||||
# recur 只剩「cron:0」那半截,而那半截看起來還很像一個完整的值。值一律折過,裡面不會有定位
|
# recur 只剩「cron:0」那半截,而那半截看起來還很像一個完整的值。值一律折過,裡面不會有定位
|
||||||
# 字元也不會有換行,所以定位字元切得準。
|
# 字元也不會有換行,所以定位字元切得準。
|
||||||
row_out() { # 十一個欄位,順序如上
|
row_out() { # 十二個欄位,順序如上
|
||||||
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
||||||
"$(flat "$1")" "$(flat "$2")" "$(flat "$3")" "$(flat "$4")" "$(flat "$5")" \
|
"$(flat "$1")" "$(flat "$2")" "$(flat "$3")" "$(flat "$4")" "$(flat "$5")" \
|
||||||
"$(flat "$6")" "$(flat "$7")" "$(flat "$8")" "$(flat "$9")" \
|
"$(flat "$6")" "$(flat "$7")" "$(flat "$8")" "$(flat "$9")" \
|
||||||
"$(flat "${10}")" "$(flat "${11}")" >>"$ROWS"
|
"$(flat "${10}")" "$(flat "${11}")" "$(flat "${12}")" >>"$ROWS"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 逾期判定。到期與逾期是兩件事:到期是「該跑了」,逾期是「截止時間過了」。
|
||||||
|
# 一筆七天一次的檢查項天天都可能到期,卻永遠不會逾期(它沒有截止時間);一筆有截止時間的
|
||||||
|
# 交辦事項可能早就逾期,卻因為排定點還沒到而判成「還沒到」。兩個判定各自獨立,一欄一個值。
|
||||||
|
#
|
||||||
|
# 回傳三種值:due 欄是空的印減號、還沒過印 no、過了印逾期的秒數。
|
||||||
|
# 印秒數不印 yes 的理由是「逾期一分鐘」與「逾期三個月」在報告上要看得出差別,而呼叫端
|
||||||
|
# 只要判「是不是數字」就分得出有沒有逾期。
|
||||||
|
# due 欄解不開的一律印 bad 並算成欄位不合法:一個解不開的截止時間跟沒有截止時間,
|
||||||
|
# 在畫面上長得一模一樣,而前者是登錄時就填錯了。
|
||||||
|
# 逾期多久,給人看的寫法。機器可讀的那幾處一律留秒數:秒數比得出大小、也不必解析。
|
||||||
|
# 頁面上只印秒數讀不出輕重——「528462 秒」要算過才知道是六天,而報告的用途正是讓人一眼
|
||||||
|
# 分出「逾期一分鐘」與「逾期三個月」。
|
||||||
|
dur_human() { # $1=秒數
|
||||||
|
_ds=$(( $1 / 86400 )); _hs=$(( ($1 % 86400) / 3600 )); _ms=$(( ($1 % 3600) / 60 ))
|
||||||
|
if [ "$_ds" -gt 0 ]; then printf '%s 天 %s 小時' "$_ds" "$_hs"
|
||||||
|
elif [ "$_hs" -gt 0 ]; then printf '%s 小時 %s 分' "$_hs" "$_ms"
|
||||||
|
else printf '%s 分' "$_ms"; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
overdue_of() { # $1=due 欄的值;回傳值印在 stdout
|
||||||
|
case "$1" in
|
||||||
|
''|'-') printf '%s' '-'; return 0 ;;
|
||||||
|
esac
|
||||||
|
_de=$(iso_to_epoch "$1") || _de=''
|
||||||
|
case "${_de:-}" in
|
||||||
|
''|*[!0-9]*) printf '%s' 'bad'; return 1 ;;
|
||||||
|
esac
|
||||||
|
if [ "$NOW" -gt "$_de" ]; then
|
||||||
|
printf '%s' "$((NOW - _de))"
|
||||||
|
else
|
||||||
|
printf '%s' 'no'
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
scan_tasks() {
|
scan_tasks() {
|
||||||
T_TOTAL=0; T_DUE=0; T_WAIT=0; T_INVALID=0; T_UNWIRED=0; T_SKIP=0
|
T_TOTAL=0; T_DUE=0; T_WAIT=0; T_INVALID=0; T_UNWIRED=0; T_SKIP=0
|
||||||
|
T_OVERDUE=0; T_DUEBAD=0; OVERDUE_LINES=''
|
||||||
true >"$ROWS" 2>/dev/null || { RC_FS=5; die 5 "逐筆判定的輸出檔寫不進去:$ROWS。"; }
|
true >"$ROWS" 2>/dev/null || { RC_FS=5; die 5 "逐筆判定的輸出檔寫不進去:$ROWS。"; }
|
||||||
if [ ! -d "$TASKS_DIR" ]; then
|
if [ ! -d "$TASKS_DIR" ]; then
|
||||||
RC_EMPTY=4
|
RC_EMPTY=4
|
||||||
@@ -898,11 +942,28 @@ scan_tasks() {
|
|||||||
_cr=$(kv_get "$_f" created)
|
_cr=$(kv_get "$_f" created)
|
||||||
_lr=$(kv_get "$_f" last_run)
|
_lr=$(kv_get "$_f" last_run)
|
||||||
_fc=$(kv_get "$_f" fail_count)
|
_fc=$(kv_get "$_f" fail_count)
|
||||||
|
_du=$(kv_get "$_f" due)
|
||||||
|
|
||||||
|
# 逾期照每一筆都判,連 paused 那幾筆也判。到期判定跳過 paused 是對的(那是人設的,
|
||||||
|
# 助理判它到期只會讓人以為它還在跑),但截止時間過了這件事不會因為有人按了暫停就消失
|
||||||
|
# ——一筆被暫停又逾期的交辦,正好是最容易被忘掉的那一種。
|
||||||
|
# done 那幾筆不算逾期:做完了就沒有截止時間可以過。
|
||||||
|
_ov='-'
|
||||||
|
if [ "$_st" != done ]; then
|
||||||
|
_ov=$(overdue_of "$_du") || { T_DUEBAD=$(( T_DUEBAD + 1 )); RC_FIELD=2; }
|
||||||
|
case "$_ov" in
|
||||||
|
''|*[!0-9]*) ;;
|
||||||
|
*) T_OVERDUE=$(( T_OVERDUE + 1 ))
|
||||||
|
OVERDUE_LINES="${OVERDUE_LINES}$(printf '%s\t%s\t%s\t%s\t%s' \
|
||||||
|
"$(flat "$_id")" "$(flat "$_st")" "$(flat "$_du")" "$_ov" "$(flat "$_ti")")
|
||||||
|
" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$_st" != pending ]; then
|
if [ "$_st" != pending ]; then
|
||||||
# done 與 paused 一律不判。paused 是人設的,助理判它到期只會讓人以為它還在跑。
|
# done 與 paused 一律不判到期。
|
||||||
T_SKIP=$(( T_SKIP + 1 ))
|
T_SKIP=$(( T_SKIP + 1 ))
|
||||||
row_out "$_id" skip "$_st" "$_kd" "$_ac" '' '' '' '' 'state 不是 pending,不判到期' "$_ti"
|
row_out "$_id" skip "$_st" "$_kd" "$_ac" '' '' '' '' 'state 不是 pending,不判到期' "$_ti" "$_ov"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -914,7 +975,7 @@ scan_tasks() {
|
|||||||
unwired) T_UNWIRED=$(( T_UNWIRED + 1 )) ;;
|
unwired) T_UNWIRED=$(( T_UNWIRED + 1 )) ;;
|
||||||
esac
|
esac
|
||||||
row_out "$_id" "$R_VERDICT" "$_st" "$_kd" "$_ac" "$_tg" "$_rc" \
|
row_out "$_id" "$R_VERDICT" "$_st" "$_kd" "$_ac" "$_tg" "$_rc" \
|
||||||
"$R_NEXT" "$R_REARM" "$R_WHY" "$_ti"
|
"$R_NEXT" "$R_REARM" "$R_WHY" "$_ti" "$_ov"
|
||||||
done
|
done
|
||||||
[ "$T_TOTAL" -eq 0 ] && {
|
[ "$T_TOTAL" -eq 0 ] && {
|
||||||
RC_EMPTY=4
|
RC_EMPTY=4
|
||||||
@@ -931,7 +992,7 @@ compose_md() {
|
|||||||
printf '事件靠比對狀態快照偵測:每一輪讀那幾支產生者留下的狀態,跟上一輪的快照比,有差別就算事件發生。快照在 `%s`,事件計數在 `%s`。\n\n' \
|
printf '事件靠比對狀態快照偵測:每一輪讀那幾支產生者留下的狀態,跟上一輪的快照比,有差別就算事件發生。快照在 `%s`,事件計數在 `%s`。\n\n' \
|
||||||
"$PREV" "$SEEN"
|
"$PREV" "$SEEN"
|
||||||
printf '**兩輪之間發生又消失的事件會漏掉。** 一支工作包在同一個巡檢週期之內鎖上又合併、一個工作階段在同一個週期之內開又關,這一節都看不到——看到的只有兩張快照的差別。往後誰假設「事件不會漏」就會出錯,而那種錯是無聲的:那筆待辦看起來就是還沒到期。\n\n'
|
printf '**兩輪之間發生又消失的事件會漏掉。** 一支工作包在同一個巡檢週期之內鎖上又合併、一個工作階段在同一個週期之內開又關,這一節都看不到——看到的只有兩張快照的差別。往後誰假設「事件不會漏」就會出錯,而那種錯是無聲的:那筆待辦看起來就是還沒到期。\n\n'
|
||||||
printf '這一節不判逾期(`due` 欄有沒有過),也不執行任何一筆。到期的那幾筆要不要做、怎麼做,在呼叫端。\n\n'
|
printf '到期與逾期是兩件事,這一節兩件都判:到期是「該跑了」,逾期是「`due` 欄那個截止時間過了」。一筆七天一次的檢查項天天都可能到期,卻永遠不會逾期;一筆有截止時間的交辦可能早就逾期,卻因為排定點還沒到而判成還沒到。這一節不執行任何一筆——到期的那幾筆要不要做、怎麼做,在呼叫端。\n\n'
|
||||||
printf '| 項目 | 內容 |\n'
|
printf '| 項目 | 內容 |\n'
|
||||||
printf '| --- | --- |\n'
|
printf '| --- | --- |\n'
|
||||||
printf '| 判定時間 | %s |\n' "$(epoch_to_iso "$NOW")"
|
printf '| 判定時間 | %s |\n' "$(epoch_to_iso "$NOW")"
|
||||||
@@ -943,10 +1004,12 @@ compose_md() {
|
|||||||
printf '| 本輪新事件 | %s 種 |\n' "$EV_NEW"
|
printf '| 本輪新事件 | %s 種 |\n' "$EV_NEW"
|
||||||
printf '| 待辦簿筆數 | %s |\n' "$T_TOTAL"
|
printf '| 待辦簿筆數 | %s |\n' "$T_TOTAL"
|
||||||
printf '| 到期 | %s |\n' "$T_DUE"
|
printf '| 到期 | %s |\n' "$T_DUE"
|
||||||
|
printf '| 逾期 | %s |\n' "$T_OVERDUE"
|
||||||
printf '| 還沒到 | %s |\n' "$T_WAIT"
|
printf '| 還沒到 | %s |\n' "$T_WAIT"
|
||||||
printf '| 判不了(欄位不合法) | %s |\n' "$T_INVALID"
|
printf '| 判不了(欄位不合法) | %s |\n' "$T_INVALID"
|
||||||
printf '| 判不了(來源還沒接線) | %s |\n' "$T_UNWIRED"
|
printf '| 判不了(來源還沒接線) | %s |\n' "$T_UNWIRED"
|
||||||
printf '| 不判(state 不是 pending) | %s |\n' "$T_SKIP"
|
printf '| 不判(state 不是 pending) | %s |\n' "$T_SKIP"
|
||||||
|
[ "$T_DUEBAD" -gt 0 ] && printf '| `due` 欄解不開 | %s |\n' "$T_DUEBAD"
|
||||||
|
|
||||||
printf '\n#### 狀態來源\n\n'
|
printf '\n#### 狀態來源\n\n'
|
||||||
printf '| 來源 | 狀態 | 筆數 | 說明 |\n'
|
printf '| 來源 | 狀態 | 筆數 | 說明 |\n'
|
||||||
@@ -975,19 +1038,36 @@ compose_md() {
|
|||||||
printf '本輪沒有新事件。\n'
|
printf '本輪沒有新事件。\n'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
printf '\n#### 逾期\n\n'
|
||||||
|
if [ "$T_OVERDUE" -gt 0 ]; then
|
||||||
|
printf '**這幾筆的截止時間過了。逾期每一輪都提,不因為提過就不再提**——助理只提醒,不代為執行,也不自己把它移出這一節。\n\n'
|
||||||
|
printf '| id | state | 截止時間 | 逾期多久 | 標題 |\n'
|
||||||
|
printf '| --- | --- | --- | ---: | --- |\n'
|
||||||
|
printf '%s' "$OVERDUE_LINES" | while IFS="$(printf '\t')" read -r _oi _os _od _oa _ot; do
|
||||||
|
[ -n "$_oi" ] || continue
|
||||||
|
printf '| `%s` | %s | %s | %s | %s |\n' "$_oi" "$_os" "$(cell "$_od")" "$(dur_human "$_oa")" "$(cell "${_ot:--}")"
|
||||||
|
done
|
||||||
|
printf '\n被暫停的那幾筆也算在裡面:截止時間過了這件事不會因為有人按了暫停就消失,而一筆被暫停又逾期的交辦正好是最容易被忘掉的那一種。\n'
|
||||||
|
else
|
||||||
|
printf '沒有逾期的待辦。`due` 欄是空的那幾筆沒有截止時間,永遠不會逾期。\n'
|
||||||
|
fi
|
||||||
|
if [ "$T_DUEBAD" -gt 0 ]; then
|
||||||
|
printf '\n有 %s 筆的 `due` 欄解不開,上表 `overdue` 那一欄是 `bad`。**一個解不開的截止時間跟沒有截止時間,在畫面上長得一模一樣**,而前者是登錄的時候就填錯了:重新登錄一筆,`id` 由建立時間與標題算出來,改欄位值算不回同一個 id。\n' "$T_DUEBAD"
|
||||||
|
fi
|
||||||
|
|
||||||
printf '\n#### 逐筆判定\n\n'
|
printf '\n#### 逐筆判定\n\n'
|
||||||
if [ -s "$ROWS" ]; then
|
if [ -s "$ROWS" ]; then
|
||||||
printf '| id | 判定 | 標題 | 動作 | trigger | recur | next_run | 重新武裝 | 為什麼 |\n'
|
printf '| id | 判定 | 逾期 | 標題 | 動作 | trigger | recur | next_run | 重新武裝 | 為什麼 |\n'
|
||||||
printf '| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n'
|
printf '| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n'
|
||||||
# 這一段交給 awk 一次做完,不用殼的 while read 逐列切。殼的 read 拿定位字元當分隔時會
|
# 這一段交給 awk 一次做完,不用殼的 while read 逐列切。殼的 read 拿定位字元當分隔時會
|
||||||
# 把連續的定位字元併成一個——定位字元算 IFS 的空白字元——於是空欄位整個消失,後面每一欄
|
# 把連續的定位字元併成一個——定位字元算 IFS 的空白字元——於是空欄位整個消失,後面每一欄
|
||||||
# 都往前挪一格,印出來的表格看起來完整,值卻全錯位。awk 的 -F'\t' 不併,空欄位就是空欄位。
|
# 都往前挪一格,印出來的表格看起來完整,值卻全錯位。awk 的 -F'\t' 不併,空欄位就是空欄位。
|
||||||
awk -F'\t' '
|
awk -F'\t' '
|
||||||
function d(s) { return s == "" ? "-" : s }
|
function d(s) { return s == "" ? "-" : s }
|
||||||
function esc(s) { gsub(/\|/, "\\|", s); return s }
|
function esc(s) { gsub(/\|/, "\\|", s); return s }
|
||||||
NF >= 11 && $1 != "" {
|
NF >= 12 && $1 != "" {
|
||||||
printf "| `%s` | %s | %s | %s | `%s` | `%s` | %s | %s | %s |\n", \
|
printf "| `%s` | %s | %s | %s | %s | `%s` | `%s` | %s | %s | %s |\n", \
|
||||||
$1, $2, esc(d($11)), esc(d($5)), d($6), d($7), d($8), d($9), esc(d($10))
|
$1, $2, d($12), esc(d($11)), esc(d($5)), d($6), d($7), d($8), d($9), esc(d($10))
|
||||||
}' "$ROWS"
|
}' "$ROWS"
|
||||||
else
|
else
|
||||||
printf '待辦簿零筆,或目錄不存在。這一輪沒有任何一筆可判——「沒判過」不等於「都沒到期」。\n'
|
printf '待辦簿零筆,或目錄不存在。這一輪沒有任何一筆可判——「沒判過」不等於「都沒到期」。\n'
|
||||||
@@ -1042,9 +1122,9 @@ cmd_scan() {
|
|||||||
# stdout 上的 task= 那幾行是給人看的。trigger、recur、why、title 四欄都可能帶空白,所以這
|
# stdout 上的 task= 那幾行是給人看的。trigger、recur、why、title 四欄都可能帶空白,所以這
|
||||||
# 幾行切不回欄位——要逐筆取值的呼叫端讀 rows_file 那個定位字元分隔的檔案,不要切這幾行。
|
# 幾行切不回欄位——要逐筆取值的呼叫端讀 rows_file 那個定位字元分隔的檔案,不要切這幾行。
|
||||||
if [ -s "$ROWS" ]; then
|
if [ -s "$ROWS" ]; then
|
||||||
awk -F'\t' 'NF >= 11 && $1 != "" {
|
awk -F'\t' 'NF >= 12 && $1 != "" {
|
||||||
printf "task=%s verdict=%s state=%s kind=%s action=%s next_run=%s rearm_at=%s trigger=%s recur=%s why=%s title=%s\n", \
|
printf "task=%s verdict=%s state=%s kind=%s action=%s next_run=%s rearm_at=%s trigger=%s recur=%s overdue=%s why=%s title=%s\n", \
|
||||||
$1, $2, $3, $4, $5, $8, $9, $6, $7, $10, $11
|
$1, $2, $3, $4, $5, $8, $9, $6, $7, $12, $10, $11
|
||||||
}' "$ROWS"
|
}' "$ROWS"
|
||||||
fi
|
fi
|
||||||
printf 'tasks_total=%s\n' "$T_TOTAL"
|
printf 'tasks_total=%s\n' "$T_TOTAL"
|
||||||
@@ -1053,8 +1133,18 @@ cmd_scan() {
|
|||||||
printf 'tasks_invalid=%s\n' "$T_INVALID"
|
printf 'tasks_invalid=%s\n' "$T_INVALID"
|
||||||
printf 'tasks_unwired=%s\n' "$T_UNWIRED"
|
printf 'tasks_unwired=%s\n' "$T_UNWIRED"
|
||||||
printf 'tasks_skipped=%s\n' "$T_SKIP"
|
printf 'tasks_skipped=%s\n' "$T_SKIP"
|
||||||
|
printf 'tasks_overdue=%s\n' "$T_OVERDUE"
|
||||||
|
printf 'tasks_due_bad=%s\n' "$T_DUEBAD"
|
||||||
|
# 逾期的那幾筆逐筆點名,不只印一個數字。一份只有數字的回報讀不出「是哪一筆過期了」,
|
||||||
|
# 而那正是唯一有用的資訊;逾期的處置一律是人接手,人要知道接哪一筆。
|
||||||
|
if [ -n "$OVERDUE_LINES" ]; then
|
||||||
|
printf '%s' "$OVERDUE_LINES" | while IFS="$(printf '\t')" read -r _oi _os _od _oa _ot; do
|
||||||
|
[ -n "$_oi" ] || continue
|
||||||
|
printf 'overdue_row=%s state=%s due=%s overdue_secs=%s title=%s\n' "$_oi" "$_os" "$_od" "$_oa" "$_ot"
|
||||||
|
done
|
||||||
|
fi
|
||||||
printf 'rows_file=%s\n' "$ROWS"
|
printf 'rows_file=%s\n' "$ROWS"
|
||||||
printf 'rows_columns=id verdict state kind action trigger recur next_run rearm_at why title\n'
|
printf 'rows_columns=id verdict state kind action trigger recur next_run rearm_at why title overdue\n'
|
||||||
printf 'due_file=%s\n' "$MD"
|
printf 'due_file=%s\n' "$MD"
|
||||||
printf 'prev_file=%s\n' "$PREV"
|
printf 'prev_file=%s\n' "$PREV"
|
||||||
printf 'seen_file=%s\n' "$SEEN"
|
printf 'seen_file=%s\n' "$SEEN"
|
||||||
@@ -1062,6 +1152,53 @@ cmd_scan() {
|
|||||||
finish_rc
|
finish_rc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 只判逾期,一律唯讀。
|
||||||
|
#
|
||||||
|
# 為什麼另開一個子命令:逾期判定跟事件與到期判定不同,它一個狀態都不必比、一份快照都不必推進
|
||||||
|
# ——只要把每一筆的 due 欄跟現在比一下。狀態查詢那一路每次都想知道「有沒有逾期的」,走 scan
|
||||||
|
# 就會推進快照,同一輪之後的事件判定會全部落空;走 events 又判不到逾期。
|
||||||
|
# 讓呼叫端自己拿 due 欄去比更糟:那就是第二套判定,跟這一支的規則會漂移,而漂移的那一天
|
||||||
|
# 兩邊都說自己是對的。
|
||||||
|
cmd_overdue() {
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--now) [ "$#" -ge 2 ] || usage; NOW_ARG="$2"; shift 2 ;;
|
||||||
|
*) usage ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
resolve_now
|
||||||
|
T_TOTAL=0; T_OVERDUE=0; T_DUEBAD=0; OVERDUE_LINES=''
|
||||||
|
if [ ! -d "$TASKS_DIR" ]; then
|
||||||
|
RC_EMPTY=4
|
||||||
|
note "待辦簿目錄不存在($TASKS_DIR),這一輪沒有任何一筆可判。「沒判過」不等於「都沒逾期」。"
|
||||||
|
else
|
||||||
|
for _f in "$TASKS_DIR"/*; do
|
||||||
|
[ -f "$_f" ] || continue
|
||||||
|
T_TOTAL=$(( T_TOTAL + 1 ))
|
||||||
|
_id=$(kv_get "$_f" id); [ -n "$_id" ] || _id=$(basename "$_f")
|
||||||
|
_st=$(kv_get "$_f" state); [ -n "$_st" ] || _st=pending
|
||||||
|
_ti=$(kv_get "$_f" title)
|
||||||
|
_du=$(kv_get "$_f" due)
|
||||||
|
[ "$_st" = done ] && continue
|
||||||
|
_ov=$(overdue_of "$_du") || { T_DUEBAD=$(( T_DUEBAD + 1 )); RC_FIELD=2; }
|
||||||
|
case "$_ov" in
|
||||||
|
''|*[!0-9]*) continue ;;
|
||||||
|
esac
|
||||||
|
T_OVERDUE=$(( T_OVERDUE + 1 ))
|
||||||
|
printf 'overdue_row=%s state=%s due=%s overdue_secs=%s human=%s title=%s\n' \
|
||||||
|
"$_id" "$_st" "$_du" "$_ov" "$(dur_human "$_ov")" "$_ti"
|
||||||
|
done
|
||||||
|
[ "$T_TOTAL" -eq 0 ] && { RC_EMPTY=4; note "待辦簿目錄在($TASKS_DIR),但零筆。"; }
|
||||||
|
fi
|
||||||
|
printf 'now=%s\n' "$(epoch_to_iso "$NOW")"
|
||||||
|
printf 'tasks_total=%s\n' "$T_TOTAL"
|
||||||
|
printf 'tasks_overdue=%s\n' "$T_OVERDUE"
|
||||||
|
printf 'tasks_due_bad=%s\n' "$T_DUEBAD"
|
||||||
|
note '這是唯讀判定:一份快照都沒推進,一筆待辦都沒改。逾期的處置一律是人接手。'
|
||||||
|
cleanup_tmp
|
||||||
|
finish_rc
|
||||||
|
}
|
||||||
|
|
||||||
cmd_events() {
|
cmd_events() {
|
||||||
while [ "$#" -gt 0 ]; do
|
while [ "$#" -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -1149,6 +1286,7 @@ FIRST_RUN=0
|
|||||||
EV_NAMES=''
|
EV_NAMES=''
|
||||||
EV_NEW=0
|
EV_NEW=0
|
||||||
T_TOTAL=0; T_DUE=0; T_WAIT=0; T_INVALID=0; T_UNWIRED=0; T_SKIP=0
|
T_TOTAL=0; T_DUE=0; T_WAIT=0; T_INVALID=0; T_UNWIRED=0; T_SKIP=0
|
||||||
|
T_OVERDUE=0; T_DUEBAD=0; OVERDUE_LINES=''
|
||||||
|
|
||||||
CMD="${1:-}"
|
CMD="${1:-}"
|
||||||
[ -n "$CMD" ] || usage
|
[ -n "$CMD" ] || usage
|
||||||
@@ -1156,6 +1294,7 @@ shift
|
|||||||
case "$CMD" in
|
case "$CMD" in
|
||||||
scan) cmd_scan "$@" ;;
|
scan) cmd_scan "$@" ;;
|
||||||
events) cmd_events "$@" ;;
|
events) cmd_events "$@" ;;
|
||||||
|
overdue) cmd_overdue "$@" ;;
|
||||||
next) cmd_next "$@" ;;
|
next) cmd_next "$@" ;;
|
||||||
*) usage ;;
|
*) usage ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
+42
-3
@@ -148,6 +148,7 @@
|
|||||||
# warn_sources= 本輪的警示來源,以「、」分隔;沒有警示就是「無」。各項全過卻判成警示
|
# warn_sources= 本輪的警示來源,以「、」分隔;沒有警示就是「無」。各項全過卻判成警示
|
||||||
# 時,原因只寫在這裡
|
# 時,原因只寫在這裡
|
||||||
# tasks_total= tasks_failing= 待辦簿筆數與連續失敗筆數,供目錄頁那一個區塊與摘要用
|
# tasks_total= tasks_failing= 待辦簿筆數與連續失敗筆數,供目錄頁那一個區塊與摘要用
|
||||||
|
# tasks_overdue= 逾期筆數(截止時間已經過了),取自到期判定那一支;判不出來時是減號
|
||||||
# tasks_due= 本輪到期的筆數;到期判定那一支失敗時為空
|
# tasks_due= 本輪到期的筆數;到期判定那一支失敗時為空
|
||||||
# events_new= 本輪偵測到的新事件種類數;到期判定那一支失敗時為空
|
# events_new= 本輪偵測到的新事件種類數;到期判定那一支失敗時為空
|
||||||
# due_status= due_rc= 到期判定那一支的結果與結束碼
|
# due_status= due_rc= 到期判定那一支的結果與結束碼
|
||||||
@@ -209,6 +210,8 @@ DUE_ROWS=''
|
|||||||
DUE_NOTE=''
|
DUE_NOTE=''
|
||||||
DUE_TASKS=''
|
DUE_TASKS=''
|
||||||
DUE_EVENTS=''
|
DUE_EVENTS=''
|
||||||
|
DUE_OVERDUE=''
|
||||||
|
FAILING_LINES=''
|
||||||
|
|
||||||
# 這支腳本是不是從 $JSC_HOME/current 那一組路徑被叫起來的。不是就大聲警告,但照跑。
|
# 這支腳本是不是從 $JSC_HOME/current 那一組路徑被叫起來的。不是就大聲警告,但照跑。
|
||||||
# 只警告、不中止是刻意的取捨:從工作樹直接跑腳本是開發時的正當用法,中止會把那條路擋掉;
|
# 只警告、不中止是刻意的取捨:從工作樹直接跑腳本是開發時的正當用法,中止會把那條路擋掉;
|
||||||
@@ -875,7 +878,7 @@ d11() {
|
|||||||
# --- 待辦簿筆數(只供目錄頁那一個區塊用)---
|
# --- 待辦簿筆數(只供目錄頁那一個區塊用)---
|
||||||
|
|
||||||
count_tasks() {
|
count_tasks() {
|
||||||
TASKS_TOTAL=0; TASKS_FAILING=0
|
TASKS_TOTAL=0; TASKS_FAILING=0; FAILING_LINES=''
|
||||||
_d="$STATE_DIR/tasks"
|
_d="$STATE_DIR/tasks"
|
||||||
[ -d "$_d" ] && [ -r "$_d" ] || return 0
|
[ -d "$_d" ] && [ -r "$_d" ] || return 0
|
||||||
for _f in "$_d"/*; do
|
for _f in "$_d"/*; do
|
||||||
@@ -883,7 +886,19 @@ count_tasks() {
|
|||||||
TASKS_TOTAL=$(( TASKS_TOTAL + 1 ))
|
TASKS_TOTAL=$(( TASKS_TOTAL + 1 ))
|
||||||
_fc=$(sed -n 's/^fail_count=//p' "$_f" 2>/dev/null | head -n1)
|
_fc=$(sed -n 's/^fail_count=//p' "$_f" 2>/dev/null | head -n1)
|
||||||
case "$_fc" in ''|*[!0-9]*) _fc=0 ;; esac
|
case "$_fc" in ''|*[!0-9]*) _fc=0 ;; esac
|
||||||
[ "$_fc" -gt 0 ] && TASKS_FAILING=$(( TASKS_FAILING + 1 ))
|
if [ "$_fc" -gt 0 ]; then
|
||||||
|
TASKS_FAILING=$(( TASKS_FAILING + 1 ))
|
||||||
|
# 逐筆點名,不只累加。一個數字說得出「有幾筆在連續失敗」,說不出是哪幾筆,而失敗次數
|
||||||
|
# 這個欄位存在的理由是指出「有一筆壞掉的項目每輪重試而沒人知道」——不說出是哪一筆,
|
||||||
|
# 那個理由就只完成了一半。
|
||||||
|
_ti=$(sed -n 's/^title=//p' "$_f" 2>/dev/null | head -n1)
|
||||||
|
_id=$(sed -n 's/^id=//p' "$_f" 2>/dev/null | head -n1)
|
||||||
|
[ -n "$_id" ] || _id=$(basename -- "$_f")
|
||||||
|
_lr=$(sed -n 's/^last_run=//p' "$_f" 2>/dev/null | head -n1)
|
||||||
|
FAILING_LINES="${FAILING_LINES}$(printf '%s\t%s\t%s\t%s' \
|
||||||
|
"$_id" "$_fc" "${_lr:--}" "${_ti:--}")
|
||||||
|
"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -920,6 +935,7 @@ due_scan() {
|
|||||||
DUE_ROWS=$(sed -n 's/^rows_file=//p' "$RD/due.out" 2>/dev/null | head -n1)
|
DUE_ROWS=$(sed -n 's/^rows_file=//p' "$RD/due.out" 2>/dev/null | head -n1)
|
||||||
DUE_TASKS=$(sed -n 's/^tasks_due=//p' "$RD/due.out" 2>/dev/null | head -n1)
|
DUE_TASKS=$(sed -n 's/^tasks_due=//p' "$RD/due.out" 2>/dev/null | head -n1)
|
||||||
DUE_EVENTS=$(sed -n 's/^events_new=//p' "$RD/due.out" 2>/dev/null | head -n1)
|
DUE_EVENTS=$(sed -n 's/^events_new=//p' "$RD/due.out" 2>/dev/null | head -n1)
|
||||||
|
DUE_OVERDUE=$(sed -n 's/^tasks_overdue=//p' "$RD/due.out" 2>/dev/null | head -n1)
|
||||||
case "$_rc" in
|
case "$_rc" in
|
||||||
0) DUE_STATUS=ok ;;
|
0) DUE_STATUS=ok ;;
|
||||||
4)
|
4)
|
||||||
@@ -997,8 +1013,19 @@ compose() {
|
|||||||
cat "$RD/d07.md"; printf '\n'
|
cat "$RD/d07.md"; printf '\n'
|
||||||
if [ -n "$DUE_MD" ] && [ -s "$DUE_MD" ]; then
|
if [ -n "$DUE_MD" ] && [ -s "$DUE_MD" ]; then
|
||||||
cat "$DUE_MD"
|
cat "$DUE_MD"
|
||||||
printf '\n本輪待辦簿共 %s 筆,其中 %s 筆 `fail_count` 大於 0。逾期(`due` 欄已經過了)還沒實作,上面的判定只講到期,不講逾期。\n\n' \
|
printf '\n本輪待辦簿共 %s 筆,其中 %s 筆 `fail_count` 大於 0。\n\n' \
|
||||||
"$TASKS_TOTAL" "$TASKS_FAILING"
|
"$TASKS_TOTAL" "$TASKS_FAILING"
|
||||||
|
if [ -n "$FAILING_LINES" ]; then
|
||||||
|
printf '#### 連續失敗的那幾筆\n\n'
|
||||||
|
printf '**這幾筆每一輪都會再試一次,而且不會自動暫停。** 助理不替人按暫停:那個狀態留給人設,也只有人解得開。\n\n'
|
||||||
|
printf '| id | 已連續失敗 | 最後一次執行 | 標題 |\n'
|
||||||
|
printf '| --- | ---: | --- | --- |\n'
|
||||||
|
printf '%s' "$FAILING_LINES" | while IFS="$(printf '\t')" read -r _fi _fn _fl _ft; do
|
||||||
|
[ -n "$_fi" ] || continue
|
||||||
|
printf '| `%s` | %s 次 | %s | %s |\n' "$_fi" "$_fn" "$(cell "$_fl")" "$(cell "$_ft")"
|
||||||
|
done
|
||||||
|
printf '\n'
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
printf '### 待辦簿到期與逾期\n\n'
|
printf '### 待辦簿到期與逾期\n\n'
|
||||||
printf '**這一輪判不出到期。** 到期判定那一支(`tools/due.sh`)%s。本輪待辦簿共 %s 筆,其中 %s 筆 `fail_count` 大於 0。這一節沒有判定結果**不代表沒有任何一筆到期**,要逐筆看就跑 `/jsc-assist:assistant status`。\n\n' \
|
printf '**這一輪判不出到期。** 到期判定那一支(`tools/due.sh`)%s。本輪待辦簿共 %s 筆,其中 %s 筆 `fail_count` 大於 0。這一節沒有判定結果**不代表沒有任何一筆到期**,要逐筆看就跑 `/jsc-assist:assistant status`。\n\n' \
|
||||||
@@ -1129,6 +1156,17 @@ case "$CMD" in
|
|||||||
d11
|
d11
|
||||||
count_tasks
|
count_tasks
|
||||||
due_scan
|
due_scan
|
||||||
|
# 逾期與連續失敗,每一輪都列一筆待人處理。
|
||||||
|
#
|
||||||
|
# 這兩件事的處置都是人接手,而且**不會自己好**:一筆逾期的交辦不會因為過了更久就不逾期,
|
||||||
|
# 一筆連續失敗的檢查項每一輪都會再試一次然後再失敗一次。所以每一輪都提,不因為上一輪
|
||||||
|
# 提過就這一輪不提——「提過了」不是「處理過了」,而助理不替人按暫停:那個狀態留給人設。
|
||||||
|
if [ -n "$DUE_OVERDUE" ] && [ "$DUE_OVERDUE" -gt 0 ] 2>/dev/null; then
|
||||||
|
add_pending "有 $DUE_OVERDUE 筆待辦逾期,截止時間已經過了" '待辦簿到期與逾期' '/jsc-assist:assistant status'
|
||||||
|
fi
|
||||||
|
if [ "$TASKS_FAILING" -gt 0 ]; then
|
||||||
|
add_pending "有 $TASKS_FAILING 筆待辦連續失敗,每一輪都在重試" '待辦簿到期與逾期' '/jsc-assist:assistant status'
|
||||||
|
fi
|
||||||
tally "$D01_STATUS"; tally "$D04_STATUS"; tally "$D07_STATUS"; tally "$D09_STATUS"
|
tally "$D01_STATUS"; tally "$D04_STATUS"; tally "$D07_STATUS"; tally "$D09_STATUS"
|
||||||
tally "$D11_STATUS"
|
tally "$D11_STATUS"
|
||||||
|
|
||||||
@@ -1159,6 +1197,7 @@ case "$CMD" in
|
|||||||
printf 'tasks_total=%s\n' "$TASKS_TOTAL"
|
printf 'tasks_total=%s\n' "$TASKS_TOTAL"
|
||||||
printf 'tasks_failing=%s\n' "$TASKS_FAILING"
|
printf 'tasks_failing=%s\n' "$TASKS_FAILING"
|
||||||
printf 'tasks_due=%s\n' "$DUE_TASKS"
|
printf 'tasks_due=%s\n' "$DUE_TASKS"
|
||||||
|
printf 'tasks_overdue=%s\n' "${DUE_OVERDUE:--}"
|
||||||
printf 'events_new=%s\n' "$DUE_EVENTS"
|
printf 'events_new=%s\n' "$DUE_EVENTS"
|
||||||
printf 'due_status=%s\n' "$DUE_STATUS"
|
printf 'due_status=%s\n' "$DUE_STATUS"
|
||||||
printf 'due_rc=%s\n' "$DUE_RC"
|
printf 'due_rc=%s\n' "$DUE_RC"
|
||||||
|
|||||||
+3
-3
@@ -306,9 +306,9 @@ RC_CMD=0; RC_WRITE=0
|
|||||||
# 欄位數,對不上一筆都不跑,而不是照舊讀進錯的欄位。
|
# 欄位數,對不上一筆都不跑,而不是照舊讀進錯的欄位。
|
||||||
_cols=$(awk -F"$TAB" 'NF>1{print NF; exit}' "$ROWS" 2>/dev/null)
|
_cols=$(awk -F"$TAB" 'NF>1{print NF; exit}' "$ROWS" 2>/dev/null)
|
||||||
case "${_cols:-0}" in
|
case "${_cols:-0}" in
|
||||||
11) ;;
|
12) ;;
|
||||||
0) note '到期清單是空的,這一輪沒有任何一筆要跑。'; _cols=11 ;;
|
0) note '到期清單是空的,這一輪沒有任何一筆要跑。'; _cols=12 ;;
|
||||||
*) die 2 "到期清單的欄位數是 ${_cols},這一支認得的是 11 欄。判到期那一支的輸出格式換過了,先對齊再跑——照舊讀下去會把指令讀成別的欄位。" ;;
|
*) die 2 "到期清單的欄位數是 ${_cols},這一支認得的是 12 欄。判到期那一支的輸出格式換過了,先對齊再跑——照舊讀下去會把指令讀成別的欄位。第 12 欄是逾期判定,跟這一支要跑什麼無關,但欄位數不對就代表兩支不是同一版,那時候前 11 欄的順序也不能假設還是原來那樣。" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
while IFS="$TAB" read -r c_id c_verdict c_state c_kind c_action c_trigger c_recur c_next c_rearm c_why c_rest; do
|
while IFS="$TAB" read -r c_id c_verdict c_state c_kind c_action c_trigger c_recur c_next c_rearm c_why c_rest; do
|
||||||
|
|||||||
Reference in New Issue
Block a user