Skip to content

About

Neon batch document classifier for Microsoft-Decision-1 and Jev, plus an arcade where a model, an algorithm or you fly the ship

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

DECISION//CLASSIFIER

Drop a pile of documents. Get answers. Stay neon.

The neon Decision Classifier UI with 18 classified documents, a scoreboard and confidence bars

Drag in hundreds of PDFs, Word files, spreadsheets and more. Each one is turned into text, sent to a decision model, and stamped with a type (invoice, credit_note, purchase_order, ...) and a confidence bar. Two models are wired up, and you pick one per batch:

Model Where it runs Confidence score
Decision-1 Microsoft-Decision-1 Azure AI Foundry deployment yes, per-type probabilities
Jev ~typesafe/jev-latest (pinnable) OpenRouter decisions endpoint yes, per-type probabilities

Both are "System One" models: you give them text and a fixed set of options, and they pick one, fast. No chat, no free text, no hallucinated labels. The answer is always one of your types, or unknown.

There is also an arcade. It started as a joke and then got out of hand. Jump to the arcade.

Quick start

uv sync
cp .env.example .env        # then fill it in (see Configuration)
uv run server.py            # http://localhost:8000

Open the page, drop in samples_100/ (100 fictional PDFs) and hit Classify. Set PORT to use a free port of your choice.

The classifier UI

  • Drop files or whole folders, or click to browse. Hundreds at once is fine: files are sent one by one, 1 to 8 in parallel.
  • Live results with a progress bar, a scoreboard per type, and a confidence bar per row. Anything under 70% turns red ("needs review").
  • Filter by type or errors, and export CSV when done.
  • Retries that behave: on a 429 every worker pauses together and honours the retry-after header, so a rate-limited deployment is not hammered.
  • Two themes: Neon (above) and 8-Bit, with a switch in the corner. Your choice is remembered.

The same page in the 8-bit Nintendo theme

Supported files: PDF, DOCX, PPTX, XLSX, XLS, HTML, TXT, MD, CSV, JSON. Text is extracted locally with Microsoft's MarkItDown, and the first 30,000 characters go to the model. Scanned PDFs with no text layer produce nothing and would need OCR.

Your own types

Edit types.json. Each type is an id and a one-line description, and the model chooses among them. unknown is added automatically as an "I don't know" option, so you don't define it.

{
  "types": [
    {"id": "invoice", "description": "Request for payment for delivered goods or services"},
    {"id": "credit_note", "description": "Correction or reversal of an earlier invoice"}
  ]
}

Command line

The same pipeline runs without the UI and writes one JSON line per document:

uv run classify.py ./documents --catalog types.json --output classifications.jsonl
uv run classify.py ./samples --min-confidence 0.70      # flag shaky answers for review

It scans folders recursively (--non-recursive to stop that), keeps going when a file fails, and records the error on that line. --max-chars changes how much text is sent. The CLI uses Decision-1 only.

The arcade

/game is a Space Invaders clone with three pilots, and you can switch pilots mid-game.

Decision Arcade in neon, the algorithm pilot clearing a wave Decision Arcade in 8-bit mode

Pilot How it plays Latency per decision
You Arrow keys or A/D, Space to fire, P to pause, M for sound depends on you
Model The game describes the board in words; Decision-1 or Jev answers with a move and a fire decision about 0.35 to 0.5 s (both models)
Algorithm A classic planner: every frame it simulates the next 1.2 s for 30 candidate positions and plays the first move of the best plan about 0.1 to 0.2 ms

The honest result, from one 60 second run each at normal speed, so treat it as a vibe and not a benchmark:

  • Algorithm: 1650 points, level 2, no lives lost.
  • Decision-1 with the game waiting for it: 250 points.
  • Decision-1 racing the clock: about 120 points, then dead.

The models are not dumb, they are late. The game runs at 60 frames a second and an answer takes 30 to 90 frames, and a text description gives them no sense of where a shot will be in half a second. That is why "Game waits for model" exists: it freezes the game while the model thinks, so you can judge its decisions without the lag. The planner needs neither trick, because it can look ahead for free.

The takeaway: these models shine at choosing from a list, like document types. For a 60 fps reflex game, write a planner. Both live in the same page so you can watch the difference.

There is sound too, synthesised in the browser (no audio files): laser, explosions, and the four-note alien march that speeds up as the aliens thin out. Square waves in 8-Bit mode, saw waves in Neon.

Configuration

Settings come from .env (real environment variables win over it). .env is git-ignored, and .env.example is the template.

Variable Needed What
AZURE_ENDPOINT yes Foundry resource endpoint, e.g. https://YOUR-RESOURCE.services.ai.azure.com
DEPLOYMENT_NAME yes Name of your Decision-1 deployment
AZURE_API_KEY one of API key. Leave empty to use Entra ID (az login, scope https://cognitiveservices.azure.com/.default)
OPENROUTER_API_KEY optional Turns on Jev in the UI
OPENROUTER_MODEL optional Defaults to ~typesafe/jev-latest. Pin a version such as typesafe/jev-1.13 if you rely on confidence thresholds
PORT optional Server port, default 8000

The Foundry call is POST {AZURE_ENDPOINT}/providers/microsoft/v1/systemone with model set to your deployment name and a choice question per decision. Jev takes the same request and returns the same response from POST https://openrouter.ai/api/alpha/decisions. The arcade asks two questions in one call (move and fire).

How it fits together

flowchart LR
    B[Browser UI] -->|one file per request| S[FastAPI server]
    S --> M[MarkItDown: file to text]
    M --> D[Decision-1 on Foundry]
    M --> J[Jev on OpenRouter decisions endpoint]
    B -->|board description| S
    S -->|move and fire question| D
    S -->|move and fire question| J
    B -.->|planner runs in the page, no network| B
Loading

Things worth knowing

  • Rate limits. Decision-1 allows as many requests per minute as the deployment's capacity. A change to the capacity can take a while to reach the data plane, and until it does you get 429s (the app waits and retries for about 15 minutes before giving up).
  • Entra vs keys. Entra needs a role that includes the Microsoft.CognitiveServices/accounts/AIServices/providers/action data action, and role changes can lag. An API key sidesteps all of it.
  • Use the decisions endpoint for Jev, not the router. typesafe/jev-router on OpenRouter's chat endpoint is a router that picks an ordinary LLM per request (a test call was answered by z-ai/glm-5.3-flash), so it is not Jev and returns no probabilities. The real Jev is a decisions model on /api/alpha/decisions: about 0.5 s per call, versus 2 to 3 s through the router in a quick comparison.
  • The decisions endpoint is alpha and may change shape.
  • Upload limit is 25 MB per file. The server keeps each upload in a temp folder only while it converts it.
  • No tests yet. It was built by trying things and watching them work.

Project layout

server.py            FastAPI app: upload endpoint, arcade endpoint, static files
classify.py          Catalog loading, Decision-1 and Jev clients, shared retry logic, the CLI
game_ai.py           Turns the arcade's move and fire questions into Decision-1 / Jev calls
static/              index.html (classifier), game.html (arcade), style.css (both themes)
types.json           Your document types
samples/             5 fictional PDFs (4 types plus one unrelated)
samples_100/         Those 5, copied 20 times, for stress testing
make_sample_pdfs.py  Regenerates the 5 sample PDFs
docs/                Screenshots used in this README

About

Neon batch document classifier for Microsoft-Decision-1 and Jev, plus an arcade where a model, an algorithm or you fly the ship

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages