Local-first applied AI workflow project for intake triage, structured decision support, and human review preparation.
This repository is built as a compact portfolio example: it keeps the AI provider optional, makes validation and persistence visible, and shows the reviewer workflow that sits between model output and business action.
This project accepts semi-structured operations requests, runs deterministic preprocessing, optionally calls an OpenAI-backed provider, validates the structured output, persists the result, and exposes a review queue for follow-up.
The main design goal is simple: use AI inside a controlled workflow, not as the owner of business state.
- shows applied AI usage in a realistic operations-style workflow
- keeps deterministic validation and persistence visible
- demonstrates provider abstraction instead of hard-coding model logic into the app
- includes reviewable API, UI, persistence, and test layers in one small project
POST /api/v1/requeststo submit and analyze a requestGET /api/v1/requests/{request_id}to inspect one request and its decisionGET /api/v1/queueto list pending queue itemsPOST /api/v1/requests/{request_id}/reviewto approve or edit a workflow decisionGET /healthfor service health- starter UI surfaces for queue and request detail review
mockandopenaiprovider modes behind a provider boundary- local SQLite persistence for demo and development
- automated test coverage for API, parsing, health, and provider behavior
| Landing page | Review queue |
|---|---|
![]() |
![]() |
| Request detail |
|---|
![]() |
- Python 3.11+
- FastAPI
- SQLAlchemy
- Pydantic
- Jinja2
- SQLite
- OpenAI Python SDK
- Docker
- GitHub Actions
- Create and activate a virtual environment.
- Install dependencies:
pip install -e .[dev]- Copy
.env.exampleto.env. - Start the app:
uvicorn app.main:app --reload- Open
http://127.0.0.1:8000
Main local surfaces:
/for the landing page/queuefor the reviewer queue/requests/{request_id}for request detail and review history
To populate the local queue with sanitized demo data, keep the app running and execute:
python scripts/seed_demo.pyFor a containerized local run:
docker compose up --buildDocker Compose runs in mock provider mode by default, so a first run does not require a real API key. Use .env only when you intentionally want to configure the optional OpenAI provider path.
mock- safest local default for demos and tests
openai- real provider path with structured output validation
The OpenAI path is intentionally optional. The workflow should still be understandable and testable when the provider is mocked.
- Submit an intake request through the API or starter UI.
- Normalize and validate the input.
- Run provider-backed or mock decision generation.
- Validate the structured response.
- Persist request, decision, and review state to SQLite.
- Review the result in the queue or request-detail view.
ruff check .
pytestRepository validation also includes GitHub Actions CI for:
- linting
- test execution
- synthetic demo/eval fixture validation
- Docker build verification
- container health smoke test
app/- API, core app wiring, providers, repositories, services, templates, and UI routes
docs/- architecture and project notes
sample_data/- sanitized demo requests
evals/- evaluation-oriented sample cases
scripts/- developer utilities, including the local demo seeding helper
tests/- API, provider, parser, and health checks
- no authentication or multi-user access control yet
- single-tenant local-demo posture only
- local SQLite is suitable for development, not as a production database strategy
- Docker support is for local packaging, not a full deployment platform
- screenshots cover the local review flow, but this is not a hosted production service
- do not commit a real
.envfile - do not commit populated runtime databases or local state under
.local/ - do not present this as production-ready without auth, stronger deployment controls, and a production database plan
- treat the OpenAI path as optional and bounded, not as the authoritative core of the workflow
- committed sample data uses synthetic
.exampleemail domains only


