Bind renewal dates to renewal clauses
This commit is contained in:
2
a2a.yaml
2
a2a.yaml
@@ -1,5 +1,5 @@
|
||||
name: contract-clock-studio-v1
|
||||
version: 0.1.4
|
||||
version: 0.1.5
|
||||
entrypoint: agent:ContractClockStudioV1
|
||||
expose:
|
||||
public: false
|
||||
|
||||
17
agent.py
17
agent.py
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user