@click.command()
@click.option(
"--gt",
"gt_path",
required=True,
type=click.Path(path_type=pathlib.Path),
help="Ground-truth TTL file.",
)
@click.option(
"--predicted",
"predicted_path",
required=True,
type=click.Path(path_type=pathlib.Path),
help="Predicted / generated TTL file.",
)
@click.option(
"--regime",
type=click.Choice(["ontology_loose", "ontology_strict"], case_sensitive=False),
default="ontology_loose",
show_default=True,
)
@click.option(
"--similarity-threshold",
type=float,
default=0.80,
show_default=True,
)
@click.option(
"--embedding-model",
type=str,
default="paraphrase-multilingual-MiniLM-L12-v2",
show_default=True,
)
@click.option(
"--json-out",
"json_out",
default=None,
type=click.Path(dir_okay=False, path_type=pathlib.Path),
help="Write full result JSON (metrics, alignment counts, entity_matches).",
)
@click.option(
"--verbose/--no-verbose",
default=True,
help="Print entity matches and triple-level TP/FP/FN.",
)
def main(
gt_path: pathlib.Path,
predicted_path: pathlib.Path,
regime: str,
similarity_threshold: float,
embedding_model: str,
json_out: pathlib.Path | None,
verbose: bool,
) -> None:
"""Align and score two TTL graphs (same logic as validation match_triples.py)."""
if not 0.0 <= similarity_threshold <= 1.0:
raise click.BadParameter(
"similarity_threshold must be between 0 and 1",
param_hint="--similarity-threshold",
)
gt_path = gt_path.expanduser().resolve()
predicted_path = predicted_path.expanduser().resolve()
click.echo(f"GT: {gt_path}")
click.echo(f"Predicted: {predicted_path}")
gt_graph = _load_ttl(gt_path)
predicted_graph = _load_ttl(predicted_path)
click.echo(
f"GT triples: {len(gt_graph)} Predicted triples: {len(predicted_graph)}"
)
payload, entity_matches = _run_match(
gt_graph,
predicted_graph,
regime=MatchRegime(regime),
similarity_threshold=similarity_threshold,
embedding_model=embedding_model,
)
payload["entity_matches"] = [
match.model_dump(mode="json") for match in entity_matches
]
if verbose:
_print_verbose(gt_graph, predicted_graph, entity_matches, payload)
else:
m = payload["metrics"]
click.echo(
f"\nP={m['precision']:.4f} R={m['recall']:.4f} F1={m['f1']:.4f} | "
f"entity F1={m['entity_f1']:.4f} | fact F1={m['fact_f1']:.4f} | "
f"matches={payload['entity_match_count']}"
)
if json_out is not None:
json_out = json_out.expanduser().resolve()
json_out.parent.mkdir(parents=True, exist_ok=True)
json_out.write_text(json.dumps(payload, indent=2), encoding="utf-8")
click.echo(f"Wrote {json_out}")