In recent years, I have watched TikTok flood with creators teaching how to use artificial intelligence better, exploring tricks and tips. At the same time, companies such as Anthropic, OpenAI and various Chinese firms — which joined this race quite a while ago — began releasing tools increasingly geared towards productivity.
Meanwhile, a strong debate about regulation and ethics in AI, under way for years, carried on. Since the start of the year, I have been racking my brains over how to use all of this more practically in our day-to-day work. Many organisations have launched challenges for artificial intelligence to generate social impact — but a good many of them still feel remote from our reality. Remote because, quite often, the ambition of solving huge problems with AI still sounds a little unrealistic to us.
Although I believe all of that is possible, perhaps the difference lies in the worldview: we are more concerned with understanding how artificial intelligence can support us today — in the periphery, in the favela, in the reality of our education and social innovation programmes here at Casa Hacker. How can it be more responsive? How can it help us make decisions day to day?
The obvious is not enough
At first, organisations and technology companies commonly think the obvious will do: buy licences for ChatGPT, Claude and other tools, and hand them out to staff indiscriminately. But where does care for data protection, for strategic information, come in? How much do people really plan for making their data accessible to artificial intelligence?
Here at Casa Hacker, we handle hundreds of PDFs every day — finance, teaching, education, social innovation — as well as spreadsheets, text documents and files generated by partners’ systems and by data analysis. Ensuring all of that is read and processed securely by AI is an enormous challenge. In practice, that challenge turns out to be even bigger and more urgent than adopting artificial intelligence in our daily work at all.
It was with this in mind that, a few months ago, we set ourselves a challenge here at Casa Hacker: to bring our database together and prepare it for artificial intelligence.
That is the part of the story I have already told in another piece. Now comes the rest — what we actually did, what went wrong, and what we learned that is useful for any organisation in the same position.
Part 1 — Where we were
One clarification first: when I talk about our system, I am talking about the Cockpit. It is the management platform we built for Casa Hacker itself — it is not a product we sell. It is where the projects, contracts, finances, budget, purchasing, invoices, legal affairs, internships, equipment inventory and teaching records live. It was born to replace a handful of proprietary platforms, each with its own licence, its own closed database and its own spreadsheet export.
By August 2026, the Cockpit already held everything. And it could answer almost nothing.
There were thousands of PDF files in storage — contracts, invoices, receipts, minutes, policies, certificates — and the content of those files was, to the system, a blur. There was also something else, which I consider more valuable: 53 columns of prose text scattered across the database. The report of a session. The syllabus of a workshop. The clause of a contract. The record of what actually happened, written by members of the team — and unreachable by any search.
Because there was no search. The Cockpit had three separate searches, one in each module, and each knew only about its own corner. Asking “where is the termination clause in the contract with that supplier?” meant opening folders.
The first finding: three of the four “we don’t have” already existed
We began by listing what was missing. There were four things. Three were not missing.
The technology for meaning-based search was already installed on the server and had never been switched on — it needed a one-line command. The program that reads the text inside a PDF (including character recognition on scanned documents) was already running in production and threw the result away at the end of each query. And the standard for recording where a piece of AI-generated data came from already existed, working, in a corner of the system.
With the names, for anyone who wants to check: the meaning-based search is the pgvector extension, which was already installed on the server and only needed a CREATE EXTENSION vector command. The PDF reading is poppler, Tesseract, OCRmyPDF and Ghostscript — four free programs that were already travelling inside our container image, called up for a one-off query and discarded straight afterwards. And the provenance record was a due-diligence table that already stored, for each result, the model used, the prompt version and a fingerprint of the content analysed. None of the three had to be bought, contracted or invented. They had to be noticed.
Three of the four “we don’t have” were, in fact, “we have and don’t use”.
That is the first lesson I want to put on record, because it applies to almost everyone: most of what gets called an “AI project” in an organisation is data preparation work that nobody had done yet. The inventory cost us a few hours and changed the design of the whole project. The work stopped being “build” and became “keep what was already being produced and discarded”.
Part 2 — What we did, in eight stages
We organised the work into eight numbered stages, and the order between them is the most important part — more important than any choice of technology.
| Stage | What it does | Status |
|---|---|---|
| 0 | Sets up the yardstick: a set of real questions to measure whether search works | done |
| 1 | Stores the text of every document, page by page | done |
| 2 | The first single search in our history | done |
| 4 | The database “dictionary”: what each piece of data means | done |
| 3 | Neighbours and similar documents (the vector part) | done |
| 5 | The door through which an AI assistant enters | live |
| 6 | The assistant can now write, not only read | live |
| 7 | 81 tools across the 13 modules, classified by risk | live |
Yes, stage 4 comes before stage 3 in the list. We swapped the order midway, for three measured reasons. I explain below.
The pieces, and why all of them are free
Before the stages, the list of what we use. It comes here rather than at the end because “with what?” tends to be the first question — and because the answer is less impressive than people expect, which is exactly the point.
- PostgreSQL 16 — the database where the Cockpit’s data already lived. Everything described below lives inside it. We did not stand up a single new database.
- pgvector — the Postgres extension that stores and compares vectors. It is the “meaning-based search” piece.
- unaccent and Postgres’s native text search — what makes “prestacao” find “prestação”.
- poppler, Tesseract 5 with the Portuguese language pack, OCRmyPDF and Ghostscript — the PDF reading and the character recognition, all inside our own machine.
- Rust, with
axumserving the API andsqlxtalking to the database — the language the Cockpit is written in. - calamine, zip and quick-xml — libraries that read
.docx,.xlsx,.pptxand.odt. They were already declared in the project; starting to use them cost no new dependency. - multilingual-e5-small — the model that turns text into vectors. It runs locally, has 384 dimensions and fits on a modest machine.
- MCP (Model Context Protocol) — the open standard through which an AI assistant talks to a system. It is stage 5, and I come back to it properly.
- pgBackRest — the database backup, which came to cover this whole new layer without one line of extra configuration, because the layer lives in Postgres.
Not one item on that list is expensive, secret or restricted. All of it is free software, publicly documented, installable today by any organisation — and most of it was already on our machine before the project began. The scarcity, in our field, is not of technology. It is of time to tidy the data. That is why this text insists on the order of the stages and not on the choice of pieces.
Stage 0 — The yardstick before the race
The first stage had two lines of code and one task that consumed all the time: building an evaluation set. It comprises 42 questions in natural language, each with the answer, the document and the page that prove it.
Three decisions that look like details and are not:
- The yardstick comes before the search. Anyone who writes the questions after seeing what the system finds writes the questions the system gets right. It is self-deception dressed up as method.
- The questions are written using different words from those in the document, on purpose. That drives the score down — and it is the only way for the number to mean “a question from a real person”.
- The page is part of the target, not just the document. It was that choice which revealed, two stages later, that our real problem was getting the page right. A test that only asked for the document would never have shown this.
And one decision about care: not a single question in the set uses personal data. Our archive contains identity documents, CPF numbers and parents’ names, and a large share of our participants are teenagers. The set was built only from institutional documents — the statutes, minutes, balance sheets, policies. Two pages that would have made good questions were discarded because they carried an RG and a CPF in the body. The reason is not only privacy: a test file gets copied and pasted far more freely than the original data ever was.
Stage 1 — The text we threw away every day
This stage uses no vendor’s artificial intelligence at all. These are programs that were already inside our system: one reads the text inside the PDF, another recognises characters when the page is a photo of paper, another decides between the two.
By name: pdftotext, from poppler, tries first, because a PDF born digital already carries its text and reading it costs milliseconds. Only if a page comes back below a threshold of usable characters is it rasterised and sent to Tesseract 5 with the Portuguese language pack, orchestrated by OCRmyPDF. That order — the cheap thing first, the expensive one only when needed — is what separated hours from days in the initial push: the old engine sent everything to character recognition, including the PDFs that did not need it.
And one honest clarification, since I spend this whole text separating “AI” from “not AI”: Tesseract 5 is a neural network. When I say this stage uses no vendor AI, I am talking about vendors — no API call, no key, no byte leaving — and not about “no neural network involved”.
No PDF leaves Casa Hacker. Not to a text recognition service, not to a “document reading” service, under any circumstances. That was decided before a single line of code and was never reopened.
Three technical choices worth translating:
The unit is the page. Storing the whole document in one text field would have been simpler and would have destroyed the product. “I found the contract” without “it is on page 3” is no use for accountability — and accountability is what pays the bills here. Today, when the search finds something, it opens the PDF at the exact page.
The document is identified by its content, not by its name. Each file has a “fingerprint” calculated from what it contains — a sha256, the same checksum a Linux distribution publishes next to the file you download. Identical files have the same fingerprint, so they are read only once. We discovered that around 18% of our archive is duplicates — the same receipt attached in two places.
The queue is a table, not a message broker. A pending document is a row with the state pendente, and a periodic sweep looks for whatever was left behind. We could have stood up a RabbitMQ or a Redis. We did not: a table in a database that already exists is covered by the backup that already exists, survives a restart with no configuration at all, and does not become one more service for someone to be woken up for at night. And, above all, the sweep covers everything by construction — including the files that arrive through paths that never pass the upload screen, such as the automatic invoice collector. A trigger on upload would cover only those who announce themselves; the sweep also covers those who do not.
And the part I am proudest of: a gap that is stated is a gap; a gap that is hidden is a lie. Today 3,858 documents are marked “unsupported format” and 94 as “error” — encrypted PDFs, corrupted files, formats with no reader. They appear named on screen, counted. Because an index that hides what it could not read produces a “not found” that people read as “does not exist” — and that is the worst possible answer in a management system, because it is identical to a correct one.
Stage 2 — The first search in our history, and the discovery nobody expected
The easy part was the expected one: search in Portuguese, with no need for accents, answering in milliseconds. Searching “prestacao de contas” finds “prestação de contas”.
This is PostgreSQL’s native text search, with no separate search service. Four pieces, all off the shelf: a language configuration of our own, joining the database’s Portuguese dictionary with the unaccent extension; a column that keeps itself up to date with the text already prepared for searching; a GIN index on top of it; and ts_headline, which returns the excerpt with the term highlighted. We did not install Elasticsearch, we did not install OpenSearch, we did not stand up a second server. I come back to why we refused, in the rules — because it was not thrift, it was access control.
The first honest result was 2 hits out of 28 questions.
Investigating, we found the reason, and it is a lovely one: our database’s search engine does not know what is rare. It weighs how many times a word appears inside a document and ignores whether it appears everywhere. In our archive, “casa” appeared on half the pages, “atividade” and “projeto” on nearly half — and the system gave those words the same weight as “bolsa incentivo”. It was not a configuration problem. It was the absence of a signal that the documentation does not warn you is absent.
The name for this, in the information retrieval literature, is inverse document frequency — the “IDF” that dedicated search engines have had off the shelf for decades, and that Postgres’s text search simply does not calculate. So we built our own: a table of word rarity, assembled from a function the database already offers (ts_stat), ready in nine seconds, today holding 323,776 stems that state the true weight of each term. With that, 2 out of 28 became 14 out of 32.
And then came the bigger lesson. One question contained the word “gitlab”, present on one single page out of 35 thousand — the perfect identifier. The right document came up in position 155. Reason: three middling words added together beat the unique word. The sum diluted precisely the strongest signal.
The fix was one line: rank first by the rarest word the page contains, and use the sum only to break ties. We tested the obvious variants before settling on that one — summing the squares of the weights, doubling the largest and adding the rest, ordering by how many terms the page covers. All measured, all worse. The winner and the three losers are recorded in the code, each with its number. Final result, after three fixes of this kind:
- document among the top 10: from 2 to 19 out of 34 questions
- correct page among the top 10: 14 out of 34
- questions containing typos: from 3 out of 6 to 6 out of 6
- response time: from 177 milliseconds to 11
One line of ranking yielded more than the entire artificial intelligence layer. I return to this in stage 3.
Stage 4 — The dictionary: the difference between the date of the fact and the date of the stamp
Searching passages answers “where is it written”. It does not answer “how much did we spend” or “how many young people did we serve”. Those questions come from querying the database — and to get that query right, you need to know what each field means.
That is why we built a data catalogue: a readable description of what each table and each column mean. It is generated automatically from the database itself and checked at every code change. A catalogue maintained by hand rots within weeks and starts to lie — which is worse than not existing.
The mechanics are modest and worth copying. The database already knows how to describe itself: Postgres keeps an inventory of its own tables and columns (information_schema) and a place where each of them can carry a comment (COMMENT ON, which lives in pg_description). A program reads both and writes catalogue files into the repository. What turns that into living documentation is the next step: a continuous integration test that fails the change if the committed catalogue diverges from the database. Without that test it is one more document; with it, whoever touches the database and does not describe what they touched cannot merge. Beyond tables, the catalogue covers the API routes and the scheduled jobs — and the route catalogue turned out to be useful in a way we had not foreseen, in stage 7.
The most important field in that catalogue is the one separating date of the fact from date of the stamp. Let me give the concrete case, because it explains everything:
There was a column called data_inscricao. It looks like the date the person registered. We measured: in 978 of 978 records, it held the day the data was imported from another system. A sibling column, measured later, had 98.9% of its records concentrated on two days.
Anyone grouping by those columns produces a growth chart that looks right, is wrong, and is presented with conviction — because the query ran without a single error. No automated test catches that. Only the dictionary catches it. We rebuilt the column from the true source, and today the catalogue carries the explicit instruction: do not group this for a time series.
The catalogue also measures its own honesty: of the 4,807 catalogued columns, only 232 (4.8%) have a written description. The generator prints that number on every run, precisely so that it is held to account rather than estimated.
When we compared how well an assistant answered questions about our data with and without the catalogue: 17 out of 19 without, 18 out of 19 with. The gain in the score is small. But in 8 of the 19 the catalogue changed the substance of the answer even when the number was already right — the assistant came to distinguish three different tables called “projeto”, came to know that one date was a stamp, came to know that the extracted text contains personal data.
Stage 3 — The “AI” part we built and left out
This is the stage most projects do first. We did it last among the preparation stages, and deliberately so: without a baseline, there is no way to assert that the AI improved anything, only to believe that it did.
The mechanics, in plain language: the text is cut into passages, and each passage is converted into a list of numbers representing its meaning. Passages with similar meaning end up close together. That makes it possible to search by meaning, not by exact word. Today we have 77,068 converted passages.
With the names: the cutting into passages is our own code, with no library — paragraphs of up to two thousand characters, with a piece of overlap between neighbours, and never crossing from one document into another. The model that turns a passage into numbers is multilingual-e5-small, 384 dimensions, running locally in ONNX format: not one passage was sent to any vendor, which keeps the stage 1 rule standing. The vectors sit in pgvector, inside the same database, with an HNSW index — the structure that finds the nearest neighbours without comparing against every one of them.
The result of the measurement was uncomfortable and is the most valuable result of the project:
The entire artificial intelligence layer — 44 thousand vectors, 167 megabytes, 83 minutes of processing, a new component in the system — yielded one more document in our question set. One line of ranking in the word search yielded two documents and three pages.
And worse: the established technique of combining the two searches made things worse, from 15 to 13. It has a name — reciprocal rank fusion, or RRF, the standard recipe for marrying word search with vector search — and the reason it failed is arithmetic, not opinion: the formula adds the inverse of the position in each list, which rewards the mediocre document in both over the excellent one in only one. On questions containing a contract number, where the word search nailed the document in first place, the combination knocked that first place down. It is a good technique, published, widely recommended — and wrong for our archive, which is full of identifiers. Only measurement could say so.
So we kept the vectors loaded and out of the search. Today they serve three useful purposes, all measured:
- Similar documents. For each document, the eight closest in the archive. One donation agreement pointed to another agreement on the same template as its neighbour, in a tenth of a second.
- Near-duplicates. 35,550 pairs. Useful for cleaning up the archive: two certificates from the same supplier, two submissions of the same receipt.
- Triage. An unclassified document receives a suggested type from its neighbours, for a person to confirm with one click.
A side effect that became a task of its own: the near-duplicates screen took 25.8 seconds. A database index on the fingerprint column was missing — four lines of migration. It dropped to 0.36 seconds. A new layer over an old system reveals what was lying dormant.
Stages 5, 6 and 7 — The assistant comes in through the front door
Here AI finally enters, and the question that guided us was: how can an assistant work with our data without breaking any of our access rules?
That door has a name: MCP, the Model Context Protocol. It is an open standard, published by Anthropic in late 2024 and now spoken by more than one assistant maker, which defines how an AI discovers and uses the tools of an external system. Before it, connecting an assistant to a system meant writing a bespoke integration for each assistant. With it, the system publishes its tools once, and any client that speaks the protocol finds them on its own.
Our MCP server is built and live. It is not a plan or a prototype: 86 tools published across the Cockpit’s 13 modules, and the proof was run against the system in production — the client asks for the tool list and receives all 86; it runs a read and receives the same answer the screen would give; it asks for a dry run of a write and receives the predicted effect with nothing happening; and, when it asks for what its scope does not reach, it gets a 403 naming the exact permission that was missing.
And this is what gives meaning to the seven preceding stages: the archive stopped being a storeroom and became reachable. The 24,549 documents, the 60,702 pages of text, the 53 columns of prose written by our team, the catalogue that says what each field means and the system’s 13 modules are, today, within reach of an AI assistant — through the same door and with the same permissions a person has. An educator can ask in plain Portuguese where the clause of a contract is and receive the document and the page. Whoever handles accountability asks for the receipt of a payment without opening a single folder. And none of that required opening the database to the AI, sending a document outside, or granting anyone a permission they did not already have.
For an organisation our size, that is the part I did not know we would manage. An MCP server in production is not what people expect from a periphery civil society organisation running on eight processing cores with no platform team. We managed it — and we managed it because we did not start here. It took having the text stored (stage 1), a search that worked (stage 2), a catalogue that explained the data (stage 4) and measured vectors (stage 3) for the door of stage 5 to be worth opening. Whoever opens that door first hands the assistant an archive it cannot read, over a database it cannot interpret. The door is the easy part. What makes it a useful door is everything that comes before.
What allowed us to get there without breaking a single access rule has three parts.
Few named tools, never free access to the database. We started with three: search documents, read a document by page, consult the catalogue. Not thirty. Free querying of the database is prohibited in writing. An assistant with free access makes more mistakes, and makes them silently.
A real login, not a password pasted into a file. The person clicks “connect” in their assistant, is taken to the Cockpit, logs in as they would on screen, sees exactly what is being requested and authorises it. The access granted is necessarily less than or equal to what they already have in the system. Nobody gains permissions by connecting an assistant.
This is OAuth 2.1 — the same “sign in with” standard everyone has already used, and the one the MCP specification provides for precisely for remote servers like ours. The alternative would be each person generating a token and pasting it into a configuration file, and the reason we refuse is prosaic: a pasted token ends up in a chat, in a history, in a screenshot. But there is a larger consequence, of reach. Because connecting became a single click, the server could be distributed to the whole house as a Claude plugin, and not only to those who know how to edit a configuration file. That changes who the audience is — and it is exactly why the masking of personal data and the calibrated expectations further on stop being refinements and become obligations. A wrong answer delivered with conviction lands hardest on whoever lacks the background to doubt it.
Every action classified by how much it hurts. Today there are 86 tools and 230 actions across the 13 modules, and each one was classified by opening the code, one by one — twice, by independent teams, because the first pass let six cases through:
- 118 reversible actions — they execute and are logged. Creating an agenda, registering a member.
- 29 actions with effects outside — without explicit confirmation, the system returns what would happen and stops. Sending an email to someone, sending a contract for signature, touching a corporate account.
- 14 actions that move money or assets — they require a logged dry run and human confirmation. Settling an invoice, reversing an entry, changing who is responsible for a piece of equipment.
We could have left the red ones out. We preferred the opposite: they are included, obliged to show in advance exactly what they are going to do. Taking an action off the table hides the risk; forcing it to explain itself first makes the risk auditable.
There is one architectural decision that may be the most reusable part of all this: the tools do not talk to the database — they call the same routes the screen calls. When someone asks the assistant to register a member, the request is dispatched internally to exactly the route the Cockpit’s form would use, carrying the identity of whoever authorised it. The consequence is that no validation, no permission check and no audit record had to be rewritten for the AI: it all runs by construction, because it is the same code. The alternative — reimplementing the rules on the assistant’s side — creates a second source of truth, which comes apart the day someone changes a rule in only one place. It is the same lesson as rule 4, just below, applied again.
And three precautions that exist because of one simple difference: a person clicks once; an assistant repeats the call when the network fails.
- Idempotency key. Every write carries an identifier for the operation. Repeating the call returns the result of the first one — it does not create a second invoice to pay.
- A dry run before the action. Every write knows how to answer “what would happen if I did this”, without doing it. For the red actions the dry run is not optional: without one logged beforehand, execution is refused.
- Lineage. Every write records who authorised it, which agent executed it, when, with what data and with what result. An action without a trail does not exist in the design.
This is where the stage 4 catalogue repaid its own investment in a way we had not foreseen. The 230 actions live in a registry that points, one by one, to the route each of them calls — and a continuous integration test checks that registry against the route catalogue at every change. A renamed route stopped being something that breaks the assistant in production and became something that fails the change before it lands. The first run of that test found six routes the registry cited that no longer existed — and, behind them, a defect in the catalogue generator itself, which came to be treated as a logged defect rather than a curiosity.
The five rules we did not negotiate
If this text is useful for anything beyond these walls, I hope it is because of this list.
- No document leaves home. All PDF reading and all text recognition happen inside our own infrastructure. We accept the cost: the ceiling on quality becomes that of our own tool, with no plan B for poor scans.
- Permission belongs to the attachment, never to the content. The same file may be attached in contexts with different owners. If the search answered by the file’s fingerprint, someone with access to one attachment would read the text from another context. The search returns the attachment, and authorisation is resolved before reaching the text.
- Masking on the way out, never in storage. When a passage goes to an external service, CPF, CNPJ, email and telephone are replaced, and a record is kept of how much data left. Storing a second “clean” copy would create two sources of truth — and masked text in the database destroys search even for those with permission.
- The search inherits access control; it does not reimplement it. That was the reason we kept meaning-based search inside our own database rather than contracting a separate service. An index that lives outside does not know who can see what: either it copies our permission rules — and comes apart the day someone changes a rule in only one place — or it leaks legal, HR and whistleblowing material. We did consider a dedicated vector database, and Qdrant was the candidate; we refused it for that reason, not for performance. Inside pgvector, filtering by what a person may see is a
JOIN; outside it, it is a second copy of our permission model. For the same reason we did not stand up Elasticsearch for the word search. And we wrote down what would change our minds: passing five million passages, or neighbour search taking more than 300 milliseconds, or a real need arising to isolate archives in separate indexes. A decision with no review criterion is stubbornness with a due date. - Every answer cites the page. Without a verifiable citation, the answer is the AI’s word. And the AI’s word does not account to any funder.
What we have today, in numbers
All measured on the day this text was written, against the system in production:
- 30,099 attachments, 24,549 distinct contents, 21,729 PDFs
- 60,702 pages of text stored, 171.7 million characters
- 4,326 pages that required character recognition
- 5,333 texts coming from fields written by members of the team
- 323,776 words in the rarity table
- 77,068 passages converted into vectors
- 44,164 vectors of 384 dimensions, generated by a model that runs on our own machine
- 1,204 API routes catalogued — 837 route-and-method combinations serve to read, 1,392 to write
- 86 AI tools published through MCP, 230 actions classified
- and the server where all this runs has 8 processing cores
That last number is not an anecdote, it is the main engineering fact of this project. We are not a technology company with infinite cloud. The initial mass effort to read documents ran on a machine rented by the hour and destroyed at the end, with read-only permission, without opening a single new port in production. After that, the daily processing runs in production with a single worker and a ceiling per pass.
And proof that the design holds: between the first measurement and today, our archive more than doubled — because the full export of an accounting system we are switching off came in. The design absorbed that with no changes at all. A process that only works at the volume it was built for is not a process.
Part 3 — Now, at last: the questions we want to ask
Once we had brought the whole database together, we began asking ourselves: what can come out of this? What answers could the context and the safe cross-referencing of this information reveal to us?
We speculated a good deal. One idea was to think about how artificial intelligence could help us carry out a predictive analysis based on the trajectory of a young woman entering one of our programmes — that is, using the history of those who have already been through here to estimate what may happen to those arriving now. Looking at Casa Hacker’s history and at the young people who have already dropped out, what could that data, handled with every care, reveal to us about this new young woman? How could it help us welcome someone with a higher likelihood of dropping out?
Here are some concrete examples of what we would like to be able to see:
- Do members from a specific area arrive late more often because of the territory’s urban problems — unpaved streets, recurrent flooding on rainy days?
- Do members from a particular neighbourhood present more respiratory problems because of a lack of infrastructure? And could we, on that basis, follow up more closely with families before it turns into recurrent absence and then drop-out?
- Do young people who move house frequently have a higher risk of dropping out in the first few months?
- Is there a pattern of silent disengagement — attendance falling away little by little, with no single absence that draws attention, but visible across a historical series?
- Do the times of highest absence coincide with the distance to public transport for particular families?
- Are there months of the year — school enrolment season, the informal working season for guardians — in which drop-out becomes more likely?
- Looking at siblings or relatives who have already been through Casa Hacker, is there a family pattern we could anticipate?
And there is a second group of questions, more ambitious, which can only be answered by cross-referencing our data with school reports and with public datasets:
- Do young people whose performance drops in a specific subject in the two months before joining a technology programme leave sooner? That would allow targeted educational support before the drop-out shows up in our attendance records.
- Cross-referencing declared family income with attendance, is it possible to identify whether young people from more vulnerable families are absent more at specific times of the month — indicating the right moment for a grant or a travel allowance?
- Do young people with a history of repeating a school year need a different pedagogical welcome from the very first month?
- Cross-referencing family composition with absences may reveal whether domestic caring responsibilities — which fall on girls far more often — explain absences that, at first sight, would look like a lack of interest. That hypothesis alone would justify the effort.
- Do young people from schools with high teacher turnover (public data from the school census) have more difficulty adapting, suggesting extra levelling-up support?
- Do young people who live further away, even with stable school reports, drop out more when transport fares rise — something that only appears when geographical data is cross-referenced with the economic calendar?
- And, among those who completed the programmes successfully, is there a recurring profile? A map of protective factors, and not only of risk?
I confess I believe this is exactly where artificial intelligence ought to act: showing us, from accumulated history and data, how to welcome each person better, and identifying patterns that our human capacity, on its own, would struggle to see.
We are not expecting AI to do trivial things for us, like reading and answering our emails — that does not interest us. What interests us is: how do we go further? How do we receive people better? How do we identify risks before they happen — not to exclude anyone, but to welcome them with greater care? If artificial intelligence does not serve that purpose at some point, I feel we will have failed.
What the yardstick says about our own ambition
If this project taught me one thing, it was to measure before asserting. So I applied the same yardstick to my own ambition, and the result deserves to be published.
What we already have:
- 1,210 registered members, 2,756 enrolments in 216 activities
- 24,309 attendance records across 2,083 sessions, from June 2022 until today
- 928 members with attendance recorded, and 733 of them with five or more records — that is, with enough of a series to draw a curve
- 3,509 individual follow-up records
- a usable address for 921 of the 1,210 members (76%), which makes territorial cross-referencing possible for three in every four
The limit we placed on ourselves
It needs to be written down, because it concerns children and adolescents.
Of our members with a recorded date of birth, 376 are under 18. Assigning a “drop-out risk score” to a teenager is not a neutral act, and a system that does so without safeguards very quickly becomes a machine for labelling young people from the periphery. We have seen that film before in credit, in public security and in education.
Our safeguards, then:
- The prediction never becomes the young person’s grade. At most it becomes an invitation to a conversation and a signal to the teaching team. Never a criterion for entry, for staying, or for selection.
- Never to exclude; always to welcome. If a number from this system is ever used to remove someone from a programme, the system has failed and should be shut down.
- The person decides, always. The machine prepares, evidences and cites. The one who decides is the educator who knows that family. This is not diplomacy — it is the technical design: every relevant action by the assistant requires human confirmation.
- Protective factors, not only risk factors. The question “what works for those who complete?” is more useful, and less dangerous, than “who is going to give up?”.
- Health data only with explicit authorisation, and guardian data with a clear legal basis. We are governed by the LGPD and we treat that as a design constraint, not as a footnote clause.
- No document and no identifiable personal data goes to an external service without passing through the masking described above.
Communicating better with those who invest
There is a point as important as the previous ones: how artificial intelligence can help us communicate better with our funders, generate data of higher quality and more auditable, give greater transparency about our spending, demonstrating more intelligently how we operate day to day for those who invest alongside us.
That part is the one that has already changed in practice, and it changed because the preparation was done in the right order. Almost all of our income is earmarked funding — tax-incentive projects, restricted donations, calls for proposals — and every expense is evidenced document by document. What used to be archaeology in folders is today a search that returns the document and the page, in milliseconds, respecting who can see what. A duplicate receipt shows up as a pair, rather than as an accidental discovery during the check on the eve of the deadline.
And there is a direct economic consequence: by 2027 we plan to switch off seven proprietary platforms that we currently pay for by subscription. We are trading a recurring licence and a fragile data processing agreement for an archive that is our own, auditable and under our responsibility.
Why we are telling this
Because the method is the part that serves other organisations, and it is the long-term outcome we wrote into our own planning: a data preparation method, documented and replicable by other civil society organisations.
No organisation in our field has infinite cloud. Almost all of them have a large archive and a small team. What transfers from here is not code — it is the order of the stages and the habit of measuring before deciding. And it is worth insisting on a point the list of pieces above already made quietly: there is not a single paid, proprietary or restricted component in this architecture. Postgres, pgvector, unaccent, poppler, Tesseract, OCRmyPDF, an open embeddings model and an open protocol. What it cost was not licences — it was deciding the order, writing the yardstick and accepting what it answered. Across eight stages, six plausible hypotheses of ours were disproved by measurement: increasing the scanning resolution (worse), cleaning the image before reading it (destroys 28% of the characters), combining the two searches (worse), showing one page per document (worsens page precision). Each refutation is written inside the code, with the number, at the exact spot where someone would try it again.
The cheapest value a measurement produces is not discovering what to do. It is preventing three changes that looked like improvements.
We say that the peripheries are not a place of waiting, they are a place of beginning. That applies to our relationship with technology too: we are not waiting for some grand artificial intelligence solution to arrive from outside, ready-made for our reality. The turning point does not come from outside. We are putting our house in order so that, when it does serve, it serves for real — to welcome people, and not to give us pretty answers about data we did not understand.
These are some of the challenges that I believe should guide us as a purpose.
The full technical account of this work, with all the measurements, architectural decisions and a glossary of the terms used here, is published as a whitepaper. The set of 42 questions we use as a yardstick is version-controlled and can be run by anyone — it was made deliberately so that another organisation can repeat the measurement on its own archive.
