RAG on Maintenance Logs and Work Orders: A Practical Walkthrough
Maintenance records are the one plant dataset RAG was actually designed for. They are real text written by people. Here is why it still goes wrong, and the step-by-step build that works.
Everything I have written so far about retrieval on plant data has been a warning. Time-series values carry no semantic content, tag names are codes rather than language, and a historian hands you a compressed reconstruction rather than measurements. If you have read why RAG on historian data fails, you know the conclusion: numeric questions belong to a query engine, not a vector search.
Maintenance records are the exception. Work orders, technician notes, and shift handovers are genuine prose written by people, which is exactly what retrieval-augmented generation was built for. This is the one place in the plant where classic RAG is the right tool.
And it still goes wrong, for reasons that have nothing to do with the ones above. Three things break it: the text is written in a private dialect, the identifiers that matter most are exactly the ones embeddings handle worst, and almost everyone chunks it incorrectly. Here is the walkthrough that avoids all three.
Why maintenance records are worth the effort
Start with why this dataset is worth building on at all.
Sensor data tells you what happened. A maintenance record tells you what a human concluded it meant, and what they did about it. That is a fundamentally different kind of information, and it is the missing half of every causal question. A temperature excursion at 02:14 is a fact. A work order from that morning saying the nozzle seal was replaced is the explanation.
That makes maintenance text the causal bridge in an industrial RAG system. It is also, in most plants, the only written record of institutional knowledge that would otherwise leave with the technician who retires next year.
The anatomy of a work order
A work order is really two datasets wearing one name, and treating them as one blob is the first mistake.
| Part | Examples | What it is good for |
|---|---|---|
| Structured fields | work order ID, asset ID, dates, type (PM or corrective), priority, cost, downtime hours, technician, failure code | filtering, grouping, counting, joining to sensor data |
| Free text | problem description, work performed, technician notes | the actual explanation of what happened and why |
The structured fields are what you filter and join on. The free text is where the value hides. A pipeline that embeds the whole record as one lump gets the worst of both: the free text diluted by boilerplate field labels, and the structured fields buried where you cannot filter on them.
Why maintenance text is harder than it looks
Before the walkthrough, it helps to know what you are actually dealing with, because maintenance free text is not clean prose.
It is written in shorthand. "Repl brg, NFF on 2nd insp, chgd seal & topped up." A technician wrote that for another technician who already had the context. An embedding model has no idea that NFF means no fault found, or that brg is a bearing.
The failure codes are often not real. Most CMMS systems have a failure taxonomy. In practice the dropdown gets left at its default, or set to whatever closes the ticket fastest. If you treat those codes as ground truth for analysis, you will produce confident statistics about a field nobody filled in honestly.
Records reference each other. "Same fault as last time." "See WO-0994." A chunk containing that sentence and nothing else is worse than useless, because it retrieves well on relevance and contains no information.
Sites are multilingual. In any international operation, the same failure mode is described in two or three languages, sometimes inside the same record.
And the text is systematically incomplete. People write up what was unusual. Routine work gets a terse line. That is a rational use of a technician's time, and it means absence of evidence in this dataset is genuinely not evidence of absence.
The walkthrough
Step 1: Extract the two parts separately
Pull the structured fields into columns and the free text into its own field. Keep them linked by the work order ID. Everything downstream depends on being able to filter on one and search the other.
Step 2: Normalise the shorthand
Build an abbreviation and synonym dictionary for your site: brg to bearing, NFF to no fault found, repl to replaced, and the hundred other local contractions your team uses. Expand them at ingest while keeping the original text intact.
This is the same unglamorous work as the semantic tag dictionary from industrial RAG, and it is the same unlock. Nobody wants to build it. It is the difference between a system that works and one that does not.
Step 3: Resolve asset identifiers
Map the asset ID on the work order to the same asset and tag identities your sensor data uses. Plants routinely name the same pump three different ways across the CMMS, the historian, and the P&ID. Until those identities are reconciled, you cannot join a work order to the process data around it, which is where most of the value lives.
Step 4: Chunk at the work-order boundary
This is the single most common technical mistake, and it is worth being blunt about it.
A work order is a complete narrative unit: problem, diagnosis, action, outcome. Chunking it into arbitrary 512-token windows severs the action from the problem that caused it. You end up retrieving "replaced the seal and topped up the reservoir" with no indication of what was wrong or which asset it happened on.
One work order should normally be one chunk. If a record is genuinely too long, split on its internal structure, at the boundary between problem description and work performed, not at a token count. And keep the asset and date in every chunk's text, not only in its metadata, so a retrieved fragment still makes sense on its own.
Step 5: Attach metadata for filtered retrieval
Every chunk carries its structured fields as metadata: asset, date, work type, cost, downtime, failure code. This lets you constrain retrieval before similarity is ever calculated. "Bearing failures on the transfer pumps in the last two years" becomes a metadata filter on asset and date plus a semantic search inside that subset, which is both far more accurate and far cheaper than searching everything.
Step 6: Use hybrid search, not pure vector
Here is the failure that catches good engineers.
Semantic search is excellent at "vibration problems on the transfer pump." It is terrible at "BRG-6204-2RS." Part numbers, equipment IDs, work order references, and failure codes are exact-match tokens, and embeddings are designed to blur exactly the kind of precise character-level distinction that makes those identifiers useful. A search for one bearing part number will happily return a different bearing that is semantically similar and practically wrong.
Run keyword search (BM25 or equivalent) alongside vector search and merge the results. Identifiers hit on the keyword side, descriptions hit on the semantic side, and you need both. In maintenance data specifically, the exact-match half is not optional.
Step 7: Join back to the process data
Now the payoff. For any retrieved work order, pull the sensor window around it: the hours before the failure was reported, and the hours after the work was completed. You get the human explanation and the machine evidence side by side.
This is what a historian cannot do alone, and it is one of the strongest practical arguments for landing plant data somewhere joinable, which I worked through in historian vs time-series database vs data lake.
What this unlocks
Once it works, a set of genuinely useful questions become answerable in seconds rather than in an afternoon of digging.
Has this happened before? The question a technician asks standing in front of a stopped machine, and the one a searchable maintenance history answers best.
What actually fixed it last time? Not the diagnosis, the action. That is in the work-performed text.
Which failures keep coming back? Recurring failures cluster in the text long before they show up in a failure-code report, precisely because the codes are unreliable.
What should I bring? Retrieving the last three similar jobs before a technician drives out, including the parts used, saves a second trip.
Notice that none of these need the model to be clever. They need the retrieval to be right.
Where it goes wrong
Three patterns worth watching.
The no-fault-found trap. NFF records are common, and they usually mean the technician could not reproduce the fault, not that no fault existed. A system that retrieves them at face value will confidently report that a recurring intermittent problem was investigated and found to be fine, which is the opposite of the truth.
Survivorship in the data. You only have records for problems that generated a work order. Failures handled informally, or absorbed by an operator without a ticket, are invisible. Any frequency statistic built on this dataset understates reality, and the system should not present those counts as complete.
Trusting the cost and downtime fields. These are entered under time pressure and often estimated. They are fine for ranking and terrible as precise figures. Use them to sort, not to sum.
The bottom line
Maintenance records are the part of the plant where retrieval-augmented generation genuinely belongs, and that makes it tempting to point a standard pipeline at them and expect it to work. It will not, for reasons specific to this data: private shorthand, unreliable codes, cross-references, and identifiers that embeddings actively destroy.
The fixes are unglamorous and they are all upstream of the model. Separate the structured fields from the free text. Expand the shorthand. Reconcile asset identities. Chunk on the work order, never on a token count. Filter on metadata before you search. Run hybrid retrieval so part numbers actually match. Then join the result back to the sensor window and you have both halves of the story: what the machine did, and what a human concluded about it.
That combination, the numeric evidence from the query-first path and the human explanation from maintenance text, is what the architecture in industrial RAG was building toward all along.
Written by Usman Nasir — control systems engineer, Stockholm.