Semantic Projection Tools
semantic_project is the MCP surface for deterministic semantic projection —
turning a grounded OMOP concept into one or more CDM rows. It is registered
only when groundworkers.semantic_projection.enabled = true (default false;
see Configuration).
It is not a grounding tool. Callers ground a concept first (concept_ground,
or their own logic) and pass the result in.
It is a thin wrapper over SemanticProjectionService — see
SemanticProjectionService for the full
definition catalogue and selection rules.
semantic_project
{
"grounded_concept_id": 4152280,
"grounded_domain": "Condition",
"grounded_concept_name": "Major depressive disorder",
"definition_hint": "condition_with_status_from_secondary_field",
"context": {
"raw_source_fields": {"role_field": "1"}
}
}
grounded_concept_id and grounded_domain are required. Everything else is
optional.
definition_hint selects a definition explicitly. Omit it only when exactly
one registered definition applies to grounded_domain — with four
built-in definitions on Condition, that domain still does not resolve by
itself, so treat definition_hint as required there in practice.
context carries whatever the selected definition needs beyond the grounded
concept itself — see the field-by-field description in
SemanticProjectionService. In
short: raw_value for a definition that checks the grounded field's own raw
code, raw_source_fields for one that reads a different source field, and
numeric_value for a definition that binds a literal measurement value.
Response (row kept):
{
"definition_name": "condition_with_status_from_secondary_field",
"role": "condition_modifier",
"status": "ok",
"rows": [
{
"row_id": "condition",
"table": "condition_occurrence",
"fields": {
"condition_concept_id": 4152280,
"condition_status_concept_id": 32902
}
}
],
"links": [],
"constraint_checks": [],
"unresolved_fields": [],
"suppressed_rows": [],
"audit_notes": ["Diagnosis paired with a separately-collected Primary/Contributing/Non-contributing role field. ..."]
}
Response (row suppressed — role field raw code "3", Non-contributing):
{
"definition_name": "condition_with_status_from_secondary_field",
"role": "condition_modifier",
"status": "suppressed",
"rows": [],
"links": [],
"constraint_checks": [],
"unresolved_fields": [],
"suppressed_rows": [
{
"row_id": "condition",
"reason": "derivation rule for 'condition_status_concept_id' matched a suppress code on 'source.raw_source_fields.role_field'",
"source_field": "source.raw_source_fields.role_field",
"source_code": "3"
}
],
"audit_notes": []
}
Nothing is silently missing here — rows is empty and suppressed_rows
explains why, with the exact source field and code that triggered it.
Response (no hint, ambiguous domain):
{
"definition_name": null,
"role": null,
"status": "no_match",
"rows": [],
"links": [],
"constraint_checks": [],
"unresolved_fields": [],
"suppressed_rows": [],
"audit_notes": [
"4 definitions match domain 'Condition' ['condition_with_status_from_secondary_field', 'criteria_gate_condition', 'family_history_condition', 'family_member_history_bundle']; pass definition_hint to disambiguate"
]
}
When to use it:
Use semantic_project when:
- a grounded item needs a second CDM column populated from a sibling source field (a role/status pairing)
- a fixed entity concept should carry context like family history while the grounded plain concept belongs in the OMOP value slot
- a source item should sometimes produce no CDM record at all, deterministically and auditably, rather than via ad hoc caller logic
- a grounded quantitative finding needs both a literal numeric value and a mapped OMOP unit concept
- you need the same input to always produce the same output — there is no LLM call anywhere in this path
Do not use it to ground a concept in the first place — that's concept_ground.
Do not expect it to guess which definition applies from free text; today's
callers must already know to pass definition_hint.
Error cases:
| Error code | Condition |
|---|---|
INVALID_INPUT |
Request failed validation (e.g. non-numeric grounded_concept_id) |
QUERY_ERROR |
Definition execution failed unexpectedly (e.g. a suppression_mode="fail" policy actually triggered) |
A NOT_FOUND-style outcome for an unknown definition_hint is not an error
— it comes back as status="no_match" with an audit note naming the unknown
hint, since the request was well-formed and the server made a deterministic
decision about it.
Typical downstream use
The tool is deliberately narrow: it turns an already-grounded concept into CDM rows under a known, registered pattern. It does not search for concepts, and it does not infer which pattern applies — the caller supplies that.