What I Learned from Building Two Data Pipelines

Last year I worked on two data pipelines with opposite problems.

At Spark Amplify, I worked on a production pipeline that could adapt whole books for readers at different reading levels. One of them was Mark Twain's Adventures of Huckleberry Finn. A large language model (LLM) rewrote the text, while later stages checked that the result was easier to read without losing the meaning, details, or voice of the original.

The other pipeline was Grid Info Tracker, a side project built with another engineer. Its job was to turn monthly grid reports and legal agreements into a queryable history of energy projects. It covered roughly three years of ERCOT GIS and battery releases, along with dates extracted from scanned agreements. Here the output was easy to name, but the input was a mess: shifting spreadsheets, inconsistent filenames, changing schemas, and scanned PDFs.

Both projects made the same weakness obvious. A pipeline can run successfully and still produce data you should not trust. The useful work is deciding what can go wrong, checking for it, and making those checks visible.

When the output is subjective

Each Spark Amplify book moved through several stages. An LLM revised the text, then automated checks compared the revision with the original. Since we were processing more than a hundred texts, rare mistakes did not stay rare for long. The system had to catch them without someone rereading every passage after every step.

Fluency was a poor measure of success. A sentence could sound natural and still alter the story. In Adventures of Huckleberry Finn, for example, a character could not suddenly wear different clothes or speak in a voice that did not fit the book. The facts and tone mattered as much as readability.

We used BERT similarity scores to detect changes in meaning and word-count checks to catch revisions that had grown or shrunk too much. Neither metric could tell us whether a passage was good. They were useful because they turned some kinds of failure into numbers we could inspect.

That distinction changed how I thought about evaluation. Language quality is not binary, and one score cannot stand in for judgment. The practical approach was to define several signs of a bad result, set reasonable thresholds, and send uncertain cases back for review. The model did the rewriting; the surrounding system made the rewriting usable.

Precision before intelligence

The grid project had a narrower question: what was the legally enforceable date in each agreement? Getting the answer into a database was harder than it sounded.

Many agreements were scans rather than digital PDFs, so ordinary text extraction failed. I tried Tesseract and several cloud OCR services. The most reliable early setup used pdfplumber to handle the PDF pages and PIL to prepare the images before OCR. Sharpening, deskewing, and converting the scans made a large difference.

I manually labeled about twenty documents and used them as a small test set. This was important because dates are unforgiving. A system that reads most of a page correctly can still be useless if it changes one digit in the date.

Once the OCR was reliable enough, an OpenAI model selected the enforceable date from the extracted text. A regular expression could find dates, but it could not reliably distinguish an effective date from a signing date or the date of an amendment. The agreements also followed different formats. The model was useful because the rule was fuzzy, not because it removed the need for rules.

The documents often ran for hundreds of pages. After inspecting them, I found that the relevant date usually appeared near the beginning or end, so the pipeline sent only the first and last ten pages to the model. That reduced cost and kept irrelevant text out of the prompt.

We later moved to Google's OCR and text-processing tools. The change was straightforward because the extraction steps were separate from the rest of the pipeline. That was the first time I saw modularity pay for itself so directly: a component stopped being the best option, and replacing it did not require a rewrite.

Monthly data is an unstable interface

The ERCOT data looked simpler. We received new CSV files each month and needed to merge roughly three years of releases into consistent GIS and battery datasets.

Appending the files was the easy part. Each release carried small surprises. Column names changed. Extra rows appeared. A battery field would be empty one month and populated the next. Comments showed up in some rows and disappeared from others. Any script written around one month's file was making assumptions about the next one.

We needed separate scripts for the first import and for later monthly updates. Some transformations were written in dbt, which I had not used before. I worked mostly with pandas, SQL, and BigQuery, using joins and comparisons to see exactly how one release differed from another.

I began to treat each monthly file like a new version of an undocumented API. It had to be inspected before it was trusted. The merge code needed explicit checks for the columns and types it expected, and it needed to fail clearly when those expectations stopped being true.

This also changed my idea of clean data. Cleanliness is not a property a dataset has on its own. Data is clean enough for a particular use. A missing battery field may be harmless for one analysis and fatal for another. The pipeline has to encode that difference.

What I would keep

I used AI in both projects, but for different jobs. At Spark Amplify it generated text. In the grid project it resolved ambiguity that was difficult to express as a fixed rule. In both cases, most of the engineering sat around the model: preparing inputs, defining acceptable output, testing edge cases, and deciding when a person should look again.

I used to count a pipeline as reliable when it finished without crashing. That is only the first test. A reliable pipeline also shows what it processed, which assumptions held, and where the result may be wrong.

The next dataset will fail in ways I cannot predict. The habit I want to keep is simple: decide how I will recognize a bad result before I start trusting the good ones.

Шта сам научио правећи два система за обраду података

Прошле године радио сам на два система за обраду података која су имала готово супротне проблеме.

У компанији Spark Amplify радио сам на систему који је читаве књиге прилагођавао различитим нивоима читања. Једна од њих била је Доживљаји Хаклберија Фина Марка Твена. Велики језички модел (LLM) прерађивао је текст, а наредни кораци су проверавали да ли је резултат лакши за читање, без губитка смисла, детаља и тона оригинала.

Други систем био је Grid Info Tracker, лични пројекат на којем сам радио са још једним инжењером. Његов задатак био је да месечне извештаје о електроенергетској мрежи и правне споразуме претвори у базу за праћење промена на енергетским пројектима током времена. Обухватио је око три године ERCOT извештаја са GIS подацима и подацима о батеријским постројењима, као и датуме издвојене из скенираних споразума. Овде је било јасно шта желимо да добијемо, али су извори били хаотични: табеле променљиве структуре, неуједначени називи датотека и скенирани документи.

Оба пројекта су показала исту слабост. Систем може да заврши без грешке, а да резултат ипак није поуздан. Најважнији део посла био је да утврдимо шта може да пође наопако, како то да препознамо и како да проверу учинимо видљивом.

Када квалитет не може лако да се измери

Свака књига у систему компаније Spark Amplify пролазила је кроз више корака. LLM би прерадио текст, а затим су аутоматске провере поредиле нову верзију са оригиналом. Обрађивали смо више од сто текстова, па су и ретке грешке убрзо постајале чест проблем. Морали смо да их пронађемо без поновног ручног читања сваког одломка после сваког корака.

Течна и природна реченица није нужно била добра. Могла је да звучи уверљиво, а да промени причу. У Доживљајима Хаклберија Фина, на пример, лик није смео одједном да носи другу одећу или да говори тоном који не припада књизи. Чињенице и глас ликова били су подједнако важни као читљивост.

Користили смо BERT оцену сличности како бисмо уочили промене у значењу, као и број речи да бисмо открили верзије које су превише проширене или скраћене. Ниједна од тих мера није могла сама да потврди да је одломак добар. Вределе су зато што су поједине врсте грешака претварале у бројеве које можемо да пратимо.

Тако сам почео другачије да размишљам о евалуацији. Квалитет језика није бинарна особина и један резултат не може да замени процену. У пракси је било боље да дефинишемо више знакова лошег резултата, поставимо разумне границе и сумњиве примере вратимо на преглед. Модел је прерађивао текст; систем око њега чинио је ту прераду употребљивом.

Прецизност пре интелигенције

Пројекат о електроенергетској мрежи имао је уже питање: ког датума је сваки споразум правно ступио на снагу? Унети тај одговор у базу података било је теже него што звучи.

Многи споразуми били су скенирани, а не дигитални PDF документи, па обично издвајање текста није радило. Испробао сам Tesseract и неколико OCR услуга у клауду. У почетку се најпоузданијим показало решење које је користило pdfplumber за рад са страницама PDF-а и PIL за припрему слика пре OCR-а. Изоштравање, исправљање искошених страница и промена формата знатно су побољшали резултат.

Ручно сам означио око двадесет докумената и користио их као мали контролни скуп. То је било важно јер датуми не трпе приближност. Систем може тачно да прочита готово целу страну, а да ипак буде бескористан ако погреши само једну цифру у датуму.

Када је OCR постао довољно поуздан, OpenAI модел је из издвојеног текста бирао датум ступања на снагу. Регуларни израз је могао да пронађе датуме, али није поуздано разликовао датум потписивања, датум измене и датум од којег споразум важи. Ни структура докумената није била уједначена. Модел је помагао зато што је правило било нејасно, а не зато што нам правила више нису била потребна.

Документи су често имали стотине страница. Ручним прегледом приметио сам да се потребан датум обично налази близу почетка или краја, па је систем моделу слао само првих и последњих десет страна. Тако смо смањили трошак и из упита избацили већину небитног текста.

Касније смо прешли на Google алате за OCR и обраду текста. Промена је била једноставна јер су кораци за издвајање текста били одвојени од остатка система. Тада сам први пут непосредно видео колико модуларност вреди: једна компонента више није била најбољи избор, а њена замена није захтевала преписивање целог решења.

Месечни извештај као нестабилан интерфејс

Подаци организације ERCOT у почетку су деловали једноставније. Сваког месеца добијали смо нове CSV датотеке, а требало је спојити око три године извештаја у уједначене скупове GIS података и података о батеријским постројењима.

Само спајање датотека било је најлакши део. Свако издање доносило је ситна изненађења. Називи колона су се мењали. Појављивали су се додатни редови. Поље о батеријама било би празно једног месеца, а попуњено следећег. Коментари су постојали у неким редовима, а затим нестајали из наредне датотеке. Скрипта написана према једном месечном извештају увек је претпостављала нешто о следећем.

Биле су нам потребне посебне скрипте за први увоз и за каснија месечна ажурирања. Део трансформација писао се помоћу алата dbt, који до тада нисам користио. Највише сам користио pandas, SQL и BigQuery, а спајањима и поређењима проверавао сам како се једно издање разликује од другог.

Временом сам сваку месечну датотеку почео да посматрам као нову верзију интерфејса без документације. Пре употребе морала је да се провери. Код за спајање морао је јасно да наведе које колоне и типове очекује и да пријави разумљиву грешку када те претпоставке више не важе.

То је променило и моје схватање уредних података. Уредност није особина коју скуп података има сам по себи. Подаци су довољно уредни за одређену намену. Празно поље о батерији може бити небитно за једну анализу, а пресудно за другу. Систем мора да зна ту разлику.

Шта бих поновио

У оба пројекта користио сам вештачку интелигенцију, али за различите послове. У компанији Spark Amplify стварала је текст. У пројекту о електроенергетској мрежи решавала је недоумице које је било тешко свести на непроменљиво правило. На оба места, највећи део инжењерског рада био је око модела: припрема улаза, дефинисање прихватљивог резултата, провера граничних случајева и одлука о томе када човек треба поново да погледа резултат.

Раније сам систем за обраду података сматрао поузданим ако заврши рад без пада. То је само прва провера. Поуздан систем мора да покаже и шта је обрадио, које су претпоставке важиле и где резултат можда није тачан.

Следећи скуп података имаће грешке које сада не могу да предвидим. Желим да задржим једну једноставну навику: пре него што поверујем добрим резултатима, морам да одлучим како ћу препознати лоше.