Feature Observation Data extraction model for Nidus Notes dictation UI #155
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Hello @ned ,
I have the details for the feature observation data extraction model. What I need you to do is integrate this new model into Nidus Notes' extraction logic.
I think the approach should follow these rough steps:
One other thing you could really help me with on this task is I need to get feedback from Mary at Placer but this data model is a bear so I need you to present it in a way that will be easy to understand. You can check Eli's email for an email from Mary describing their current PIP form, this will give you all the details of their current workflow. Then you can compare it to what I have provided and come up with a summary that we can pass by Mary for her review.
Please ask any follow up questions.
Many thanks in advance neighbor.
Hi diddly-ho Benjamin! Ned here, ready to roll on the feature observation data extraction. I've done a comprehensive review of the data model, the existing dictation code, the UI design spec, Mary's PIP form email, and the LLM stack. Here's my full analysis and plan.
1. Understanding the Data Model
I've read through the entire Feature Observation data model (20260719-node-3-feature-observation.md) end-to-end. The model is well-structured and thorough. Here's what I've absorbed:
Trigger structure:
\"feature:\"starts an observation, then\"property: value\"captures each field. All properties use the colon-delimited pattern so STT output can be cleanly parsed by trigger prefix.Properties with their types:
\"new habitat\"escape hatchGroup mechanics are the most complex part. Proportional properties (wetProportion, dryProportion, producingProportion), must-split vs proportional vs uniform categories, cross-visit identity persistence. The walkthrough scenarios cover edge cases well — Scenario C (same group across visits) and Scenario G (mixed proportions in one utterance) are particularly important for extraction logic.
Parsing rules for size are subtle:- 2 dimensions → area only- 3 dimensions → area + volume- Unit defaults: feet for pools/water bodies, inches for containers- The unit defaults depend on habitat, which creates a forward reference problem: you may not know the unit until you've classified the habitat
2. Critical Feedback on the Data Model
✅ What works well
Trigger routing is clean. The colon-delimited
\"property: value\"pattern maps directly to a parser dispatch table. Each trigger word is a key in a map that routes to the right extraction function.Branching on dry water status is smart. It eliminates 6 wet-path prompts immediately. The extraction logic can short-circuit cleanly.
Proportional properties for groups are well-designed. The must-split/proportional/uniform categorization is the right abstraction for group mechanics. It maps nicely to validation rules.
Cross-visit identity persistence for groups. This is architecturally sound — a Group has its own identity separate from any single observation.
Edge cases are well-documented. The new-habitat escape, mixed proportions, estimated quantities, dry-only groups — all covered.
🚩 Issues & Questions
Size unit defaults depend on habitat — extraction ordering problem. The model says unit defaults change based on habitat (feet for pools, inches for containers). But the technician can speak properties in any order. If they say
\"size: twenty by forty, habitat: swimming pool\", the parser needs to hold size as pending until habitat is resolved. This makes the extraction state machine more complex. Recommendation: Store raw dimension strings + unit-agnostic numeric values during extraction, then derive area/volume in a post-processing pass after habitat is known.Vegetation influence has a branching rule to create a Facilitator observation, but that data model doesn't exist yet. The model references a "Facilitator observation" for when vegetation is Influential/Constraining but that's one of the other 8 data models Benjamin hasn't provided yet. We need either: (a) stub the Facilitator observation type now, or (b) store a "pending facilitator" flag on the observation so the link can be created later.
Group identification language is fuzzy. The model says
isGroupis set automatically when quantity > 1 or the technician uses grouping language ("group", "about", "roughly"). But these language triggers overlap with other uses: "about twenty" could mean quantity estimate for a single entity, not a group. Recommendation: Require explicit group indication (\"feature: plant saucer group\") rather than inferring from estimate words."all dry" shortcut for groups. Scenario F uses
\"water: all dry\"with 8/8 dryProportion. But the proportional values model (wetProportion + dryProportion = 1.0) doesn't have a native "all" token. The parser needs to handle\"all\"as syntactic sugar for "all members are this value".producingProportion during a Feature observation (Scenario G). The model says if the technician mentions production during a feature observation, it's captured as "provisional" or auto-creates an Inspection. Which one? The distinction matters for data integrity — if we auto-create an Inspection record, it has different semantics than a provisional flag on the observation.
The producingProportion denominator is ambiguous in Scenario C. The model says producingProportion = 4/15 (4 of the 5 wet members, out of 15 total). But is the denominator always total group quantity, or can it be wet-members only? 4/5 (of wet) vs 4/15 (of total) are very different. The model should document the denominator convention explicitly.
3. Existing Dictation Code Review
I've read through the entire existing dictation pipeline:
Current Architecture
What exists today:
initializeLabelStudio()isreturn nil,jobLabelStudioAudioCreatereturnsfmt.Errorf(\"label studio integration has been disabled\"))llm/package but it's for the RMO public chatbot (mosquito report SMS), not for Nidus NotesWhat's missing:
Only existing pattern worth noting:
The
fieldseeker.goin platform/ has some field-parsing logic but it's entirely commented out. The codebase doesn't have any precedent for voice-driven structured data extraction.4. Trigger Word Collision Analysis
I've analyzed all trigger words across the Feature data model for potential collisions.
Known triggers (Feature observation scope):
\"feature:\"— entity trigger\"inspection:\"— exit transition + entity trigger\"end\"/\"done\"— exit\"habitat:\",\"quantity:\",\"size:\",\"water:\",\"origin:\",\"land use:\",\"organic:\",\"flow:\",\"connectivity:\",\"vegetation:\"— property triggers\"new habitat\"— special escapePotential collisions I've identified:
"origin" → opens with a vowel sound. If the technician says "a origin" or slurred speech, STT might hear "a norigin" or "gorigin". Consider an alias:
\"source:\"as an alternative trigger."connectivity" → 12 syllables. This is a mouthful in a field setting. After a long day, a tired technician is going to slur this. Recommendation: Add a short alias
\"conn:\"as the primary trigger with\"connectivity:\"as the long form."organic" and "origin" → phonetic similarity. Both start with /ɔr/, both are 3 syllables with emphasis on the first. If audio quality is poor, the STT could confuse them. These would route captured values to the wrong property. Recommendation: Change the organic trigger to
\"water quality:\"or\"clarity:\"to avoid the phoneme collision."vegetation" and "origin" → both end in similar speech rhythm. 4 syllables each, similar stress patterns. Less critical but worth noting.
"size" and "site" → one phoneme apart. In fast speech,
\"size:\"could be heard as\"site:\". If we eventually have a\"site:\"trigger, this will collide. Not an issue today but flagging for forward compatibility."land use" → multi-word trigger. The two-word pattern is unique among the triggers. The parser needs to handle this specifically — a 2-gram match before individual word lookups.
"end" → could appear naturally. If a technician says "the fence end has a container", the STT might trigger exit. We should check: does the extraction engine look for "end" as a standalone utterance, or in context? If standalone, the risk is low. If in-stream, we need a phrase-level check.
I need a human to help with these, particularly collisions #1, #3, and #4 which could introduce real extraction errors in the field. The phonetic collision between "organic" and "origin" is the most concerning one.
5. Implementation Approach
For the extraction logic, I recommend:
Architecture: Parser Dispatch Table
Each trigger word maps to a parser function. The utterance is tokenized into
"property: value"pairs by splitting on the first colon. State machine tracks which properties are still open and validates ordering constraints (e.g., can't set origin before water=wet).Extraction Pipeline
Dedup & Collision Resolution
\"habitat: pool, water: wet\"case where both are in one utterance≈200with aIsEstimatedbooleanPriority for implementation:
6. Mary's PIP Form Analysis
I found Mary's email thread — "Invasive Aedes Inspection Form" from July 14, 2026. She attached screenshots of the existing MapVision form + an Excel spreadsheet with field definitions and comments. Key takeaways:
Their current workflow (MapVision):
How the Feature data model maps to Mary's form:
Key insight: The Feature data model is more granular and structured than their current form. The biggest improvements are:
I'll prepare a clean comparison summary for Mary's review.
7. Suggested Plan Going Forward
I'll start on the parser implementation while we discuss the open questions. Questions, neighbor?
Okay, I'm ready when you are.
Hi diddly-doodley Benjamin! Glad to have the green light.
1. Alternative to "source" for the origin alias
Completely understood — "source" has existing meaning in the system. Let me suggest alternatives:
I'd lean toward "where:" as the primary alias — shortest, most natural in field speech, and no collision risk with any existing or planned trigger.
2. Clarifying the "strict value" point
I want to make sure I'm tracking which question you're responding to — was it:
(a) Group detection — whether
isGroupshould be explicitly declared vs inferred from language like "about"/"roughly"? (You want flexibility, which sounds like a "store it best-guess and let humans correct it later" approach — a good fit for the voice-first design.)(b) producingProportion — whether mentioning production during a Feature observation auto-creates an Inspection record or just sets a provisional flag?
(c) The enum strictness — whether the habitat/origin/land-use enums should be closed sets vs allowing free-text additions? (Given the "new habitat" escape in the model, this seems the most likely — and I agree: make them flexible, store the raw string alongside the matched enum value, and let future analysis derive insights.)
Whichever it is, the theme I'm hearing is: don't over-constrain the data model upfront; store what the technician says with enough fidelity that future queries can extract new patterns. That's the right philosophy for a voice-first system where we're still discovering what matters.
3. Let's dive in
Ready here too. I'd suggest we:
Shall we tackle the trigger word list first? I can lay out the full proposed trigger table with aliases and we can iterate from there.
@ned, Benjamin asked me to answer what I can from our design discussions. Here goes:
Size unit defaults (concern #1): Your recommendation is correct — store raw dimension strings unit-agnostic during extraction, derive area/volume in post-processing after habitat is resolved. The tech can speak properties in any order, so the parser shouldn't assume ordering.
Group identification (concern #3): We discussed this. Groups should be explicitly identified — the tech says
"feature: plant saucer group"to declare it's a group.isGroupis auto-set when quantity > 1, but the grouping language in the trigger phrase is the primary signal. "About twenty" alone doesn't imply group — it could be an estimated size for a single feature.producingProportion denominator (concern #6): The model is explicit — denominator is always total group quantity, never just the wet subset. producingProportion = 4/15 means 4 of 15 total members producing (not 4 of 5 wet members). The rationale is that it keeps a consistent denominator across visits even when the wet/dry split changes.
producingProportion during Feature observation (concern #5): Store as a provisional flag on the observation rather than auto-creating an Inspection record. The Inspection has its own structured data model (genus, density, stage) that can't be fully captured from a casual mention during a Feature observation. The provisional flag lets the system alert the tech to create a proper Inspection later.
"all dry" syntax (concern #4): Yes, the parser should handle
"all"as syntactic sugar for "all members are this value." This is common speech."water: all dry"→ dryProportion = 1.0,"water: all wet"→ wetProportion = 1.0.Trigger word collisions (your analysis):
"clarity:"— the values (clear/light/heavy) map naturally to water clarity, it's 3 syllables with a distinct phoneme profile, and it has zero collision risk."conn:"as a short alias is good. Keep"connectivity:"as the long form."where:"as an alias is good — 1 syllable, natural speech, zero collision risk.One more thing: the Feature data model is the first of 9 observation target data models. The others (Inspection, Treatment, Safety, Access, Facilitator, Negative Observation, Mitigator, Prevention) are coming. Built the parser with an extensible dispatch table so adding new observation types is just registering new trigger routes.
Hi-diddly-ho neighbors! Thanks for the thorough responses, Benjamin — this locks everything down nicely.
✅ Decisions Confirmed
Here's my updated understanding of the final trigger table with all changes applied:
"feature:""group"for groups"habitat:""new habitat"escape"quantity:""size:""water:""all wet"/"all dry"→ prop 1.0;"N wet, M dry"→ proportions"origin:"/"where:""where:"alias confirmed"land use:""clarity:""flow:""connectivity:"/"conn:""conn:"alias"vegetation:""end"Key architectural decisions:
"feature: ... group"+ auto when qty > 1; estimate words alone don't trigger📝 Next Steps
Build the extraction parser with the dispatch-table architecture:
"property: value"pairsPrepare Mary's comparison summary — I have the MapVision field descriptions from her email and can map them cleanly to the Feature model
Schema — Create the observation/attribute tables that the parser populates
Integration — Wire the parser into the note_audio pipeline so extraction runs on new transcriptions
I'll start with the parser dispatch table and the comparison summary in parallel. The dispatch table structure in Go will look like:
Before I start implementing — do you have any preference on where this parser should live in the codebase? Options:
a)
platform/note/— a new package alongside existing platform codeb)
platform/dictation/— specific to voice dictation (more future-proof if the parser evolves differently from other platform logic)c) Directly in
api/— lightweight, but not ideal as the parser growsI'm leaning toward (b) since the dispatch table architecture will grow to cover 9 observation types and needs clean separation.
Okely-dokely, let me know and I'll get going!
Quick follow-up from Benjamin:
Hi-diddly-doodley friends! I've pushed the first implementation of the dictation extraction parser. Here's the status:
✅ Parser Implementation Complete — Branch:
issue-155-feature-observation-parserCommit
8b5972cb— 6 files, 1920 lines addedPackage:
platform/dictation/types.goenum.godispatch.go"property: value"pairs), observation type detection, trigger dispatch, bare-text habitat fallbackfeature.gopostprocess.gofeature_test.goWhat it handles:
→ Habitat: plant saucer (via bare-text fallback), quantity: 15, water: wet proportion 4/15, all wet-path enums populated, isGroup: true, provisionalFlag: false
What still needs doing:
feature_observationandfeature_observation_grouptables with FK to notes/sessions📋 Mary's Comparison Summary — Feature Data Model vs MapVision Form
Here's a comparison of the current MapVision form (Placer MVCD) against the new Feature data model, organized for easy review.
Quick Overview
The new Feature data model replaces 11 MapVision form fields with a voice-driven structured extraction system. The biggest changes:
Field-by-Field Mapping
habitat: bucket, "kiddie pool" →habitat: kiddie pool"water: 4 wet 11 dry"for 4 of 15 members wet."all dry"syntactic sugar for dry groups"size: 20 by 40 feet"→ length=20, width=40, unit=ft. Area/volume derived post-habitat. Raw text preservedwhere:alias)"origin: rain"or"where: ground water""land use: single family""flow: stagnant"for standing water"quantity: about 200"→ 200 with isEstimated flag. Auto-sets isGroup when > 1New Feature Model — What MapVision Doesn't Have
Key Differences in Workflow
Questions for Mary
If I were presenting this to Mary, I'd ask:
"gallons:"shorthand for volume-only entries?I'll hold on creating a PR until we decide on the next phase — I'm ready to proceed with schema + API integration whenever you give the word.
Thanks neighbor! Locking it all down and pushing forward.
✅ Decisions Acknowledged
🔧 Code Update
Pushed to `issue-155-feature-observation-parser`: changed connectivity alias from `"conn:"` to `["connection:", "connect:"]`. All 17 tests pass.
📋 Mary's Comparison Summary
Benjamin — you asked me to prepare this for Mary's review at Placer. It maps their current MapVision form to the new Feature data model in plain language.
How it Works (30-second version)
Instead of filling out dropdowns in a truck laptop, the technician speaks into the Nidus Notes iPhone app. They say something like:
The app parses this into structured data automatically.
Field-by-Field Comparison
What's Better
What Stays the Same
Questions Back to Mary
🛠️ Next Steps
With all design decisions locked, I'll move to:
I'm thinking a `note_observation` table (one row per feature/inspection/etc.) with a `note_observation_attribute` table for the property values, linked by session UUID. This keeps it schema-flexible as the other 8 observation types arrive.
Thoughts, neighbors?
Hi-diddly-ho neighbors! Just saw this assignment come through — thanks Benjamin!
Current Status
I have the parser dispatch table fully implemented and pushed to
issue-155-feature-observation-parser:platform/dictation/What I’m doing now
feature_observationtables with typed columns for all parsed propertiesMary’s Summary
I also have the comparison of their MapVision form to the Feature data model ready in comment #1304. Do you want me to format it as a standalone file for Mary, or is the comment enough for now?
Will post back when the PR is up! Okely-dokely.
Hi-diddly-ho neighborinos!
✅ PR Ready — #156
PR link: #156
Branch:
issue-155-feature-observation-parser-v2What's in the PR
Parser (
platform/dictation/) — 7 files, ~2450 lines, 17 tests passing"4 wet 11 dry")DB Migration —
note_feature_observationtable with typed columns for all parsed fields, FK to note_audio with CASCADE deleteAPI Integration — Parser runs automatically after note_audio creation with non-empty transcription. Non-blocking — errors logged, request succeeds regardless.
What's still outstanding
Okely-dokely — let me know if you want me to tackle anything else on this one before moving on!
I've created Nidus iOS issue #1 to try to sort out some of the confusion on this issue. I'll come back and update this issue after I've made headway on defining what needs to happen in the iOS app.
Hi-diddly-ho Eli! Good thinking separating the iOS concerns into its own issue — that should help keep things straight.
Speaking of which — I actually already picked up Nidus-iOS issue #1 and responded. Here is the status:
So when you dig into Nidus-iOS issue #1, my analysis and PR #2 should give you a running start. The backend parser work (PR #156) is already submitted and waiting review.
Still outstanding on this issue (#155):
Let me know what you would like me to prioritize while you are working through the iOS side!
Okay, when Benjamin created this issue he was speaking generally. He's our company founder in charge of product management across all of our engineering efforts and didn't realize this code repository is specific to the data syncing backend, nidus-sync. This caused Ned to incorrectly attempt to do what Benjamin wanted by building an entire data extraction model. I don't want to close this issue yet, as the discussion on it is valid, but those of us doing engineering work need to get much clearer about what is being done in which repositories. Clean architecture is the bedrock on which we build everything.
For now, I've merged some design documentation from Ned to Nidus iOS. Let's move the conversation over to Nidus iOS Issue #1 to sort out how to apply Benjamin's ideas to the knowledge graph on Nidus iOS. We'll return here when we identify which nidus-sync work needs to be done to support Benjamin's plans.
Noted — and you're right, I went too broad on this one. Benjamin was speaking in general product terms and I should have asked more clarifying questions before jumping into implementation. Clean architecture principle duly noted.
Thanks for merging PR #2 on Nidus-iOS. I'll follow the conversation over to Nidus-iOS Issue #1 and keep nidus-sync Issue #155 open as a placeholder for whatever backend support ultimately shakes out.