---
name: worksite-situation-shortlists
description: "Sort a batch of tractor or loader camera runs into candidate CSVs per worksite situation (mud or standing water, heaps of soil or manure in view) with Vivu. Use when curating eval or training data."
---Worksite situation shortlists with Vivu
This skill turns a batch of front camera videos from recent runs of an autonomous tractor, loader or other off-road machine (camera streams your team has already exported from the machine logs to mp4) into one CSV per worksite situation on the team's list: puddles, standing water or glossy wet mud on the ground near the machine, and a heap of soil, manure, straw or bales in view (the reviewer decides whether it sits in the work path). Claude measures the batch, prices it against the user's Vivu plan, uploads it to a private Vivu project, runs one precise search per situation, cuts a contact sheet at half second steps for every window Vivu returns, labels each window looks right, looks wrong or can't tell, and writes every candidate into its situation's CSV with an empty reviewer_verdict column. The data engineer decides what goes into the eval or training set.
The value is in what only the picture holds. File names carry a date, a field and a run number; the machine's logs say where it drove, how fast and how hard the engine worked. None of them says that the headland was under water after Tuesday's rain, or that a manure heap sat in front of the loader on Thursday. The same field looks different every day, and exported camera streams have no audio or captions to search. Vivu's search is used only to shortlist: in our test run the first water wording returned a dry farm track and dry grass as standing water, and a heap search that asked for heaps "in the path" returned a loaded trailer. So every candidate is labeled from its frames, rejected candidates stay in the CSV, and a short list never means the batch had no such stretch.
When to use
Use when someone asks to find the runs where the machine drove into mud or standing water, to pull loader footage approaching heaps for an obstacle eval set, or to sort a batch of field runs by situation before labeling.
Not for these:
- A one off question about one run ("was there water in this file?"): search Vivu directly and look at the frames.
- Dust clouds that hide the field. Our test corpus had no such clip, so a dust search is untested; the probe we ran returned 2 windows, 0 real and 2 false, both sun glare through a dirty windscreen that the reason called a dust cloud. The skill does not offer a dust class. Use engine load, implement and weather logs to pick dusty runs, then look at them.
- People in the work area. A person search in our test run returned 7 windows, 4 real and 3 false (the camera wearer's own arm, drivers inside cabs), so the skill does not offer it. Use the team's own person detector or annotation tool, under its data policy.
- Moving things and events: a vehicle crossing ahead, the machine getting stuck, an implement hitting something. The skill lists static situations only, and makes no safety judgment.
- Failure or intervention root causes, or anything on the machine or in real time. The CSVs are review lists made after the run.
- Frame accurate labels, masks or bounding boxes: use the team's annotation tool.
- Hundreds of hours at once. Pick a subset from the run logs first (fields, dates, rain days, loading jobs); one run of this skill is sized for about an hour of footage.
- Night footage, thermal cameras and fog: untested. The skill can run, but have the reviewer look at every row.
Working principles
- Report measured numbers, not estimates. When a number is an estimate, say so.
- Nothing is verified until it has been checked against the source video. Vivu's results are candidates, Claude's labels are a first pass, and the reviewer's verdict is the decision.
- Stop and tell the user when a required capability or tool is missing. Do not guess around it.
- Ask the user before anything that is expensive to redo (the batch and its index minutes, before upload) and before writing the full CSVs (one sample row first). The skill posts and sends nothing; if the user asks Claude to put the CSVs somewhere, ask again before each write.
- Labels come from frames, never from Vivu's reason text, which describes the situation it was asked for even when the frame shows something else.
- The CSVs keep every candidate and carry no recall or precision figures. A search misses some stretches and caps how many come back, so an empty or short list does not prove the batch has none.
What you need before starting
Check each item at the start of the run and tell the user plainly what is missing before doing anything else.
| Requirement | Why | How to check |
|---|---|---|
| Vivu connector with write access | create a private project, open its upload page, search | vivu_get_account shows can_create_projects: true (tool names may carry a server prefix). A write call failing with "has not granted vivu.write" means the user reconnects Vivu and allows write access |
| The run videos as mp4 files on the user's computer | Vivu indexes video files, and every sheet is cut from the local file | ffprobe -v error -show_entries format=duration -of csv=p=0 FILE prints a length for each file; ROS bags, MCAP or the machine's own log format must be exported to video by the team's tools first |
| A shell on that computer with ffmpeg and ffprobe | measure the batch, cut contact sheets and zoomed frames | ffmpeg -version, ffprobe -version |
| An upload path | move the files into Vivu | vivu_open_upload_page plus the user's own browser, or a browser tool that can attach local files |
| Someone who reviews the CSVs | Claude's labels are a first pass; the reviewer decides | ask who fills reviewer_verdict; the default is the user |
This skill needs Claude Code on the user's computer (the terminal or the Code tab of Claude Desktop), because it reads local files and runs ffmpeg. Claude on the web and cloud sessions cannot reach the files. It downloads nothing, so no residential IP is needed. Nothing recurs, so no scheduler is involved; run it once per batch.
Inputs to collect
Ask for anything missing, most important first.
- BATCH_DIR: the folder that holds the exported mp4 files for this batch. Required.
- The situations and what counts for each. Default: the classes in Step 4 with the label rules in Step 5. If the team defines a class differently (for example, only heaps in the work path count, or only water deeper than a wheel rut counts), the team's definition wins; write it into config.json before labeling.
- BATCH_NAME: a short name such as a farm or site and a week. It names the working folder and the Vivu project. Default: the name of BATCH_DIR.
- Batch size. Default: up to 60 minutes of video, or what remains of the plan this month, whichever is smaller.
- Who reviews the CSVs. Default: the user.
Files and state
Keep everything in one working folder next to BATCH_DIR:
worksite-BATCH_NAME/
config.json BATCH_DIR, classes with query, maximum_results and label rules, Vivu project id
files.csv source_file, duration_s, listed_name, video_id
results/ search results without the result page link, one JSON per class
sheets/ one folder per class with contact sheets and zoomed frames
CLASS.csv one per class: every candidate, Claude's label, empty reviewer_verdict
state.json steps done, files uploaded, classes searched with job ids, classes cut off, candidate keys labeled
The commands in Steps 2 to 5 run from the folder that holds both BATCH_DIR and worksite-BATCH_NAME/, so the relative paths in them resolve.
A rerun reads state.json first and skips finished steps. A file already marked uploaded is never uploaded again, a class with a saved result is not searched again, and a candidate key (video_id, start_ms and class) that already has a label is not labeled again. The next batch gets its own folder and its own Vivu project, so its searches do not return last week's candidates.
Step 1: Check the Vivu connector and the setup
Goal: confirm every requirement before spending anything.
- Call vivu_get_account. If the tool does not exist, tell the user to add the Vivu connector in Claude (
https://mcp.vivu.ai/mcp) and stop. If the account does not showcan_create_projects: true, or a later write call fails with "has not granted vivu.write", ask the user to reconnect Vivu and allow write access, then stop until they have. - Run
ffmpeg -versionandffprobe -version. - List BATCH_DIR and run the ffprobe length command on one file.
Done when vivu_get_account shows can_create_projects: true, ffmpeg and ffprobe print their versions, and ffprobe prints a length for a file in BATCH_DIR.
Step 2: Measure the batch and price it
Goal: an indexed set the user's plan can pay for, approved before upload.
- Run the ffprobe length command on every mp4 in BATCH_DIR and write files.csv with source_file and duration_s. Add listed_name: the file name with each space, bracket, plus sign and percent sign replaced by an underscore, because that is how Vivu will list it.
- Call vivu_get_usage and show one table:
| This batch | Plan allowance | Remaining this month | |
|---|---|---|---|
| Index minutes | sum of duration_s / 60 | from vivu_get_usage | from vivu_get_usage |
| Search credits | 5 per class, 10 for the default two (estimate), plus 5 for each rewording | from vivu_get_usage | from vivu_get_usage |
Plan facts: Free is $0 a month with 20 index minutes a month and 50 search credits a month; Premium is $30 a month with 180 index minutes and 500 search credits. A precise search uses 5 credits in total. A typical batch of ten runs of four minutes each is about 40 index minutes (estimate), which is more than Free covers in a month and fits in Premium.
- If the batch does not fit, offer levers in this order: pick a smaller subset with the run logs (the fields, dates or jobs the dataset needs most), split the batch across two runs or two months, and only then a larger plan. Never drop files the user asked for without saying which ones.
- Confirm the Compliance items with the user.
The account used in our test run returns no allowance figures, so the comparison with a real plan and the user's approval were not exercised in our test run.
Done when the user approves the file list and its index minutes, and config.json records both.
Step 3: Upload and index
Goal: every file in the batch indexed in a private Vivu project for this batch.
- Call vivu_list_projects and reuse a project named "Worksite BATCH_NAME" if one exists. Otherwise call vivu_create_project with that name and visibility "private". The default visibility is organization, which shows the project to everyone in the user's Vivu organization, and field footage with workers, farmyards and number plates is sensitive.
- Call vivu_open_upload_page with the project ID immediately before uploading. The link expires in 180 seconds and works once, so never post or store it. Give it to the user to open in their own browser and select the files, or open it in a browser tool that can attach local files. Claude in Chrome accepts at most 10 MB per upload call and run videos are usually larger, so they normally go through the user's own browser or the Vivu web app. Never split or recompress a file to fit.
- Poll vivu_list_videos until every file shows ready. Match each Vivu file name to files.csv by listed_name and record its video_id there and in state.json.
In our test run the 12 videos (24.0 minutes) were all ready 514 seconds after the upload started. They went through the same upload page with an automated browser, so opening the link in the user's own browser was not exercised in our test run.
Done when every file in files.csv shows ready and has a video_id.
Step 4: Search each situation class
Goal: a saved list of candidate windows for every class.
| Field | Mode | maximum_results | Output |
|---|---|---|---|
| water_or_mud | precise | 40 | water_or_mud.csv |
| pile_on_route | precise | 40 | pile_on_route.csv |
The query for each field, word for word (one per line, field name first):
water_or_mud: brown puddles or a sheet of standing water with a shiny, reflecting surface, or glossy wet black mud, lying on the ground close in front of the machine or camera; dry dirt tracks, wheel ruts in dry soil, patches of snow and wet asphalt roads do not count
pile_on_route: a heap or mound of soil, manure, straw or bales lying on open ground in front of the camera; a loaded trailer, a full loader bucket or a crop standing in the field does not count
The searches run side by side, one per class, because each class is its own list for the reviewer. Within a class the chain is: the precise search finds candidate windows, the contact sheet shows what the camera saw across each window, Claude labels it, and the reviewer decides. Every search is precise because only precise returns a time window; fast returns whole files with an empty reason, and the batch is already a chosen subset. Exported machine camera streams have no audio and no on screen text about the scene, so there is no second modality to switch to.
Both wordings are the second ones we tried on the same footage (see the worked example). The water wording names a dry track, snow and a wet road because the first wording returned them. The heap wording no longer asks whether the heap is in the machine's path: asking for that returned a loaded trailer and a heap beside a tractor, so the search now finds heaps in view and the reviewer judges the path from the sheets. Keep the exclusions unless the team's definition differs.
maximum_results is also the ceiling on how many candidates come back. 40 leaves room for an hour of footage with several stretches per run; a search that returns exactly 40 was cut off, and the batch holds more candidates than one search can return. Then tell the user, mark the class cut off in state.json, and offer to split the batch into two smaller batches with their own projects (quote the index minutes again and wait for approval as in Step 2). In our test run no search came close to 40, so a cut off search was not exercised in our test run.
Run each query with vivu_search_videos (project_id, query, mode, maximum_results). It returns a job ID. Call vivu_get_search_results until complete is true; each status call can wait up to 45 seconds, so a pending search is not a stalled one. Save each completed result as results/FIELD.json without its result_page_url field, and record the job_id in state.json. Show the Vivu result page link in the live reply only; it expires after four hours, so it never goes into a saved file or a CSV.
Run each class once. If the team changes a wording, the new wording is untested until its first candidates have been through Step 5.
Done when every class has a completed result saved in results/, and every class whose search returned exactly its maximum_results is marked cut off in state.json.
Step 5: Make contact sheets for every candidate and label it
Goal: every candidate labeled looks right, looks wrong or can't tell from what its frames show.
For each result in results/FIELD.json, cut contact sheets of its window from the local file at two frames a second:
mkdir -p worksite-BATCH_NAME/sheets/FIELD
ffmpeg -v error -y -ss START -to END -i FILE -vf "fps=2,scale=384:-2,tile=5x4" worksite-BATCH_NAME/sheets/FIELD/rRANK_STEM_STARTMS_%02d.png
FIELD is the class, FILE the local source file (BATCH_DIR/ followed by its source_file), STEM its name without .mp4, RANK the result_number written with two digits (r01, r09) so the files sort by rank, STARTMS the result's start_ms, and START and END the result's start_ms and end_ms divided by 1000. Each sheet holds 20 tiles covering 10 seconds of the window; the last sheet is padded with black tiles. Tile k on sheet n (both counted from 0, left to right and top to bottom) is at START + 10 * n + k / 2 seconds. The tile position gives each frame's time, so the command needs no text overlay. Look at every sheet of a window.
Heap windows can be long, because a loader works the same heap for minutes. For a window longer than 40 seconds, use fps=0.5 in the same command (one tile every 2 seconds); then tile k on sheet n is at START + 40 * n + 2 * k seconds.
Puddles seen through a side window, and a heap at the edge of a wide angle cab camera, are small on a tile. When a tile shows something that might be water or a heap but the sheet does not settle it, extract a zoomed full size frame at that tile's second:
ffmpeg -v error -y -ss SECONDS -i FILE -frames:v 1 -vf "crop=iw/2:ih/2:X:Y,scale=iw*2:-2" worksite-BATCH_NAME/sheets/FIELD/rRANK_STEM_SECMS_zoom_PART.png
SECONDS is the time of the tile, SECMS the same time in milliseconds, X is 0 for the left half or iw/2 for the right half, Y is 0 for the top half or ih/2 for the bottom half, and PART names the crop (for example left_bottom) so two crops of the same second get different files. In our test run the sheets settled every candidate, so the zoomed frame was not exercised in our test run.
Label rules (defaults; the team's definitions from Inputs win):
- water_or_mud looks right when puddles, a sheet of standing water or glossy wet mud are visible on the ground near the machine. It looks wrong for a dry track, wheel ruts in dry soil, snow, a wet road surface and water only on the windscreen.
- pile_on_route looks right when a heap of soil, manure, straw or bales lies on the ground in the picture. Say in the note whether it is ahead of the machine or beside it, because "in the path" is the reviewer's call. It looks wrong for a loaded trailer, a full bucket, a standing crop or a windrow.
- can't tell when the zoomed frame still does not settle it (glare, a dirty windscreen, the object hidden by the loader arms). The reviewer looks at the source.
Write a note of what the frames show for every candidate, such as "slurry puddles on the wet yard ahead of the loader from 01:06" or "dumped heaps on the field beside the tractor, not in its path". Never copy the reason into the note. Record each candidate key and its label in state.json.
Worked example from our test run
The test corpus was public farm machinery footage published under a Creative Commons license, used in place of autonomy team footage because none was available: 12 videos (24.0 minutes) cut at fixed offsets from cab camera recordings of telehandlers and loaders working manure heaps, a tractor hauling soil onto a wet field, a stuck tractor in a meadow, cultivating in low sun and in snow, following a forage harvester, and a mini excavator loading a spreader, with the audio removed and the files renamed so the names said nothing about content. These are wide angle cab cameras with the hood and the driver's hands in frame, and some stretches were filmed by hand outside the cab; a machine mounted perception camera sees the ground differently. Water and heap stretches were marked by hand on contact sheets before any search, together with look alikes (dry tracks, ruts, snow, a wet road, loaded trailers, full buckets).
- water_or_mud, first wording ("standing water, a puddle or a patch of wet mud on the ground ahead"): 3 candidates, 1 real and 2 false, and 2 missed. The false ones were a dry farm track with a wet asphalt road, and dry grass with snow patches.
- water_or_mud, the wording in the table, after 1 rewording on the same corpus: 2 candidates, 2 real and 0 false, and 1 of the 3 water stretches marked by hand missed (mud around a sunk wheel, filmed outside the cab).
- pile_on_route, first wording ("lies on the ground in the path ahead of the machine"): 9 candidates, 7 real and 2 false, and 2 of the 9 heap stretches marked by hand missed. The false ones were a loaded trailer seen through the side window and a heap beside a tractor.
- pile_on_route, the wording in the table, after 1 rewording on the same corpus: 13 candidates, 13 real and 0 false, and 1 heap stretch missed (soil dumped next to a trailer). Some of the real windows show heaps beside the machine rather than ahead of it, and one window ran 103 seconds; the notes say so, and the reviewer decides about the path.
- The 12 videos were all ready 514 seconds after the upload started. The two CSVs held 15 rows in total.
Done when every candidate in results/ has at least one contact sheet in sheets/ and a label in state.json.
Step 6: Write one CSV per class and hand it over
Goal: a review list per class that a data engineer can work through without opening Vivu.
Show the user one sample row and the field mapping, and wait for an OK before writing the rest:
source_file,rank,start_mmss,end_mmss,search_reason,contact_sheets,claude_label,claude_note,reviewer_verdict
WS_06.mp4,5,01:29,01:37,"A large mound of manure lies on the open ground in the field in front of the camera.",sheets/pile_on_route/r05_WS_06_89000_01.png,looks right,"handheld camera outside the tractor; dumped manure heaps on the worked field at 01:31-01:35; beside the machine, not in its path",
The row above is from our test run; the user's rows carry their own file names.
| Column | Source | If unavailable |
|---|---|---|
| source_file | files.csv, matched by listed_name | keep the row with Vivu's file name, label it can't tell with the note "no local file matched", and tell the user |
| rank | result_number in results/FIELD.json | none |
| start_mmss, end_mmss | start_ms and end_ms, written MM:SS | none |
| search_reason | the result's reason, copied as is; it is Vivu's description, not evidence | blank |
| contact_sheets | the sheet files and zoomed frames from Step 5, separated by semicolons | none; the row cannot be labeled without them |
| claude_label | Step 5 (inferred from frames) | can't tell |
| claude_note | what the frames show, in Claude's words, including ahead or beside for heaps (inferred) | blank |
| reviewer_verdict | left empty for the reviewer | empty |
claude_label and claude_note are Claude's reading of the frames, not a measurement. Whether a heap "blocks" the route, or a puddle is deep enough to matter, depends on the machine and the team's definition, so the reviewer goes through every row, the looks right ones included, before anything enters a dataset.
Write FIELD.csv for every class with one row per result, sorted by rank, including the rows labeled looks wrong: a reviewer may disagree with a label, and the rejected rows show what the search confuses. The CSV uses the user's own file names and times, never a Vivu result page link, so it stays usable after the link expires.
Then tell the user, per class, how many candidates came back, how many Claude labeled looks right, looks wrong and can't tell, and whether the list was cut off at maximum_results. Say plainly that an empty or short list does not mean the batch has none, that puddles seen at the edge of the frame are the easiest to miss, and that dust and people were not searched. Do not add recall or precision figures: the hand marks needed to measure them do not exist for the user's batch.
The CSVs stay in the working folder. Passing them on is the user's step. If the user asks Claude to upload or post them (a shared drive, a tracker, a chat), name the destination, show the rendered first rows and wait for a yes before each write.
In our test run the CSVs were written from the dry run's results and sheets; showing the sample row to a user and handing the CSVs over were not exercised in our test run.
Done when every class has a CSV whose row count equals the number of results in results/FIELD.json, the user approved the sample row and the mapping, and state.json marks the batch done.
Compliance
- Use machine footage the team owns or has the rights to analyze. For public videos used as extra material, check the license and the platform's terms first and keep them for internal analysis only. Have the user confirm this before Step 3.
- Farm workers, site crews and visitors on camera did not agree to be filmed for a dataset. The skill lists stretches by situation only; it does no face, plate or identity recognition and never searches for people. Follow the team's data policy for people in frame (some teams blur faces before any upload, and children are common on farms). Do not use this skill to collect footage of particular people or of children; if the batch is meant to gather footage of children, stop and do not upload it. Confirm before Step 3.
- The skill makes no safety judgment, does not say why a run failed or needed an intervention, and does nothing on the machine or in real time. The CSVs are review lists for people, not annotation files.
- The videos stay in the user's Vivu project until the user deletes them. Create the project as private; the default is visible to the whole Vivu organization. Delete videos or the project only when the user asks, and confirm first.
- The skill writes only local files. Any upload or post of the CSVs goes through the approval in Step 6.
Known failure modes
| Symptom | Cause | Fix |
|---|---|---|
| (observed) a dry farm track and dry grass came back as standing water: 3 returned, 1 real and 2 false | the first water wording matched any brown ground near the machine | keep the exclusions in the table wording; label from the sheet, never from the reason |
| (observed) a loaded trailer came back as a heap in the path: 9 returned, 7 real and 2 false; the reason said "a large heap of manure lies on the ground directly in the path of the tractor and trailer" | the search cannot tell what is on the ground from what is on a trailer, or ahead from beside | the table wording excludes trailers and drops the path condition; the reviewer judges the path from the sheets |
| (observed) heaps beside the machine came back as true heap windows: 13 returned, 13 real, several beside rather than ahead | the table wording finds heaps in view, not heaps in the path | say ahead or beside in claude_note; the reviewer decides |
| (observed) mud around a sunk wheel was missed: 2 returned, 2 real, 1 missed | short stretch filmed from an odd angle | an empty or short list does not prove the footage has no such stretch; say so in the summary |
| (observed) sun glare on a dirty windscreen came back as a dust cloud: 2 returned, 0 real and 2 false | glare and dust on the glass look like dust in the air | the skill has no dust class; pick dusty runs from the machine's own logs |
| (observed) the camera wearer's own arm came back as a person in the work area: 7 returned, 4 real and 3 false | the person search does not separate people in the field from hands and drivers in the cab | the skill does not search for people; use the team's own tools under its data policy |
| (observed) a heap window ran 103 seconds | a loader works the same heap for minutes | use the slower sheet rate from Step 5 for long windows and set start_mmss and end_mmss from the tiles if the team needs tight edges |
| upload page asks to sign in or shows an error | the one time upload link expires after 180 seconds | request a new link right before opening it |
| a file is rejected by the browser upload tool | the tool accepts at most 10 MB per call | the user adds the file in their own browser or the Vivu web app |
| "has not granted vivu.write" | Vivu connected read only | the user reconnects Vivu with write access |
| a result's file name does not match BATCH_DIR | Vivu replaced a space, bracket, plus or percent sign in the name | match on listed_name in files.csv |
| a search returns exactly its maximum_results | the batch holds more candidates than one search returns | mark the class cut off, say so in the summary, and offer to split the batch |
| labels on night, fog or thermal footage look unreliable | this footage is untested | treat every row as unmeasured and have the reviewer check each one |