Skip to content

ontocast.cli.match_graphs

Match two TTL graphs locally (same pipeline as /match/* HTTP APIs).

main(gt_path, predicted_path, regime, similarity_threshold, embedding_model, json_out, verbose)

Align and score two TTL graphs (same logic as validation match_triples.py).

Source code in ontocast/cli/match_graphs.py
@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}")