[{"data":1,"prerenderedAt":232},["ShallowReactive",2],{"seite-\u002Fen\u002Fopen-source\u002Fdms-selbst-gebaut\u002F":3},{"id":4,"title":5,"body":6,"description":223,"extension":224,"meta":225,"navigation":226,"path":227,"robots":228,"seo":229,"stem":230,"__hash__":231},"seiten_en\u002Fopen-source\u002Fdms-selbst-gebaut.md","Three programs or one of my own",{"type":7,"value":8,"toc":213},"minimark",[9,13,17,22,25,44,47,51,54,57,61,64,86,89,93,96,107,125,132,135,146,150,153,156,159,163,166,169,172,175,178,182,196],[10,11,5],"h1",{"id":12},"three-programs-or-one-of-my-own",[14,15,16],"p",{},"In February 2026, I wanted to get rid of my paperwork: invoices, contracts,\ndoctor's letters, tax documents. Photograph them or upload them as a PDF, and\nthe rest should happen by itself. Beforehand I evaluated more than 30\nopen-source projects, and afterward I built it myself. This article explains\nwhy, and what things look like half a year later.",[18,19,21],"h2",{"id":20},"five-requirements-and-a-weak-laptop","Five requirements and a weak laptop",[14,23,24],{},"The list was short, and every point was mandatory:",[26,27,28,32,35,38,41],"ol",{},[29,30,31],"li",{},"Upload of photos and PDFs with an archive behind it",[29,33,34],{},"Text recognition (OCR) via Mistral OCR",[29,36,37],{},"Tags that follow from the content, without me setting them",[29,39,40],{},"Full-text search and a chat that answers questions across all documents",[29,42,43],{},"A single interface for everything",[14,45,46],{},"Point 2 came from the hardware. My laptop has 4 GB of graphics memory and\nlittle RAM. MinerU needs 16 to 32 GB, Docling has peaks of 3 to 4 GB, PaddleOCR\nwants a decent GPU. That ruled out local text recognition in good quality.",[18,48,50],{"id":49},"the-pattern-in-the-evaluation","The pattern in the evaluation",[14,52,53],{},"I evaluated document archives like Paperless-ngx, Papermerge, Docspell, Mayan\nEDMS, and Teedy, RAG tools like AnythingLLM, kotaemon, Open WebUI, Dify, and\nRAGFlow, plus ERP systems with a document module. Each project got a row, each\nrequirement a column.",[14,55,56],{},"After a few rows, the pattern was clear. The archives could archive, tag, and\nsearch, but not answer questions. The RAG tools could answer questions, but not\nfile anything: no folders, no tags per document, no archive to browse. Each\ngroup had exactly half.",[18,58,60],{"id":59},"the-best-combination-was-three-programs","The best combination was three programs",[14,62,63],{},"The closest was Paperless-ngx with two extensions:",[65,66,67,74,80],"ul",{},[29,68,69,73],{},[70,71,72],"strong",{},"Paperless-ngx"," as the archive with full-text search",[29,75,76,79],{},[70,77,78],{},"paperless-gpt"," for Mistral OCR and tags via a language model",[29,81,82,85],{},[70,83,84],{},"paperless-ai"," for the chat across all documents",[14,87,88],{},"That is three programs with three interfaces, three configurations, and two\nindexes that have to match the archive. To upload a document, you go to one\ninterface; to ask a question, to another. That is exactly what I did not want.\nRequirement 5 was the reason for building my own.",[18,90,92],{"id":91},"what-was-built-instead","What was built instead",[14,94,95],{},"The DMS is a Vue 3 interface with PrimeVue on Supabase: PostgreSQL with\npgvector for the vectors, Storage for the files, Edge Functions for the\nprocessing. An upload goes through four stages:",[97,98,103],"pre",{"className":99,"code":101,"language":102},[100],"language-text","upload-document → process-ocr → extract-data → generate-embed\n","text",[104,105,101],"code",{"__ignoreMap":106},"",[14,108,109,112,113,116,117,120,121,124],{},[104,110,111],{},"upload-document"," computes the SHA-256 of the file and rejects duplicates.\n",[104,114,115],{},"process-ocr"," extracts PDFs with a text layer locally and sends only photos and\nscans to Mistral OCR. ",[104,118,119],{},"extract-data"," determines the document type, extracts\nfields like amount and deadline according to a schema, and assigns tags.\n",[104,122,123],{},"generate-embed"," splits the text into chunks of 1000 characters that overlap by\n200, and stores their vectors.\nIf a stage fails, the error is recorded on the document.",[14,126,127,128,131],{},"The search combines both things PostgreSQL provides: German full-text search\nwith ",[104,129,130],{},"tsvector"," and vector search with pgvector, weighted 0.4 and 0.6. The chat\ngets the matching chunks through the same search and names, for each answer,\nthe documents it comes from.",[14,133,134],{},"Because everything is in one database, the permissions also live in one place.\nA team can restrict document types like salary statements to admins. The search\nfunction filters by the same rules as the document list, so a restricted\ndocument ends up neither in results nor as a source in the chat. With three\nprograms, each index would have had to replicate the archive's permissions.",[14,136,137,138,145],{},"No part of the application calls Mistral directly. All calls go through a\n",[139,140,144],"a",{"href":141,"rel":142},"https:\u002F\u002Fgithub.com\u002Fgstrainovic\u002Fai-proxy",[143],"nofollow","proxy of my own",", which holds the key\nand counts usage per organization.",[18,147,149],{"id":148},"was-mistral-ocr-the-right-choice","Was Mistral OCR the right choice?",[14,151,152],{},"In February, it was the only one the laptop allowed. In September, I measured\nagain: Mistral OCR against vision models that Infomaniak and kvant offer in\nSwitzerland, with synthetic and real documents, clean and distorted.",[14,154,155],{},"On clean scans, large vision models like Kimi K2.6 and Qwen3.5 are as good or\nbetter, but slower and more expensive. As soon as the source gets difficult,\nMistral OCR is ahead: on distorted pages with dense tables, it found 91 percent\nof the cells, the vision models at most 82. On Swiss documents like QR bills\nand salary statements, it made the fewest character errors, and on real phone\nphotos it found 97 to 99 percent of the fields.",[14,157,158],{},"For data protection, Mistral is not the best choice, but a permissible one.\nThe servers are in France, and the Swiss Data Protection Act (nDSG) allows\ntransfers to the EU without further safeguards. A Swiss provider would be\nbetter in terms of location. The rule for the test was therefore: quality\nbefore location. The details will follow in a separate article.",[18,160,162],{"id":161},"what-things-look-like-today","What things look like today",[14,164,165],{},"For this article, I repeated the evaluation in September 2026. The field moves\nfast: some projects have gained Mistral OCR, Papermerge is looking for new\nmaintainers, and OpenKM now distributes its community edition only without\nsource code.",[14,167,168],{},"The biggest change was at Paperless-ngx. Version 3.0 came out in July 2026 with\nbuilt-in AI: suggestions for title, tags, and document type, and a\nchat across one or more documents, with links to the sources. The language\nmodel is Ollama or any OpenAI-compatible API. Paperless-ngx itself offers cloud\ntext recognition only via Azure. Mistral OCR and tags at ingestion are still\nprovided by paperless-gpt. According to its README, paperless-ai is no longer\nmaintained.",[14,170,171],{},"Papra, a lean archive, has had Mistral OCR and tags via a language model since\nJuly. It only lacks the chat.",[14,173,174],{},"So three programs have become two. If you want a private archive with chat\ntoday and can live with two interfaces, try Paperless-ngx 3 with paperless-gpt\nfirst. It is more mature than my project can become in half a year.",[14,176,177],{},"Building your own pays off if one of the five requirements is non-negotiable,\nor if the archive is meant to run for others. Organizations with roles,\npermissions per document type all the way into the chat, usage per\norganization: that is hard to bolt onto three connected programs.",[18,179,181],{"id":180},"what-carries-over","What carries over",[65,183,184,187,190,193],{},[29,185,186],{},"Write down the requirements as columns before filling the first row, and\nnote the source for each cell.",[29,188,189],{},"Look for patterns, not for the winner. \"Each group has half\" says more than\nthirty individual verdicts.",[29,191,192],{},"Count combinations: three programs that together can do everything are a\nsolution of their own with their own maintenance effort.",[29,194,195],{},"Repeat the evaluation before a decision if it is older than a few months. In\nthis field, a lot changes in half a year.",[14,197,198,199,204,205,208,209,212],{},"The source code is open:\n",[139,200,203],{"href":201,"rel":202},"https:\u002F\u002Fgithub.com\u002Fgstrainovic\u002Fdms",[143],"github.com\u002Fgstrainovic\u002Fdms",". The complete\nevaluation with both versions is in ",[104,206,207],{},"docs\u002Fevaluation.md",", the processing in\n",[104,210,211],{},"supabase\u002Ffunctions\u002F",".",{"title":106,"searchDepth":214,"depth":214,"links":215},2,[216,217,218,219,220,221,222],{"id":20,"depth":214,"text":21},{"id":49,"depth":214,"text":50},{"id":59,"depth":214,"text":60},{"id":91,"depth":214,"text":92},{"id":148,"depth":214,"text":149},{"id":161,"depth":214,"text":162},{"id":180,"depth":214,"text":181},"Why I built my document archive with OCR, tags, and chat myself instead of connecting Paperless-ngx and extensions. What the evaluation showed and what things look like half a year later.","md",{},true,"\u002Fopen-source\u002Fdms-selbst-gebaut",null,{"title":5,"description":223},"open-source\u002Fdms-selbst-gebaut","RRK-yDFLPvYWaN-mH_BSDkf-Gh5Qnsh70mN8F7av7gQ",1790517213246]