context loading, path resolution, form state, dynamic rules, and provider-neutral assessment runner/cache boundary
This commit is contained in:
parent
eccf1874fb
commit
8361f9ea45
29 changed files with 2809 additions and 65 deletions
|
|
@ -27,6 +27,7 @@ from markitect_tool.diagnostics import (
|
|||
has_error,
|
||||
valid_severity,
|
||||
)
|
||||
from markitect_tool.runtime import RuntimeContext, evaluate_form_state, load_runtime_context_file
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
|
@ -55,6 +56,7 @@ class ContractCheckResult:
|
|||
document_path: str | None = None
|
||||
contract_path: str | None = None
|
||||
metrics: dict[str, Any] = field(default_factory=dict)
|
||||
runtime: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
data = {
|
||||
|
|
@ -63,6 +65,7 @@ class ContractCheckResult:
|
|||
"document_path": self.document_path,
|
||||
"contract_path": self.contract_path,
|
||||
"metrics": self.metrics or None,
|
||||
"runtime": self.runtime or None,
|
||||
}
|
||||
return {key: value for key, value in data.items() if value is not None}
|
||||
|
||||
|
|
@ -131,26 +134,48 @@ def validate_contract(contract: DocumentContract) -> ContractValidationResult:
|
|||
|
||||
|
||||
def check_markdown_file(
|
||||
markdown_path: str | Path, contract_path: str | Path
|
||||
markdown_path: str | Path,
|
||||
contract_path: str | Path,
|
||||
*,
|
||||
context_path: str | Path | None = None,
|
||||
runtime_context: RuntimeContext | None = None,
|
||||
) -> ContractCheckResult:
|
||||
"""Parse and check a Markdown file against a contract file."""
|
||||
|
||||
document = parse_markdown_file(markdown_path)
|
||||
contract = load_contract_file(contract_path)
|
||||
return check_document_contract(document, contract)
|
||||
context = runtime_context
|
||||
if context_path is not None:
|
||||
context = load_runtime_context_file(context_path)
|
||||
return check_document_contract(document, contract, runtime_context=context)
|
||||
|
||||
|
||||
def check_document_contract(
|
||||
document: Document, contract: DocumentContract
|
||||
document: Document,
|
||||
contract: DocumentContract,
|
||||
*,
|
||||
runtime_context: RuntimeContext | None = None,
|
||||
) -> ContractCheckResult:
|
||||
"""Check a parsed Markdown document against a document contract."""
|
||||
|
||||
contract_validation = validate_contract(contract)
|
||||
document_metrics = collect_metrics(document)
|
||||
diagnostics = list(contract_validation.diagnostics)
|
||||
runtime: dict[str, Any] = {}
|
||||
if contract_validation.valid:
|
||||
diagnostics.extend(_check_document_type(document, contract))
|
||||
diagnostics.extend(_check_fields(document, contract))
|
||||
if runtime_context is None and not contract.rules:
|
||||
diagnostics.extend(_check_fields(document, contract))
|
||||
else:
|
||||
form_state = evaluate_form_state(
|
||||
document,
|
||||
contract,
|
||||
runtime_context or RuntimeContext.empty(),
|
||||
)
|
||||
diagnostics.extend(form_state.diagnostics)
|
||||
context = runtime_context or RuntimeContext.empty()
|
||||
runtime["context"] = context.to_dict()
|
||||
runtime["form_state"] = form_state.to_dict()
|
||||
diagnostics.extend(_check_document_metrics(document, contract, document_metrics))
|
||||
diagnostics.extend(_check_assertions(document.body, contract.assertions, document, contract))
|
||||
diagnostics.extend(_check_sections(document, contract, document_metrics))
|
||||
|
|
@ -161,6 +186,7 @@ def check_document_contract(
|
|||
document_path=document.source_path,
|
||||
contract_path=contract.source_path,
|
||||
metrics=document_metrics.to_dict(),
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ def _looks_like_contract(data: dict[str, Any]) -> bool:
|
|||
"metric_bands",
|
||||
"assertions",
|
||||
"forms",
|
||||
"rules",
|
||||
"rubrics",
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@ class DocumentContract:
|
|||
assertions: list[AssertionSpec] = field(default_factory=list)
|
||||
forms: list[dict[str, Any]] = field(default_factory=list)
|
||||
context: dict[str, Any] = field(default_factory=dict)
|
||||
rules: list[dict[str, Any]] = field(default_factory=list)
|
||||
rubrics: list[dict[str, Any]] = field(default_factory=list)
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
raw: dict[str, Any] = field(default_factory=dict)
|
||||
|
|
@ -264,6 +265,7 @@ class DocumentContract:
|
|||
assertions=assertions_from_value(raw.get("assertions")),
|
||||
forms=raw.get("forms") if isinstance(raw.get("forms"), list) else [],
|
||||
context=raw.get("context") if isinstance(raw.get("context"), dict) else {},
|
||||
rules=raw.get("rules") if isinstance(raw.get("rules"), list) else [],
|
||||
rubrics=raw.get("rubrics") if isinstance(raw.get("rubrics"), list) else [],
|
||||
metadata=metadata,
|
||||
raw=raw,
|
||||
|
|
@ -284,6 +286,7 @@ class DocumentContract:
|
|||
"assertions": [assertion.raw for assertion in self.assertions],
|
||||
"forms": self.forms,
|
||||
"context": self.context,
|
||||
"rules": self.rules,
|
||||
"rubrics": self.rubrics,
|
||||
"source_path": self.source_path,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue