Skip to content
This repository was archived by the owner on Aug 9, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/lex/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from lex.errors import ErrorCode, LexError
from lex.evidence import (
build_provision_evidence,
jsonable_metadata,
normalize_for_search,
search_field_matches,
search_rank_score,
Expand Down Expand Up @@ -206,7 +207,13 @@ def get_cmd(
click.echo(body, nl=not body.endswith("\n"))
return
if as_json:
click.echo(json.dumps({"metadata": meta, "body": body}, indent=2, ensure_ascii=False))
click.echo(
json.dumps(
{"metadata": jsonable_metadata(meta), "body": body},
indent=2,
ensure_ascii=False,
)
)
return
click.echo(text, nl=not text.endswith("\n"))
except LexError as exc:
Expand Down
13 changes: 11 additions & 2 deletions src/lex/evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def _optional_str(meta: dict[str, Any], key: str) -> str | None:
return text if text else None


def _jsonable_meta_value(value: Any) -> Any:
def jsonable_meta_value(value: Any) -> Any:
"""Coerce YAML-parsed scalars to JSON-serializable forms.

Unquoted frontmatter dates become datetime.date / datetime.datetime via
Expand All @@ -144,9 +144,18 @@ def _jsonable_meta_value(value: Any) -> Any:
return text
if isinstance(value, date):
return value.isoformat()
if isinstance(value, dict):
return {key: jsonable_meta_value(item) for key, item in value.items()}
if isinstance(value, list):
return [jsonable_meta_value(item) for item in value]
return value


def jsonable_metadata(meta: dict[str, Any]) -> dict[str, Any]:
"""Return a JSON-serializable copy of parsed frontmatter."""
return {key: jsonable_meta_value(value) for key, value in meta.items()}


def build_provision_evidence(markdown: str, anchor: str) -> dict[str, Any]:
"""Build §7.6 provision JSON evidence from a whole-law Markdown file."""
meta, body = parse_frontmatter(markdown)
Expand Down Expand Up @@ -186,7 +195,7 @@ def build_provision_evidence(markdown: str, anchor: str) -> dict[str, Any]:
if key == "warning":
evidence[key] = str(value)
else:
evidence[key] = _jsonable_meta_value(value)
evidence[key] = jsonable_meta_value(value)

for key in ("country", "language", "document_type", "status"):
if key in evidence and evidence[key] is not None:
Expand Down
60 changes: 60 additions & 0 deletions tests/test_agent_retrieval.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,66 @@ def test_provision_json_serializes_unquoted_yaml_dates() -> None:
assert data["warning"] == "Cite the official source."


def test_get_json_serializes_unquoted_yaml_dates(tmp_path: Path) -> None:
"""Whole-law lex get --json must serialize unquoted YAML dates."""
import hashlib
import shutil
from contextlib import chdir

from lex.evidence import jsonable_metadata

src = Path(__file__).parent / "fixtures/sample_dataset"
dataset = tmp_path / "dataset"
shutil.copytree(src, dataset)
md_path = dataset / "countries/xx/laws/sample-law/current.md"
source_path = dataset / "countries/xx/laws/sample-law/source.html"
sha = hashlib.sha256(source_path.read_bytes()).hexdigest()
md_path.write_text(
f"""---
id: xx/sample-law
country: xx
title: Test Law of 2026
language: en
document_type: law
status: official_current
official_id: TEST-2026-001
source_url: https://example.gov.xx/laws/2026/001
source_file: source.html
source_sha256: {sha}
source_license: CC-BY-4.0
source_attribution: Test Official Publisher, Testland
source_terms_url: https://example.gov.xx/terms
rights_reviewed_at: 2026-01-01
published_at: 1946-04-29
consolidated_at: 2004-01-04
retrieved_at: 2026-07-21T21:21:12Z
---

# Test Law of 2026

<a id="art-1"></a>
## Article 1

First provision text.
""",
encoding="utf-8",
)
meta, _ = parse_frontmatter(md_path.read_text(encoding="utf-8"))
assert type(meta["published_at"]).__name__ == "date"
assert type(meta["retrieved_at"]).__name__ == "datetime"
assert json.dumps(jsonable_metadata(meta))

runner = CliRunner()
with chdir(dataset):
result = runner.invoke(main, ["get", "xx/sample-law", "--json"])
assert result.exit_code == 0, result.output
data = json.loads(result.output)
assert set(data.keys()) == {"metadata", "body"}
assert data["metadata"]["published_at"] == "1946-04-29"
assert data["metadata"]["consolidated_at"] == "2004-01-04"
assert data["metadata"]["retrieved_at"] == "2026-07-21T21:21:12Z"


def test_search_diacritic_normalization_and_matched_on() -> None:
runner = CliRunner()
accented = runner.invoke(
Expand Down
Loading