diff --git a/mcp-server/src/legal_mcp/services/audit.py b/mcp-server/src/legal_mcp/services/audit.py index 9992499..ff29386 100644 --- a/mcp-server/src/legal_mcp/services/audit.py +++ b/mcp-server/src/legal_mcp/services/audit.py @@ -7,7 +7,7 @@ from __future__ import annotations import json import logging -from datetime import datetime +from datetime import datetime, timezone from uuid import UUID, uuid4 from legal_mcp.services import db @@ -38,7 +38,7 @@ async def log_action( VALUES ($1, $2, $3, $4, $5, $6, $7)""", uuid4(), action, case_id, document_id, json.dumps(details or {}, ensure_ascii=False, default=str), - user, datetime.utcnow(), + user, datetime.now(timezone.utc), ) logger.info("AUDIT: %s | case=%s | user=%s | %s", action, case_id, user, json.dumps(details or {}, ensure_ascii=False)[:200]) diff --git a/mcp-server/src/legal_mcp/services/research_md.py b/mcp-server/src/legal_mcp/services/research_md.py index 64e7ec2..49287d0 100644 --- a/mcp-server/src/legal_mcp/services/research_md.py +++ b/mcp-server/src/legal_mcp/services/research_md.py @@ -15,8 +15,14 @@ import os import re from datetime import datetime from pathlib import Path +from zoneinfo import ZoneInfo from typing import Any +# Display timezone (INV-UI9): timestamps surfaced to humans are rendered in +# Israel time deterministically, regardless of the process clock (the container +# may run UTC or Asia/Jerusalem). Storage stays UTC; this is display-only. +IL_TZ = ZoneInfo("Asia/Jerusalem") + # Placeholder strings — any of these means "not yet filled" CHAIR_POSITION_PLACEHOLDERS = ( "[ימולא ע\"י יו\"ר הוועדה]", @@ -191,7 +197,7 @@ def parse(file_path: Path) -> dict[str, Any]: date_str = date_match.group(1) if date_match else "" stat = file_path.stat() - mtime_iso = datetime.fromtimestamp(stat.st_mtime).isoformat() + mtime_iso = datetime.fromtimestamp(stat.st_mtime, tz=IL_TZ).isoformat() result: dict[str, Any] = { "header": { @@ -354,7 +360,7 @@ def update_chair_position( "saved": True, "section_id": section_id, "preview": preview, - "timestamp": datetime.now().isoformat(), + "timestamp": datetime.now(IL_TZ).isoformat(), } diff --git a/scripts/calibrate_effort.py b/scripts/calibrate_effort.py index 968d1e4..4fe92c7 100644 --- a/scripts/calibrate_effort.py +++ b/scripts/calibrate_effort.py @@ -58,8 +58,9 @@ import json import logging import os import sys -from datetime import datetime, timezone +from datetime import datetime from pathlib import Path +from zoneinfo import ZoneInfo from statistics import mean logger = logging.getLogger(__name__) @@ -344,8 +345,17 @@ async def _run(args, ts: str) -> dict: return {"dry_run": False, "grid": grid_summary, "by_block": by_block} +IL_TZ = ZoneInfo("Asia/Jerusalem") + + def _ts() -> str: - return datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + """Filename-safe report stamp in Israel time (INV-UI9: human-facing display). + + This script runs on the host, which is UTC, so a process-clock stamp would + mislead the chair. We pin Asia/Jerusalem and suffix ``IL`` (instead of a bare + ``Z``) so the displayed/filename time is unambiguously Israel-local. + """ + return datetime.now(IL_TZ).strftime("%Y%m%dT%H%M%S-IL") def _write_report(result: dict, ts: str) -> tuple[Path, Path]: @@ -356,8 +366,10 @@ def _write_report(result: dict, ts: str) -> tuple[Path, Path]: g = result["grid"] n = g["n_finals"] + il_now = datetime.now(IL_TZ).strftime("%Y-%m-%d %H:%M") lines = [ f"# #208 — כיול model×effort מול הסופיים — {ts}\n", + f"> נוצר: {il_now} (שעון ישראל · Asia/Jerusalem)\n", f"> ⚠️ **גודל-מדגם: {n} סופיים** ({', '.join(g['finals']) or '—'}). " "זוהי **עדות-כיוון, לא רגרסיה** — מעט תיקים בעלי סופי-עלוי. " "ההמלצה אדוויזורית; ההכרעה בידי היו\"ר/המפעיל.\n",