Skip to content

Commit d06f077

Browse files
committed
Refactor PlanChange schema to use add/replace/delete like batch edits
1 parent 2cf4fa7 commit d06f077

File tree

7 files changed

+698
-118
lines changed

7 files changed

+698
-118
lines changed

README.md

Lines changed: 90 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ Meadow supports AI agent-generated plans for batch modifications to works. The s
130130
#### Data Model
131131

132132
**Plans** - High-level task definitions
133-
- `prompt`: Natural language instruction (e.g., "Translate titles to Spanish in alternate_title field")
133+
- `prompt`: Natural language instruction (e.g., "Add a date_created EDTF string for the work based on the work's existing description, creator, and temporal subjects")
134134
- `query`: OpenSearch query string identifying target works
135135
- Collection query: `"collection.id:abc-123"`
136136
- Specific works: `"id:(work-id-1 OR work-id-2 OR work-id-3)"`
@@ -139,31 +139,110 @@ Meadow supports AI agent-generated plans for batch modifications to works. The s
139139
**PlanChanges** - Work-specific modifications
140140
- `plan_id`: Foreign key to parent plan
141141
- `work_id`: Specific work being modified
142-
- `changeset`: Map of field changes tailored to this work
142+
- `add`: Map of values to append to existing work data
143+
- `delete`: Map of values to remove from existing work data
144+
- `replace`: Map of values to fully replace in work data
143145
- `status`: Individual approval/rejection tracking
144146

145-
#### Example Workflow
147+
Each PlanChange must specify at least one operation (`add`, `delete`, or `replace`).
146148

149+
#### PlanChange payloads
150+
151+
- `add` merges values into existing metadata. For lists (like subjects or notes) the values are appended when they are not already present. Scalar fields (e.g., `title`) are merged according to the context (`:append` for `add`, `:replace` for `replace`).
152+
- `delete` removes the provided values verbatim. For controlled vocabularies this means the JSON structure must match what is stored in the database (role/term maps). The planner normalizes structs and string-keyed maps automatically when executing changes.
153+
- `replace` overwrites existing values for the provided keys. Use this when the existing content should be replaced entirely instead of appended or removed.
154+
155+
Controlled metadata entries (subjects, creators, contributors, etc.) follow the shape below. For subjects you must supply both the `role` (with at least `id`/`scheme`) and the `term.id`; extra fields such as `label` or `variants` are ignored during execution but can be included when working with structs in IEx:
156+
157+
```elixir
158+
%{
159+
descriptive_metadata: %{
160+
subject: [
161+
%{
162+
role: %{id: "TOPICAL", scheme: "subject_role"},
163+
term: %{
164+
id: "http://id.loc.gov/authorities/subjects/sh85141086",
165+
label: "Universities and colleges",
166+
variants: ["Colleges", "Higher education institutions"]
167+
}
168+
}
169+
]
170+
}
171+
}
172+
```
173+
174+
When constructing PlanChanges you can mix-and-match operations as needed. For example, to remove an outdated subject and add a new one in a single change:
175+
176+
```elixir
177+
delete: %{
178+
descriptive_metadata: %{
179+
subject: [
180+
%{role: %{id: "TOPICAL", scheme: "subject_role"}, term: %{id: "mock1:result2"}}
181+
]
182+
}
183+
},
184+
add: %{
185+
descriptive_metadata: %{
186+
subject: [
187+
%{role: %{id: "TOPICAL", scheme: "subject_role"}, term: %{id: "mock1:result5"}}
188+
]
189+
}
190+
}
191+
```
192+
193+
#### Example Workflows
194+
195+
**Adding new metadata:**
147196
```elixir
148-
# 1. Create a plan with a high-level prompt and work selection
197+
# 1. Create a plan with a query - PlanChanges are auto-generated for matching works
149198
{:ok, plan} = Meadow.Data.Planner.create_plan(%{
150199
prompt: "Add a date_created EDTF string for the work based on the work's existing description, creator, and temporal subjects",
151200
query: "collection.id:abc-123"
152201
})
153202

154-
# 2. Agent generates work-specific changes
155-
{:ok, change_a} = Meadow.Data.Planner.create_plan_change(%{
203+
# 2. Agent updates each auto-generated PlanChange with work-specific values
204+
changes = Meadow.Data.Planner.list_plan_changes(plan.id)
205+
206+
change_a = Enum.at(changes, 0)
207+
{:ok, updated_change_a} = Meadow.Data.Planner.update_plan_change(change_a, %{
208+
add: %{descriptive_metadata: %{date_created: ["1896-11-10"]}}
209+
})
210+
211+
change_b = Enum.at(changes, 1)
212+
{:ok, updated_change_b} = Meadow.Data.Planner.update_plan_change(change_b, %{
213+
add: %{descriptive_metadata: %{date_created: ["1923-05"]}}
214+
})
215+
```
216+
217+
**Removing unwanted values:**
218+
```elixir
219+
# Remove extraneous subject headings
220+
{:ok, change} = Meadow.Data.Planner.create_plan_change(%{
156221
plan_id: plan.id,
157-
work_id: "work-a-id",
158-
changeset: %{descriptive_metadata: %{date_created: ["1896-11-10"]}}
222+
work_id: "work-id",
223+
delete: %{
224+
descriptive_metadata: %{
225+
subject: [
226+
%{role: %{id: "TOPICAL", scheme: "subject_role"}, term: %{id: "http://example.org/photograph"}},
227+
%{role: %{id: "TOPICAL", scheme: "subject_role"}, term: %{id: "http://example.org/image"}}
228+
]
229+
}
230+
}
159231
})
232+
```
160233

161-
{:ok, change_b} = Meadow.Data.Planner.create_plan_change(%{
234+
**Replacing existing values:**
235+
```elixir
236+
# Replace the title
237+
{:ok, change} = Meadow.Data.Planner.create_plan_change(%{
162238
plan_id: plan.id,
163-
work_id: "work-b-id",
164-
changeset: %{descriptive_metadata: %{date_created: ["1923-05"]}}
239+
work_id: "work-id",
240+
replace: %{descriptive_metadata: %{title: "New Title"}}
165241
})
242+
```
166243

244+
**Reviewing and executing:**
245+
```elixir
167246
# 3. User reviews and approves
168247
{:ok, _} = Meadow.Data.Planner.approve_plan(plan, "[email protected]")
169248
{:ok, _} = Meadow.Data.Planner.approve_plan_change(change_a, "[email protected]")

0 commit comments

Comments
 (0)