Skip to content

Services — API Reference

Application Composition

build_application(config) is the shared composition root for direct Python consumers. It builds the adapters, the service container, and returns a GroundworkersApp with both attached.

groundworkers.app

load_plugin_readiness(config_path, plugin)

Load current configuration and verify one plugin without TUI coupling.

verify_plugin_readiness(config, plugin)

Build and run one plugin's optional read-only readiness check.

This is deliberately a headless host operation. TUI code may render its result, while MCP tools can return the same contract directly.

VocabService

VocabService provides vocabulary search and concept navigation over OMOP CDM vocabulary tables. It is the backing service for the search MCP tools and a dependency of MappingService.

groundworkers.services.vocab

ConceptMappingResult dataclass

Relationship-driven mapping result for a single source concept_id.

One shape for every navigation. navigate_to_standard and navigate_to_value previously returned two dataclasses that were field-for-field identical apart from whether the list was called standard_concepts or related_concepts; the distinction lives at the wire boundary instead, where it carries meaning for the caller.

ConceptMatch dataclass

A single candidate returned by search_exact, search_normalized, or search_fulltext.

concept_id property

Convenience accessor; the identifier is carried in concept.

MappedConcept dataclass

A concept reached from a source concept, plus the edge that reached it.

concept is a payload from base.concept_payload, not a parallel field list. This used to be five hand-listed identity fields, which meant mapping results silently lacked concept_code, is_active and classification_concept — the flags every other concept payload reports. Composing the shared payload means an upstream field reaches these results without editing anything here.

VocabService

Direct Python API for OMOP vocabulary search and concept navigation.

Exposes raw quality signals (ts_rank, standard_concept flag) so callers can apply their own quality thresholds and decide whether to navigate non-standard results to their standard equivalents.

Raises GroundworkersError for database or query errors. Raises ValueError for invalid arguments.

fts_available property

True when the concept_name_tsvector sidecar column is present.

navigate_to_standard(concept_ids)

Return standard equivalents for a list of concept_ids via "Maps to" relationship edges.

For concept_ids that are already standard: standard_concepts = [self]. For concept_ids with no outbound "Maps to" relationship: standard_concepts = []. concept_ids not found in the vocabulary are silently omitted.

navigate_to_unit(concept_ids)

Return "Maps to unit" related concepts for the given concept_ids.

navigate_to_value(concept_ids)

Return "Maps to value" related concepts for the given concept_ids.

probe_fulltext()

Smoke-test PostgreSQL full-text search with a representative term.

A sidecar column and GIN index can exist while all sidecar values are NULL. Requiring one real match makes that state visible to health reporting instead of treating schema presence as operational readiness.

search_exact(query, *, domain=None, vocabulary_id=None, standard_only=False, active_only=False, include_synonyms=True, parent_ids=None, limit=20)

Case-insensitive exact match against concept_name and optionally concept_synonym_name.

standard_only defaults to False so the caller can inspect non-standard candidates and decide whether to navigate to their standard equivalents.

Returns name matches before synonym matches; deduplicates by concept_id so a concept that matches both name and synonym only appears once.

search_fulltext(query, *, domain=None, vocabulary_id=None, standard_only=False, active_only=False, include_synonyms=True, parent_ids=None, min_rank=0.0, limit=20)

PostgreSQL FTS match using the tsvector sidecar column (GIN-indexed).

Returns (results, fts_available). When fts_available is False the sidecar column was not detected and results is always []; the caller should fall through to another search strategy.

ts_rank is included in each result so the caller can apply its own quality threshold. Synonym FTS is included when the synonym sidecar column is also present; otherwise synonym results are silently omitted.

search_normalized(query, *, domain=None, vocabulary_id=None, standard_only=False, active_only=False, include_synonyms=False, normalization_profile='verbatim', parent_ids=None, remove_stop_phrases=True, limit=20)

Deterministic near-verbatim search after text normalization.

Both the query and candidate text are normalized before comparison. Distinct from full-text search: deterministic equality, not ranked retrieval.

normalize_text_for_matching(text, *, profile='verbatim', remove_stop_phrases=True)

Normalize free text into a deterministic matching form.

serialise_concept_mapping(mapping, *, targets_key)

Serialise a mapping result, naming the target list for the calling tool.

One shape internally; targets_key keeps the wire term meaningful — standard_concepts for standardization, related_concepts for the value and unit navigations.

serialise_concept_match(match)

Serialise a ConceptMatch to a JSON-safe dict for MCP tool responses.

Serialise a value/unit navigation result for MCP tool responses.

serialise_standard_mapping(mapping)

Serialise a standardization result for MCP tool responses.

GraphService

GraphService is the direct-Python surface for deterministic graph-backed concept lookup, hierarchy traversal, path finding, standard mapping, and neighbor exploration.

groundworkers.services.graph

GraphService

Caller-facing graph domain logic backed by the omop-graph runtime.

This service owns multi-step orchestration — hierarchy walks, edge and path assembly, neighbourhood shaping, standard-mapping, and grounding tier selection — composing the normalized primitives exposed by OmopGraphAdapter. It never touches omop-graph internals directly; the adapter owns the backend and returns plain dicts/tuples.

async_ground_with_plan(request) async

Async grounding path that keeps query encoding on the MCP event loop.

canonicalize_domain(domain)

Canonical domain_id, or None if the domain is unrecognised.

concept_views(concept_ids)

Batch-fetch normalized concept views keyed by concept_id.

Thin passthrough to the adapter's batch lookup. Returns an empty dict for unknown ids or on backend failure (the adapter swallows enrichment errors), so callers can treat a missing key as "unknown concept".

get_associations(concept_id, *, direction='out', predicate_subkinds=None, active_only=True, limit=50)

Enumerate ASSOCIATION-kind edges (therapeutic/associative links).

get_extended_inheritance(concept_id, *, direction='out', predicate_subkinds=None, active_only=True, limit=50)

Enumerate HIERARCHY-kind relationship edges (extended inheritance).

This is the raw edge-level HIERARCHY view — NOT the concept_ancestor closure used by get_ancestors/get_descendants. See the tool docstring.

ground_with_plan(request)

Execute a caller-supplied grounding plan: run tiers in order, keep the first tier that yields (FTS-overlap filtered) hits, then enrich and shape.

ConceptGroundingService

ConceptGroundingService owns the caller-facing grounding policy over GraphService: resolver tier ordering, ancestry constraints, and grounding explanations.

groundworkers.services.grounding

ConceptGroundingService

Use-case policy for free-text grounding over the OMOP graph.

The graph service owns omop-graph execution details. This service owns the caller-facing grounding strategy: domain normalization, optional ancestry constraints, tier ordering, and response explanation.

async_ground(query, *, limit, domain, vocabulary_id, parent_ids=None, standard_only=False, active_only=False, include_embedding=True) async

MCP-facing grounding with native async embedding resolution.

MappingService

MappingService is the direct-Python API for mapping workflows. The mapping MCP tools delegate to this service rather than implementing orchestration in the tool module.

groundworkers.services.mapping

MappingService

Direct Python API for mapping-oriented vocabulary workflows.

async_concept_candidate_bundle(query, *, domain=None, vocabulary_id=None, standard_only=False, active_only=True, include_synonyms=True, include_normalized=True, include_fulltext=True, include_embedding=True, include_standard_mappings=True, include_hierarchy_context=False, include_relationship_summary=False, parent_ids=None, per_channel_limit=10, overall_limit=30, model_name=None) async

MCP-facing candidate bundle with native async query encoding.

async_concept_nearest_standard_ancestor(*, query=None, concept_id=None, domain=None, vocabulary_id=None, parent_ids=None, max_depth=5, candidate_limit=10) async

MCP-facing ancestor lookup with async grounding for text queries.

TextService

TextService provides LLM-backed clinical text preprocessing. The text MCP tools (text_normalize, text_decompose, text_disambiguate) delegate to this service.

groundworkers.services.text

LLM-backed clinical text semantics.

This package keeps the public TextService surface small and stable while separating result models, prompt definitions, and service orchestration into modules that can grow independently.

DecomposeResult

Bases: BaseModel

Result of decomposing free text into a list of clinical search terms.

DecomposeTerm

Bases: BaseModel

One extracted clinical concept from a decomposition.

DisambiguateResult

Bases: BaseModel

Result of listing all plausible interpretations of an ambiguous term.

Interpretation

Bases: BaseModel

One candidate interpretation of an ambiguous term.

MappingCleanupResult

Bases: BaseModel

Result of rewriting source text into a more mappable search phrase.

NormalizeResult

Bases: BaseModel

Result of a single-term normalization.

TextService

Direct Python API for LLM-backed clinical text preprocessing.

TextService interprets caller-provided clinical phrases. It normalizes single terms, decomposes multi-concept free text, and surfaces ranked interpretations when the input is ambiguous. The outputs are typed and ready to feed into downstream concept grounding workflows.

All methods raise ValueError for invalid input and GroundworkersError for LLM backend failures or malformed structured responses.

decompose(text, *, domain_hint=None, max_terms=10, model_name=None)

Decompose a free-text clinical description into normalized search terms.

disambiguate(text, *, domain_hint=None, max_interpretations=5, model_name=None)

List all plausible clinical interpretations of an ambiguous term.

mapping_cleanup(text, *, context=None, domain_hint=None, model_name=None)

Rewrite source text into a more mappable OMOP search phrase.

normalize(text, *, domain_hint=None, model_name=None)

Normalize a clinical term, abbreviation, lay phrase, or misspelling.

build_user_prompt(operation, text, **kwargs)

Construct the user-turn prompt for a text operation.

Shared between TextService methods and MCP prompt handlers so both present the same request surface to the LLM.

Clamping is applied here so prompt handlers and service calls always show the same bounded values.

DomainService

DomainService provides LLM-backed batch OMOP domain classification for structured field labels and example values. The domain_classify MCP tool delegates to this service.

groundworkers.services.domain

DomainService — LLM-assisted OMOP domain classification for data dictionary attributes.

Accepts a batch of field labels and their example response values, and returns a mapping of label → OMOP domain string for any label that can be confidently classified. Labels that yield null or an unrecognised domain are omitted so that callers fall through to the next resolution tier (keyword heuristics).

Valid domains returned: Measurement, Condition, Observation, Procedure, Drug, Device, Metadata, Identifier.

"Metadata" and "Identifier" are groundcrew-internal sentinel values, not OMOP CDM domains. The ingester treats them as skip signals and does not create SourceItems for those rows.

TODO: this isn't actually asking about domain_id, it is asking about target CDM table, and is a pre-cursor to semantic projection. This is likely fine with the shorter list of domains, but it could end up being very confusing if that is not clear - some thought required here...

DomainService

Classify data dictionary field labels into OMOP CDM domains via the LLM adapter.

async_classify_attributes(label_values, model_name=None) async

Async MCP-facing variant of :meth:classify_attributes.

classify_attributes(label_values, model_name=None)

Classify field labels into OMOP domains.

label_values maps each field label text to a (possibly empty) list of example response-value strings. Returns a dict containing only labels that received a valid domain string — labels mapped to null or an unrecognised value are excluded so callers fall through to the next tier.

Raises BACKEND_UNAVAIL when the LLM cannot be reached. Raises QUERY_ERROR when the response is not valid JSON.

SourcePlanningService

SourcePlanningService provides stateless source-analysis and assisted planning workflows for pre-ingest artifacts.

groundworkers.services.source_planning

Stateless source-planning artifacts.

This package defines the neutral planning objects that dependency-facing services can produce without knowing anything about ACP session state, review queues, or persistence models.

AnnotatedTable dataclass

Bases: NormalisedTable

Normalized table plus semantic annotation.

This is the first stage that may infer downstream semantic intent. It should retain enough signal for caller-facing orchestration to decide whether the result is strong enough to accept, assist, or route for review.

from_normalised(table, **kwargs) classmethod

Build semantic annotation output from a normalized table.

AssistedColumnRoleClassifier

Apply explicit LLM assistance to uncertain or unresolved columns only.

async_classify(*, baseline, model_name=None) async

Classify candidates through the model backend's async API.

ColumnAnnotation dataclass

Semantic annotation for one normalized column.

ColumnRole

Bases: StrEnum

Semantic role assigned during column annotation.

ColumnRoleClassifier

Classify normalized columns using deterministic header and sample rules.

The classifier is intentionally conservative. It should assign obvious semantic roles confidently and leave weaker cases visible as uncertain instead of overfitting to one source-system shape.

classify(table)

Return semantic annotations for one normalized table.

classify_tables(tables)

Classify multiple normalized tables.

FormatDetector

Identify a submitted source container format from bytes and filename.

detect(content, filename=None)

Return the most likely SourceFormat for content.

HeaderProvenance dataclass

Column-level record of structural cleanup performed during normalization.

Attributes

original: The raw header surface emitted by decomposition. normalised: The final structural header surface used by downstream semantic stages. operations: Ordered normalization steps applied to reach normalised.

IngesterRouter

Map semantically annotated tables to ingestion strategies.

The router stays deterministic and stateless. It does not create chunks, items, or review records; it only turns semantic table annotation into a planning decision that caller-facing orchestration can consume.

route(table)

Return (strategy, routed_table) for one annotated table.

route_tables(tables)

Route multiple annotated tables.

IngestionPlan dataclass

Cross-table planning result consumed by stateful orchestration.

groundable_tables()

Return tables that remain eligible for downstream ingestion.

is_safe_to_ingest()

Return True when the plan has no hard failures.

IngestionStrategy

Bases: StrEnum

Downstream ingestion strategy chosen from semantic annotation outputs.

NormalisationPolicy dataclass

Configuration for structural cleanup.

The defaults are intentionally conservative
  • preserve table identity and row order
  • stabilize headers and cell text
  • prune only columns that are structurally empty after cleanup

NormalisedTable dataclass

Structural cleanup output for downstream semantic reasoning.

This is the stage where header surfaces and cell text are stabilized. It must not assign column roles, domain hints, or strategy choices.

from_raw(table, *, headers, rows, sample_rows, header_provenance, normalisation_notes=None, warnings=None) classmethod

Build a normalized table while preserving non-semantic identity.

PlanningError dataclass

Hard planning failure that should stop autonomous progression.

PlanningWarning dataclass

Recoverable planning issue that should remain visible to callers.

PreIngestBundle dataclass

Top-level stateless planning result envelope.

IngestionPlan remains the core decision object. The optional artifact lists exist for transparency, debugging, and adapter-facing inspection.

RawTable dataclass

Post-decomposition table artifact before structural normalization.

RawTable stays close to the extracted source shape. It may preserve typed or format-specific cell values and should not imply downstream semantic meaning.

SourceFormat

Bases: StrEnum

Container format of the submitted source content.

SourcePlanningService

Thin composition root for the stateless source-planning pipeline.

This service owns stage sequencing only. It detects source format, decomposes content into tables, normalises structure, classifies columns, routes tables to ingestion strategies, and packages the result as a PreIngestBundle.

async_plan_source_assisted(content, *, filename=None, caller_hint=None) async

MCP-facing assisted plan using native async model completion.

classify_columns(table)

Expose deterministic classification directly when needed.

plan_source(content, *, filename=None, caller_hint=None)

Plan one submitted source artifact end to end.

plan_source_assisted(content, *, filename=None, caller_hint=None)

Plan one submitted source artifact with explicit LLM assistance.

plan_tables(tables, *, caller_hint=None)

Plan already-decomposed tables.

plan_tables_assisted(tables, *, caller_hint=None)

Plan already-decomposed tables with explicit LLM assistance.

TableDecomposer

Convert source bytes into one or more RawTable artifacts.

classify_columns(table)

Convenience wrapper for one-shot deterministic classification.

classify_tables(tables)

Convenience wrapper for one-shot classification over many tables.

normalise_headers(*args, **kwargs)

Backward-compatible alias for the PR1 helper name.

normalise_table(table, *, policy=None)

Normalize a raw table into a stable structural representation.

It takes a RawTable and returns a NormalisedTable with cleaned headers, cleaned cell text, deterministic duplicate handling, conservative empty-column pruning, and explicit provenance about the structural changes that were made.

normalise_tables(tables, *, policy=None)

Normalize multiple raw tables with one shared policy.

plan_source(content, *, filename=None, caller_hint=None)

Convenience wrapper for one-shot source planning.

plan_tables(tables, *, caller_hint=None)

Convenience wrapper for planning already-decomposed tables.

route_table(table)

Convenience wrapper for routing one annotated table.

route_tables(tables)

Convenience wrapper for routing many annotated tables.

Knowledge Catalogue

The catalogue is a standalone discovery service rather than an entry in app.services. It reads bundled and configured packs and returns applicable manifest/content objects.

groundworkers.services.knowledge.catalogue

Knowledge catalogue — discovers and filters pack manifests.

The catalogue reads from a packs/ directory tree structured as: packs/ {layer}/ {pack-name}/ manifest.yaml rules.yaml (optional) guidance.md (optional) examples.yaml (optional)

The catalogue is the single entry point for any agent or pipeline stage that needs to discover which knowledge packs apply to a given job context. It can be called at any phase — not only at source planning time.

KnowledgeCatalogue

Discovers pack manifests under a packs root directory and filters by context.

all()

Return all discovered manifests without filtering.

get_pack(name)

Return one pack's manifest plus the content of its bundled files.

Looks the pack up by name and loads guidance.md (raw markdown) and rules.yaml / examples.yaml (parsed YAML). Returns None when no pack with that name is discovered. A file that is absent or unreadable leaves the corresponding field None rather than failing the whole lookup.

invalidate()

Clear the manifest cache, forcing a re-read on next query.

query(*, source_system=None, domains=None, section_names=None, layer=None, include_local=True)

Return manifests whose applicability conditions match the given context.

SemanticProjectionService

SemanticProjectionService deterministically projects a grounded concept into one or more CDM rows via omop-semantics. Not part of app.services — see SemanticProjectionService for why.

groundworkers.services.semantic_projection

DefinitionTrigger dataclass

Declares which grounded domains a definition applies to.

Deliberately simple: domain-only matching. Concept-group-aware matching (e.g. "only for descendants of concept X") belongs in omop_semantics itself if it turns out to be needed by more than one consumer — see agent-stack's SEMANTIC_INTEGRATION design notes. Until then, an ambiguous domain match is reported as no_match rather than guessed.

SemanticProjectionRequest

Bases: BaseModel

Input to SemanticProjectionService.project().

SemanticProjectionResult

Bases: BaseModel

Deterministic projection outcome, transport-ready for the MCP response.

status semantics: - ok — a definition matched and every row is fully bound. - partial — a definition matched but some row fields still need more context (see unresolved_fields). - suppressed — a definition matched but every row it would have produced was dropped by a DerivationRule or SpecialValuePolicy (see suppressed_rows) — nothing should be written for this item. - no_match — no definition matched (including an ambiguous match with no definition_hint to disambiguate).