n8n 連携: 期間の作業区間を集め、送信済み・重なりを判定する

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 03:12:11 +09:00
co-authored by Claude Opus 5.5
parent c3c42d5988
commit 38a3307cd4
2 changed files with 151 additions and 0 deletions
+72
View File
@@ -1258,6 +1258,78 @@ def make_blocks(session, gap_min=N8N_GAP_MIN, min_min=N8N_MIN_MIN, now=None):
return out
def default_task_name(folder, title):
"""タスク名の初期値: [作業フォルダ名] セッションのタイトル。"""
name = Path(str(folder).replace("\\", "/")).name if folder else ""
title = one_line(title, 200)
return f"[{name}] {title}" if name else title
def collect_blocks(store, project_id, start, end, gap_min=N8N_GAP_MIN, min_min=N8N_MIN_MIN, now=None):
"""期間 [start, end] (date, ローカル) に始まる作業区間を集める。project_id が空なら全プロジェクト。"""
lo = datetime.combine(start, datetime.min.time()).timestamp()
hi = datetime.combine(end + timedelta(days=1), datetime.min.time()).timestamp()
projects = store.projects()
if project_id:
projects = [p for p in projects if p["id"] == project_id]
if not projects:
raise FileNotFoundError(project_id)
out = []
for project in projects:
for path in store.session_files(project["id"]):
if path.stat().st_mtime < lo:
continue # 期間より前に書き終わったログは開かない
session = store.session(path)
if session["startEpoch"] is None or session["startEpoch"] >= hi:
continue
folder = session.get("cwd") or project["path"]
for block in make_blocks(session, gap_min, min_min, now):
if not lo <= block["startEpoch"] < hi:
continue
block.update(
{
"project": project["id"],
"projectPath": folder,
"title": session["title"],
"taskName": default_task_name(folder, session["title"]),
}
)
out.append(block)
out.sort(key=lambda b: b["startEpoch"])
return out
def mark_blocks(blocks, rows, deleted_sources, now=None):
"""n8n の work_log の行と突き合わせて、送信済み / 削除済み / 重なりを付ける。
重なりは [開始, 終了) が少しでも重なること (接しているだけは重ならない)。
相手は work_log の行 (進行中は終了 = now) と一覧の他の区間。同じ source は除く。
"""
now = time.time() if now is None else now
sent = {r.get("source") for r in rows if r.get("source")}
deleted = set(deleted_sources or ())
spans = []
for row in rows:
start = parse_ts(row.get("start_time"))
if start is None:
continue
end = parse_ts(row.get("end_time")) or now
spans.append((start, end, {"kind": "row", "id": row.get("id"), "source": row.get("source") or "",
"task": row.get("task") or "", "start": iso(start), "end": iso(end)}))
for block in blocks:
spans.append((block["startEpoch"], block["endEpoch"], {"kind": "block", "source": block["source"],
"task": block.get("taskName") or "",
"start": block["start"], "end": block["end"]}))
for block in blocks:
block["sent"] = block["source"] in sent
block["deleted"] = not block["sent"] and block["source"] in deleted
block["overlaps"] = [
info for start, end, info in spans
if info["source"] != block["source"] and start < block["endEpoch"] and block["startEpoch"] < end
]
return blocks
# ---------------------------------------------------------------- HTTP