Skip to content

Commit 4ebed08

Browse files
satraclaude
andcommitted
feat(041): add prompt field for data collection instructions
Data elements now carry a `prompt` field — the instruction or question presented to the person/process filling the field. This captures semantic meaning beyond the description: - openMINDS: _instruction → prompt (e.g., "Enter the physical pixel size for this grid image (in x,y order)") - ReproSchema: activity preamble + item question → prompt (combined context for what is being asked) - Stored as LinkML annotation, extracted into semantic["prompt"] - Included in embedding text computation for alignment — elements with similar prompts across sources will align better Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8bc9fc9 commit 4ebed08

5 files changed

Lines changed: 37 additions & 12 deletions

File tree

library/src/undata_library/adapters/linkml.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,8 @@ def _extract_via_schemaview(
222222
val = ann_val.value if hasattr(ann_val, "value") else str(ann_val)
223223
if ann_key == "unit":
224224
sem["unit"] = val
225+
elif ann_key == "prompt":
226+
sem["prompt"] = val
225227

226228
# Aliases stored for downstream alignment
227229
if slot_def.aliases:

library/src/undata_library/adapters/linkml_builder.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def add_slot(
4545
aliases: list[str] | None = None,
4646
minimum_value: float | None = None,
4747
maximum_value: float | None = None,
48+
prompt: str | None = None,
4849
) -> None:
4950
"""Add a slot to the schema if not already present.
5051
@@ -53,6 +54,11 @@ def add_slot(
5354
to resolve alias-based lookups to the canonical slot.
5455
minimum_value: Minimum numeric value constraint.
5556
maximum_value: Maximum numeric value constraint.
57+
prompt: Data collection prompt — the instruction or question presented
58+
to the person/process filling this field. Carries semantic meaning
59+
beyond the description (e.g., "Enter the physical pixel size for
60+
this grid image (in x,y order)"). Stored as a LinkML annotation
61+
and included in embedding computation for alignment.
5662
"""
5763
from linkml_runtime.linkml_model import SlotDefinition
5864

@@ -83,6 +89,8 @@ def add_slot(
8389
slot.minimum_value = minimum_value
8490
if maximum_value is not None:
8591
slot.maximum_value = maximum_value
92+
if prompt:
93+
slot.annotations["prompt"] = prompt
8694
schema.slots[name] = slot
8795

8896

library/src/undata_library/adapters/openminds.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,17 +169,15 @@ def _add_schema_to_linkml(self, ld: Any, data: dict, lb: Any) -> None:
169169
prop_def.get("_linkedTypes") and len(prop_def.get("_linkedTypes", [])) > 1
170170
)
171171

172-
desc = (
173-
prop_def.get("description")
174-
or prop_def.get("_instruction")
175-
or ""
176-
)[:500] or None
172+
desc = (prop_def.get("description") or "")[:500] or None
173+
instruction = (prop_def.get("_instruction") or "")[:500] or None
177174
lb.add_slot(
178175
ld,
179176
name,
180177
range=rng,
181-
description=desc,
178+
description=desc or instruction,
182179
multivalued=multivalued,
180+
prompt=instruction,
183181
)
184182
slot_names.append(name)
185183
if prop_key in required_fields:

library/src/undata_library/adapters/reproschema.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,22 @@ def to_linkml(self, source_path: Path, **options: Any) -> Any:
201201
description = _extract_label(
202202
item_data.get(
203203
"description",
204-
item_data.get(
205-
"schema:description",
206-
item_data.get("question", item_data.get("schema:question", "")),
207-
),
204+
item_data.get("schema:description", ""),
208205
)
209206
)
207+
question = _extract_label(
208+
item_data.get(
209+
"question",
210+
item_data.get("schema:question", ""),
211+
)
212+
)
213+
# Build prompt: activity preamble + item question
214+
prompt_parts = []
215+
if activity_desc:
216+
prompt_parts.append(activity_desc)
217+
if question:
218+
prompt_parts.append(question)
219+
item_prompt = " — ".join(prompt_parts) if prompt_parts else None
210220

211221
response_options, resolved_ro = _parse_response_options(item_data, items_dir)
212222
data_type = _infer_type_from_response(resolved_ro)
@@ -243,9 +253,12 @@ def to_linkml(self, source_path: Path, **options: Any) -> Any:
243253
schema,
244254
item_name,
245255
range=linkml_range,
246-
description=description[:500] if description else None,
256+
description=(description or question)[:500]
257+
if (description or question)
258+
else None,
247259
minimum_value=min_val,
248260
maximum_value=max_val,
261+
prompt=item_prompt[:500] if item_prompt else None,
249262
)
250263
slot_names.append(item_name)
251264

library/src/undata_library/embeddings.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,11 @@ def _build_element_text(element_data: dict) -> str:
211211
# ValueSets
212212
parts.append(semantic["name"])
213213

214-
# 2. Type information
214+
# 2. Data collection prompt (instruction/question — carries semantic meaning)
215+
if semantic.get("prompt"):
216+
parts.append(f"prompt: {semantic['prompt']}")
217+
218+
# 3. Type information
215219
type_parts = []
216220
if semantic.get("data_type"):
217221
type_parts.append(f"type={semantic['data_type']}")

0 commit comments

Comments
 (0)