NNora the Policy Navigator

Deliverables

What we've shipped

M6In Progress

Platform Selection

The technologies we use to build Nora, the alternatives we considered for each, and why we chose what we did. We first listed the alternatives, then compared their trade-offs and made a selection. This milestone feeds into our design document (D2).

Selectedin the codebaseProposedour plan, not built yetOpenwaiting on the client or UNC

Client & UNC Constraints

These requirements from our client and coach dictated or narrowed some choices. Components they affected are marked Constrained below.

  • Deploy on UNC infrastructure

    Nora should run on Carolina CloudApps, UNC's OpenShift-based container platform, so it lives on after the semester under UNC ownership rather than on a student or commercial account.

    Coach meeting, Sep 24

  • Sign in with Onyen

    Researchers and admins log in with their UNC Onyen. Production SSO (Shibboleth) needs UNC ITS access, so the semester build uses a mock login that models the Onyen flow.

    Client kickoff, Sep 15; spec F1–F3

  • Curated, approved data only

    Answers must be grounded in fewer than 200 curated documents from the UNC Policy Knowledge Base, with citations. No confidential records and no live link to IRB, COI, or other enterprise systems.

    Client kickoff, Sep 15; spec NF5

  • AI providers need UNC approval

    Researchers' questions may include Onyens and research details, so the LLM and embedding providers, and where telemetry is stored, must be acceptable to UNC. These are still open questions with the client.

    Open question with client

  • Simple UNC-branded UI

    Keep UNC colors and a simple design, using UNC Research's official branding. Analytics must be downloadable.

    Client meeting, Sep 29

Phase 1 · Alternatives

Every option we studied for each part of the platform. Our pick is highlighted.

Phase 2 · Comparison & Selection

Component 01

Selected

Application Architecture

Is Nora a client-server web app, a single full-stack app, or something else?

Pros

  • Frontend and backend can each use the best language for the job (TypeScript for UI, Python for AI)
  • Clean JSON API boundary (spec I7) that is easy to test and document
  • Each half ships as its own container, which suits OpenShift

Cons

  • Two codebases and two build pipelines
  • Needs a proxy so the browser talks to one origin

Pros

  • One codebase and one deploy; server and client code side by side
  • We already use it for this team website

Cons

  • Pushes the backend into JavaScript, away from Python's RAG and data tooling
  • Best experience is on Vercel; self-hosting a Node server on OpenShift is more work

Pros

  • Python end to end, with built-in admin and auth
  • No separate frontend build

Cons

  • Interactive pieces (chat-style Ask flow, live dashboard charts) are awkward without a JS framework
  • Template UI is harder to make feel modern

Our selection

A browser-based React single-page app talks to a FastAPI server over JSON, and the server talks to PostgreSQL and the external AI APIs. The frontend only ever calls relative /api paths, forwarded by Vite in development and by nginx in production, so there is no CORS setup. This split lets us keep the AI work in Python while the UI uses mainstream React tooling. There is no specific hardware or OS target: Nora runs in any modern desktop or mobile browser (spec NF1).

Component 02

Selected

Programming Languages

Which languages do we write the frontend and backend in?

TypeScript + Python

Our selection

Pros

  • TypeScript catches API-shape mistakes in the UI at compile time
  • Python has the strongest ecosystem for embeddings, RAG, and LLM SDKs
  • Both are taught and widely known, which helps handoff

Cons

  • Two languages to lint, test, and maintain

Pros

  • One language and shared types between client and server
  • Every major LLM vendor ships a JS SDK

Cons

  • Thinner ecosystem for document parsing, chunking, and evaluation than Python

Pros

  • Mature, enterprise-standard backend

Cons

  • Verbose and slower to iterate in for a one-semester project
  • Weaker AI tooling than Python

Our selection

Python 3.12 on the backend and TypeScript on the frontend. Most of Nora's hard problems (chunking policy documents, embeddings, retrieval, prompting) are in the backend, and Python is where that tooling is best. The team is comfortable in both.

Component 03

Selected

Frontend Framework

What do we build the researcher and admin UI with?

React

Our selection

Pros

  • Largest ecosystem: charting, tables, and component libraries for the admin dashboard
  • Most of the team has used it
  • Easiest framework for a future UNC developer to pick up

Cons

  • Only a view library; routing and data fetching are separate choices

Pros

  • Gentle learning curve and clear single-file components
  • Official router and state library

Cons

  • Smaller ecosystem and less team experience

Pros

  • Batteries included: routing, forms, HTTP, and DI out of the box

Cons

  • Heavy and opinionated for a small app
  • Steepest learning curve, and no one on the team knows it

Pros

  • Small bundles and very little boilerplate

Cons

  • Smallest ecosystem and hiring pool of the four, which hurts handoff

Our selection

React 19 with TypeScript. The deciding factors were team experience, the size of its ecosystem for the admin dashboard, and how easy it will be for whoever maintains Nora at UNC to find help.

Component 04

Selected

Frontend Build Tool

How do we bundle and serve the React app?

Vite

Our selection

Pros

  • Near-instant dev server and hot reload
  • Builds plain static files that nginx can serve on OpenShift
  • Built-in dev proxy for /api

Cons

  • No server-side rendering out of the box (Nora doesn't need it)

Pros

  • Routing, SSR, and API routes included

Cons

  • Needs a Node server in production; overlaps with FastAPI

Pros

  • Familiar from older tutorials

Cons

  • Deprecated by the React team in 2025

Our selection

Vite. Nora sits behind a login, so it gains nothing from server rendering or SEO, and a static build served by nginx is the simplest thing to run on CloudApps.

Component 05

Selected

Backend Framework

What serves the REST API and runs the RAG pipeline?

FastAPI

Our selection

Pros

  • Async, which suits waiting on LLM and embedding API calls
  • Request and response validation from Pydantic types
  • Generates interactive OpenAPI docs automatically (/api/docs)

Cons

  • No built-in admin panel or auth; we pick those pieces ourselves

Pros

  • Built-in ORM, migrations, auth, and admin site

Cons

  • Heavier, and its sync-first design fits slow AI calls less well
  • Its admin site wouldn't match the client's dashboard design anyway

Pros

  • Minimal and easy to learn

Cons

  • No built-in validation or API docs; more glue code to write

Pros

  • Same language as the frontend

Cons

  • Moves the AI pipeline out of Python

Our selection

FastAPI, with SQLAlchemy 2.0 as the ORM and Alembic for schema migrations. FastAPI gives us typed endpoints and free API docs with very little code, and async handlers keep the server responsive while it waits on the LLM.

Component 06

SelectedConstrained

Database & Vector Search

Where do we store policies, users, questions, and feedback, and how do we search policy text by meaning?

Client constraint: Data must stay where UNC approves; whether telemetry can live off UNC infrastructure is still an open question.

Pros

  • One database for relational data and vector embeddings
  • Runs anywhere Postgres runs, including in a CloudApps container
  • At under 200 documents, pgvector is more than fast enough

Cons

  • Fewer vector-specific features than a dedicated vector DB

Pros

  • Purpose-built similarity search, filtering, and scaling

Cons

  • A second datastore to run, secure, and keep in sync
  • Pinecone is a hosted service, which raises the same data-location question

Pros

  • Flexible document model with vector search built in

Cons

  • Vector search is an Atlas cloud feature, so data leaves UNC
  • Our data (users, roles, questions, feedback) is relational

Pros

  • Hosted Postgres with pgvector, auth, and a dashboard

Cons

  • Hosted outside UNC, pending client approval
  • Its auth and SDK would tie us to the vendor

Our selection

PostgreSQL 17 with the pgvector extension, enabled by our first Alembic migration. Our corpus is small, and keeping embeddings next to the policy rows they belong to removes a whole system. The app only depends on a DATABASE_URL, so it can point at a CloudApps Postgres or, if UNC allows it, a hosted Postgres like Supabase without code changes. We would use Supabase only as plain Postgres, never its auth or client SDK.

Component 07

OpenConstrained

LLM Provider

Which model writes the plain-language, cited answer from the retrieved policy passages?

Client constraint: Researchers' questions are sent to this provider, so the choice needs UNC approval.

Pros

  • Strong at following grounding instructions and citing sources
  • Built-in citations support for document-grounded answers

Cons

  • External commercial API; usage cost falls to the client after handoff
  • Not yet approved for UNC data

Pros

  • Widely used, with mature SDKs and docs

Cons

  • Same data and cost questions as any external API

Pros

  • Enterprise data terms; may fit an existing university Microsoft agreement

Cons

  • Needs an Azure subscription and approval from UNC

Pros

  • Data never leaves UNC; no per-call fees

Cons

  • Needs GPU resources CloudApps may not offer
  • Noticeably weaker answers and more for UNC to maintain

Where it stands

Not decided yet: this depends on which providers UNC approves, and we have asked the client. Until then all LLM calls go through one provider interface in the backend, so swapping providers is a configuration change and tests never call a real API. Claude is our working default for development (spec I8).

Component 08

OpenConstrained

Embedding Model

What turns policy text and questions into vectors for retrieval and FAQ clustering?

Client constraint: Same UNC data-approval question as the LLM provider.

Pros

  • Cheap, good quality, simple API

Cons

  • External API; questions leave UNC

Pros

  • Strong retrieval quality; the embedding provider Anthropic recommends

Cons

  • Another external vendor to get approved

Pros

  • Runs on CPU inside our own backend container; data stays on UNC infrastructure
  • No per-call cost

Cons

  • Somewhat lower quality than the best hosted models
  • Larger container image

Where it stands

Not decided yet, for the same reason as the LLM. Embeddings sit behind their own interface. If UNC won't approve sending questions to an external vendor, a local Sentence Transformers model is our fallback, since our corpus is small enough to embed on CPU.

Component 09

Proposed

RAG Orchestration

Do we use a framework to chunk, retrieve, and prompt, or write the pipeline ourselves?

Hand-written pipeline

Leading option

Pros

  • Small, readable code we fully understand and can test at 100% coverage
  • Full control over the "can't answer confidently, refer to an office" rule
  • No fast-moving framework for UNC to keep up with

Cons

  • We write chunking, retrieval, and prompt assembly ourselves

Pros

  • Huge library of integrations and examples

Cons

  • Layers of abstraction that make behavior harder to debug and test
  • Frequent breaking changes

Pros

  • Focused on document indexing and retrieval

Cons

  • Same abstraction and churn concerns, to a lesser degree

Our plan

We plan to write the pipeline directly on SQLAlchemy and pgvector. With under 200 documents, the pipeline is a few hundred lines, and the behavior the client cares most about (citing sources, refusing to guess) has to be explicit and tested rather than hidden in a framework.

Component 10

Proposed

Charting

What draws the admin dashboard's line and donut charts (spec I4)?

Recharts

Leading option

Pros

  • React components, so charts are written like the rest of the UI
  • Covers line, donut, and bar charts, which is all the dashboard needs

Cons

  • Less flexible than D3 for unusual charts

Pros

  • Popular, lightweight, canvas-based

Cons

  • Imperative API needs a React wrapper

Pros

  • Very feature-rich, with built-in export to image

Cons

  • Large bundle; more than we need

Pros

  • Total control over every visual

Cons

  • Far more code for standard charts

Our plan

Recharts, pending the dashboard build. Analytics downloads, which the client requires, will be CSV exports generated by the backend, so they don't depend on the chart library.

Component 11

SelectedConstrained

Authentication

How do users sign in, and how do we know who is an admin?

Client constraint: Must use UNC Onyen. Production SSO requires UNC ITS access (contact: David Cowig).

Pros

  • Matches the client's requirement and UNC's real login
  • Mock login lets us build and demo without waiting on ITS

Cons

  • The final SSO integration pattern on CloudApps is not confirmed

Pros

  • Quick to set up, with hosted login pages

Cons

  • Not Onyen; researchers would need a separate account

Pros

  • No external dependency

Cons

  • Storing passwords is a security liability, and it isn't Onyen

Our selection

Dictated by the client. The semester build uses a mock login that models Onyen (spec F3), and production will use UNC Shibboleth SSO, likely a proxy in front of the app that passes identity in headers. Either way the backend reads the user's role from our own users and roles tables, and the login provider sits behind an interface so the swap doesn't touch the rest of the app. Per our coach, auth comes near the end of October.

Component 12

SelectedConstrained

Hosting & Deployment

Where does Nora run in production, and who keeps it running after we leave?

Client constraint: Our coach directed us to deploy on Carolina CloudApps, UNC's deployment platform.

Pros

  • Run by UNC, so Nora and its data stay on UNC infrastructure
  • Lives on after the semester under UNC ownership, with no student-paid accounts
  • Runs standard Docker images

Cons

  • Containers must run as non-root with an arbitrary UID
  • Less turnkey than a commercial PaaS; access goes through UNC

Pros

  • Easy git-push deploys with Postgres add-ons

Cons

  • Free tier ended in November 2022, so the client would pay ongoing fees
  • Data hosted outside UNC

Pros

  • Fast setup with generous hobby tiers for demos (spec NF10)

Cons

  • Hobby tiers aren't meant for production; paid plans after handoff
  • Data hosted outside UNC

Pros

  • Most control and scalability

Cons

  • Far more setup and ops work; needs a UNC-owned AWS account and billing

Our selection

Carolina CloudApps. Our coach directed it, and it also answers the handoff question: Nora runs on UNC infrastructure, owned by UNC, with no commercial account for the client to pay for or take over. Our images are built for it (non-root, arbitrary UID, the frontend served by nginx-unprivileged on port 8080). Because everything is a standard container, Nora could still move to another host if needed.

Component 13

Selected

Containers, CI & Dev Tooling

How do we run the stack locally, and what checks every change?

Pros

  • One command (docker compose up) runs Postgres, backend, and frontend identically on every laptop
  • The same images we test are what CloudApps runs
  • GitHub Actions is free for our repo and lives next to the code

Cons

  • Docker adds some setup and memory overhead on laptops

Pros

  • No Docker to learn

Cons

  • "Works on my machine" problems across Mac and Windows
  • Doesn't match how CloudApps runs the app

Pros

  • Capable, mature CI services

Cons

  • Our code is on GitHub; another service adds accounts and setup

Our selection

Docker images for the backend and frontend (with dev and prod targets), Docker Compose for local development, and GitHub Actions for CI. Every PR runs ruff and pytest (100% backend coverage required), oxlint and a type-checked frontend build, the database migrations, and the Docker builds. A second workflow enforces our feature → dev → qa → main branch flow.

Nora's Platform at a Glance

ArchitectureReact SPA + FastAPI REST API (web, browser-based)Selected
LanguagesTypeScript, Python 3.12Selected
FrontendReact 19 + ViteSelected
BackendFastAPI, SQLAlchemy 2.0, AlembicSelected
DatabasePostgreSQL 17 + pgvectorSelected
LLMBehind an interface; Claude in developmentOpen
EmbeddingsBehind an interface; local model as fallbackOpen
RAGHand-written pipelineProposed
ChartsRechartsProposed
AuthMock Onyen login → UNC Shibboleth SSOSelected
HostingCarolina CloudApps (OpenShift)Selected
ToolingDocker, Docker Compose, GitHub ActionsSelected

References

Each option above links to its official site or docs. These are the other sources we used in making the decisions.