Bind renewal dates to renewal clauses

This commit is contained in:
2026-07-18 05:00:29 -03:00
parent e88577c90d
commit 11e097a1a5
3 changed files with 32 additions and 7 deletions

View File

@@ -1,5 +1,5 @@
name: contract-clock-studio-v1
version: 0.1.4
version: 0.1.5
entrypoint: agent:ContractClockStudioV1
expose:
public: false

View File

@@ -44,8 +44,8 @@ ALLOWED_BROWSER_MEDIA_TYPES = (
)
DATE_RE = re.compile(r"\b(20\d{2}-\d{2}-\d{2})\b")
RENEWAL_DATE_RE = re.compile(
r"\brenew(?:s|al|ed|ing)?\b[^.\n;]{0,160}\b(?:on|as\s+of|effective|date|until|through)\s+"
r"(20\d{2}-\d{2}-\d{2})\b",
r"\brenew(?:s|al|ed|ing)?\b[^.\n;]{0,160}?\b(?:on|as\s+of|effective(?:\s+on)?|"
r"date(?:\s+is)?|until|through)\b\s*:?\s*(20\d{2}-\d{2}-\d{2})\b",
re.IGNORECASE,
)
RENEWAL_RE = re.compile(r"\brenew(?:s|al|ed|ing)?\b", re.IGNORECASE)
@@ -76,7 +76,7 @@ class ContractClockStudioV1(A2AAgent[ContractClockStudioV1Config, PlatformUserAu
"contracts, saves user-scoped timelines, and reopens them in a packed "
"one-page studio."
)
version = "0.1.4"
version = "0.1.5"
config_model = ContractClockStudioV1Config
auth_model = PlatformUserAuth
@@ -277,8 +277,9 @@ def _analyze_text(*, contract_id: str, title: str, text: str) -> dict[str, Any]:
return {"ok": False, "code": code, "contract_id": contract_id}
deadlines: list[dict[str, str]] = []
if RENEWAL_RE.search(clean_text):
renewal_date = explicit_dates[0]
renewal_match = RENEWAL_DATE_RE.search(clean_text)
if renewal_match:
renewal_date = date.fromisoformat(renewal_match.group(1))
deadlines.append({"kind": "renewal", "date": renewal_date.isoformat()})
notice_match = NOTICE_RE.search(clean_text)
if notice_match:
@@ -287,6 +288,12 @@ def _analyze_text(*, contract_id: str, title: str, text: str) -> dict[str, Any]:
deadlines.append(
{"kind": "notice", "date": (renewal_date - timedelta(days=notice_days)).isoformat()}
)
elif RENEWAL_RE.search(clean_text):
return {
"ok": False,
"code": "ambiguous_renewal_date",
"contract_id": contract_id,
}
else:
# Explicit dates outside a renewal clause are kept as dated obligations;
# relative invoice terms without a concrete anchor are intentionally not guessed.

View File

@@ -21,7 +21,7 @@ def test_full_stack_product_contract():
frontend = (ROOT / "frontend" / "src" / "App.jsx").read_text(encoding="utf-8")
browser_client = (ROOT / "frontend" / "src" / "a2a.js").read_text(encoding="utf-8")
assert "version: 0.1.3" in manifest
assert "version: 0.1.5" in manifest
assert "public: false" in manifest
assert "frontend:" in manifest and "mount: /app" in manifest
assert "resources:" in manifest and "databases:" in manifest
@@ -84,6 +84,24 @@ def test_success_and_failure_extraction_are_explicit_date_only():
}
def test_renewal_date_is_bound_to_the_renewal_clause():
result = agent._analyze_text(
contract_id="multi-date-contract",
title="Multi-date contract",
text=(
"This Agreement is effective on 2025-01-01. "
"It renews automatically on 2026-12-31 unless either party gives "
"at least 60 days written notice."
),
)
assert result["ok"] is True
assert result["deadlines"] == [
{"kind": "renewal", "date": "2026-12-31"},
{"kind": "notice", "date": "2026-11-01"},
]
def test_browser_upload_validation():
doc = agent.BrowserDocument(
filename="contract.txt",