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_related_concept_mapping(mapping)
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).