Documented repository for the Andromeda File Analyzer: a Next.js front end, .NET 9 Web API, and Python invoice parsers that ingest files, extract invoice data, and store results in PostgreSQL.
In this web-based application, users can upload an invoice in the form of an image file. The image file will then be fed to an LLM (DonutAI or MinerU, the latter is a more functional option) of the user's choosing. The LLM will then extract all of the relevant information to a JSON format, which will then be available for the user to see. Once the user confirms that the information is correct, the JSON data will then be available to send to a database.
Users can upload multiple files to be fed into the LLM, and these can be viewed in a table or grid view on the main page. In addition, the user can sort these files by different values. On the backend, users can easily implement their own LLM to work with the web app through a python file.
api/– ASP.NET Core Web API (Program.cs, EF Core models, controllers) that owns the database and file ingest endpoints.api.Tests/– xUnit tests that exercise the invoice workflow.ai-demo/– Python preprocessing scripts:app_mineru.py(MinerU pipeline) andapp_donut.py(DonutAI sample).ml-invoice-scraper/– Next.js UI that uploads documents, reviews parsed results, and can send accepted invoices to another API.Informational/API Contract.md– Original API contract and flows used to scope the project.
- .NET 9 SDK
- Node.js 18+ (for Next.js)
- Python 3.10+ with
pip(for MinerU/Donut scripts) and system dependencies required by MinerU - PostgreSQL 15+ running locally
gitand a modern browser
- Clone and install tools
- Install the prerequisites above.
- Optional:
dotnet tool install --global dotnet-ef(to run migrations manually).
- Database
- Create a local database/user, e.g.:
createdb MyAppDbcreateuser user(or your own role) and give it access.
- Set the connection string via either:
api/appsettings.Development.json->ConnectionStrings.Postgres- or environment variable
ConnectionStrings__Postgres="Host=localhost;Port=5433;Database=MyAppDb;Username=davis;Password=<pw>"
- Tables are auto-created at API startup through
db.Database.MigrateAsync(). You can also runcd api && dotnet ef database update. - (Optional Lakebase) To enable Databricks Lakehouse inserts, also set
ConnectionStrings:Lakebase(or envConnectionStrings__Lakebase) to a Databricks/Postgres connection string, e.g.Host=<databricks_host>;Port=5432;Database=databricks_postgres;Username=<user>;Password=<pw>;SSL Mode=Require.
- Create a local database/user, e.g.:
- Python model helpers
cd ai-demo- Install dependencies for the chosen model (MinerU:
pip install mineru loguru bs4 python-dateutil; Donut:pip install transformers accelerate pillow datasets). GPU acceleration is optional but recommended for performance. - If Python is not on PATH or scripts move, set env vars:
PYTHON_EXE=/full/path/to/pythonPYTHON_SCRIPT=/full/path/to/app_mineru.py(or override inappsettings.*viaPythonProcessing:ScriptPath).
- Run the API
cd apidotnet restoredotnet run --launch-profile http
- Run the Next.js UI
cd ml-invoice-scraper- Update
.env.localNEXT_PUBLIC_API_BASE_URLto match the API base (defaults tohttp://localhost:5207/api). npm installnpm run devthen openhttp://localhost:3000.
- Configuration:
Program.csreadsConnectionStrings:Postgres; optionalPythonProcessing:PythonExecutableandPythonProcessing:ScriptPathoverride Python. - Entities / Tables (snake_cased in Postgres):
invoices: vendor/recipient contact info, description, date fields (invoice_date,due_date,discount_date,fee_date,file_date,ingest_date), money fields (subtotal,discount_percent,discount_amount,tax_percent,tax_amount). Defaults are"N/A"strings and0numerics;ingest_datedefaults tonow() at time zone 'utc'.invoice_items: per-line items withitem_name,quantity,price,total,description, and timestamp mirrors.file_names: stored binaries and metadata (file_name,consolidated_file_name,content_type,file_data, FKinvoice_id). Cascade delete removes children with their invoice.
- Controllers:
POST /api/invoice-files- Upload a file (fileform field) and optionalmodel(MinerUorDonutAI). Stores binary, runs Python parser, and seeds invoice + items from the JSON output.GET /api/invoice-files- List stored files (metadata only).GET /api/invoice-files/{id}- Download a stored file.DELETE /api/invoice-files/{id}- Remove file (and invoice if no other files remain).POST /api/invoice-files/{id}/rescan- Re-run parsing on an already stored file.POST /api/invoices- Create a blank invoice with defaults.GET /api/invoices/{id}- Fetch invoice with items.PUT /api/invoices/{id}- Update invoice fields and replace items.
- Databricks Lakehouse / Lakebase bridge (from
POC_dbricks_lakebase):LakebaseClientopens an Npgsql connection usingConnectionStrings:Lakebase(or envConnectionStrings__Lakebase) and inserts into aninvoicestable that must expose:invoice_id,vendor_name,recipient_name,vendor_email,vendor_phone,recipient_email,recipient_phone,invoice_date,due_date,discount_date,fee_date,ingest_date,file_date,vendor_address,recipient_address,subtotal,discount_percent,discount_amount,tax_percent,tax_amount,description,created_at. Date fields are sent as ISO 8601 strings.POST /api/lakebase/invoiceswrites whatever invoice payload you send and returns the Lakehouseid. Example:curl -X POST http://localhost:5207/api/lakebase/invoices -H "Content-Type: application/json" -d "{\"invoiceId\":\"123\",\"vendorName\":\"ACME\",\"recipientName\":\"Contoso\"}"POST /api/lakebase/invoices/{invoiceId}/syncloads the invoice from the local Postgres database and forwards it to Lakebase with the same insert routine.
- Landing page lets users pick AI model (MinerU or DonutAI) and upload files; uploads hit
/api/invoice-files. - Scans view pulls
/api/invoice-files, shows previews, allows delete/rescan, and exposes "Upload Selected" to forward accepted invoices to Lakebase (or another target if you swap out the handler). - Uses
NEXT_PUBLIC_API_BASE_URLfor all backend calls; adjust when pointing to hosted APIs. - Assets (logos/background) live in
public/; styling is primarily tailwind-based in React components. - Model options can be provided via NEXT_PUBLIC_MODELS as JSON (e.g., [{"label":"MinerU","value":"MinerU"},{"label":"Donut AI","value":"DonutAI"}]); otherwise the UI falls back to MinerU/Donut AI defaults.
app_mineru.pyruns the MinerU pipeline and normalizes invoice fields; defaults to resolving script from../ai-demo/app_mineru.py.app_donut.pyloads the Donut model to extract invoice JSON from an image.- API resolves Python via
PYTHON_EXEor PATH; logs stdout/stderr to help debug parser issues.
- Flow: the UI sends a
modelform field toPOST /api/invoice-files;InvoiceFilesController.ResolvePythonScriptPathpicks the Python entrypoint based on that value and runs it with arguments<inputPath> <outputPath>. The API reads the JSON atoutputPath(or falls back to stdout) and maps it into invoice fields. - Build a script: add a new CLI entrypoint in
ai-demo/(e.g.,app_newmodel.py) that accepts the two args above and writes JSON. Aim to emit adataobject with keys likevendor_name,vendor_email,vendor_phone,vendor_address,customer_name/recipient_name,invoice_date,due_date,subtotal,tax_amount, and anitemsarray where each item hasname,description,quantity,unitprice, andtotal. The extractor is tolerant of slight key differences (seeInvoiceFilesController.TryExtractInvoiceDetails). - Wire it into the API: extend the switch in
ResolvePythonScriptPath(api/Controllers/InvoiceFilesController.cs) to map your newmodelvalue to the new script name. If you prefer not to change code, you can temporarily point the existingPYTHON_SCRIPTorPythonProcessing:ScriptPathto your script and reuse an existing model name. - Update the UI: add the new model option to the dropdowns and TypeScript unions in
ml-invoice-scraper/src/app/page.tsxandml-invoice-scraper/src/app/scans/page.tsx. Ensure themodelstring you send matches the API switch case (case-insensitive). - Dependencies: document the new model's Python packages under the Python setup step and install them in
ai-demo(e.g.,pip install <packages>). Test with a localcurl -F "file=@sample.pdf" -F "model=NewModel" http://localhost:5207/api/invoice-files.
- The "Upload Selected" button (see
ml-invoice-scraper/src/app/scans/page.tsx) now calls${NEXT_PUBLIC_API_BASE_URL}/lakebase/invoices/{invoiceId}/syncfor each accepted invoice ID, reusing the backend Lakebase sync endpoint. - Success/failure is surfaced via alerts; make sure the API base URL points to a backend with a valid
ConnectionStrings:Lakebaseor uploads will fail. - If you need to target a different downstream system, replace the
fetchinhandleUploadSelectedwith your own endpoint (optionally driven by an env var such asNEXT_PUBLIC_UPLOAD_ENDPOINT).
- Ports: API defaults to
http://localhost:5207(http profile) andhttps://localhost:5001(https profile). UI defaults tohttp://localhost:3000. - CORS is permissive in Development; tighten policies before production.
- Large files:
POST /api/invoice-filesallows up to ~50 MB via[RequestSizeLimit(50_000_000)]. - Python temp files are cleaned after each request; ensure the service account has permission to write to the OS temp directory.