diff --git a/README.md b/README.md index f1cea06b..88591113 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,145 @@ # Balancer -Balancer is a website of digital tools designed to help prescribers choose the most suitable medications -for patients with bipolar disorder, helping them shorten their journey to stability and well-being - -## Usage - -You can view the current build of the website here: [https://balancertestsite.com](https://balancertestsite.com/) - -## Contributing - -### Join the Balancer community - -Balancer is a [Code for Philly](https://www.codeforphilly.org/) project +[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://choosealicense.com/licenses/agpl-3.0/) +[![Code for Philly](https://img.shields.io/badge/Code%20for%20Philly-Project-orange)](https://codeforphilly.org/projects/balancer) +[![Stack](https://img.shields.io/badge/Stack-Django%20%7C%20React%20%7C%20PostgreSQL%20%7C%20K8s-green)](https://github.com/CodeForPhilly/balancer) + +**Balancer** is a digital clinical decision support tool designed to assist prescribers in selecting the most suitable medications for patients with bipolar disorder. By providing evidence-based insights, Balancer aims to shorten the patient's journey to stability and well-being. + +This is an open-source project maintained by the **[Code for Philly](https://www.codeforphilly.org/)** community. + +--- + +## πŸ“‹ Table of Contents + +- [Architecture](#-architecture) +- [Prerequisites](#-prerequisites) +- [Environment Configuration](#-environment-configuration) +- [Quick Start: Local Development](#-quick-start-local-development) +- [Advanced: Local Kubernetes Deployment](#-advanced-local-kubernetes-deployment) +- [Data Layer](#-data-layer) +- [Contributing](#-contributing) +- [License](#-license) + +--- + +## πŸ— Architecture + +Balancer follows a modern containerized 3-tier architecture: + +1. **Frontend**: React (Vite) application serving the user interface. +2. **Backend**: Django REST Framework API handling business logic, authentication, and AI orchestration. +3. **Data & AI**: PostgreSQL (with `pgvector` for RAG) and integrations with LLM providers (OpenAI/Anthropic). + +```mermaid +graph TD + User[User / Prescriber] -->|HTTPS| Frontend[React Frontend] + Frontend -->|REST API| Backend[Django Backend] + + subgraph "Data Layer" + Backend -->|Read/Write| DB[(PostgreSQL + pgvector)] + end + + subgraph "External AI Services" + Backend -->|LLM Queries| OpenAI[OpenAI API] + Backend -->|LLM Queries| Anthropic[Anthropic API] + end + + subgraph "Infrastructure" + Docker[Docker Compose (Local)] + K8s[Kubernetes / Kind (Dev/Prod)] + end +``` -Join the [Code for Philly Slack and introduce yourself](https://codeforphilly.org/projects/balancer) in the #balancer channel +--- -The project kanban board is [on GitHub here](https://github.com/orgs/CodeForPhilly/projects/2) +## πŸ›  Prerequisites -### Code for Philly Code of Conduct +Before you start, ensure you have the following installed: -The Code for Philly Code of Conduct is [here](https://codeforphilly.org/pages/code_of_conduct/) +* **[Docker Desktop](https://www.docker.com/products/docker-desktop/)**: Required for running the application containers. +* **[Node.js & npm](https://nodejs.org/)**: Required if you plan to do frontend development outside of Docker. +* **[Devbox](https://www.jetify.com/devbox)** (Optional): Required only for the Local Kubernetes workflow. +* **Postman** (Optional): Useful for API testing. Ask in Slack to join the `balancer_dev` team. -### Setting up a development environment +--- -Get the code using git by either forking or cloning `CodeForPhilly/balancer-main` +## πŸ” Environment Configuration -Tools used to run Balancer: -1. `OpenAI API`: Ask for an API key and add it to `config/env/env.dev` -2. `Anthropic API`: Ask for an API key and add it to `config/env/env.dev` +To run the application, you need to configure your environment variables. -Tools used for development: -1. `Docker`: Install Docker Desktop -2. `Postman`: Ask to get invited to the Balancer Postman team `balancer_dev` -3. `npm`: In the terminal run 1) 'cd frontend' 2) 'npm install' 3) 'cd ..' +1. **Backend Config**: + * Navigate to `config/env/`. + * Copy the example file: `cp dev.env.example dev.env` + * **Action Required**: Open `dev.env` and populate your API keys (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.). Ask the project leads in Slack if you need shared development keys. -### Running Balancer for development + > **⚠️ SECURITY WARNING**: Never commit `config/env/dev.env` to version control. It is already ignored by `.gitignore`. -Start the Postgres, Django REST, and React services by starting Docker Desktop and running `docker compose up --build` +2. **Frontend Config**: + * The frontend uses `frontend/.env` (or `.env.production` for builds). + * Key variable: `VITE_API_BASE_URL` (Defaults to `http://localhost:8000` for local dev). -#### Postgres +--- -The application supports connecting to PostgreSQL databases via: +## πŸš€ Quick Start: Local Development -1. **CloudNativePG** - Kubernetes-managed PostgreSQL cluster (for production/sandbox) -2. **AWS RDS** - External PostgreSQL database (AWS managed) -3. **Local Docker Compose** - For local development +This is the standard workflow for contributors working on features or bug fixes. -See [Database Connection Documentation](./docs/DATABASE_CONNECTION.md) for detailed configuration. +1. **Clone the Repository** + ```bash + git clone https://github.com/CodeForPhilly/balancer.git + cd balancer + ``` -**Local Development:** -- Download a sample of papers to upload from [https://balancertestsite.com](https://balancertestsite.com/) -- The email and password of `pgAdmin` are specified in `balancer-main/docker-compose.yml` -- The first time you use `pgAdmin` after building the Docker containers you will need to register the server. - - The `Host name/address` is the Postgres server service name in the Docker Compose file - - The `Username` and `Password` are the Postgres server environment variables in the Docker Compose file -- You can use the below code snippet to query the database from a Jupyter notebook: +2. **Install Frontend Dependencies** (Optional but recommended for IDE support) + ```bash + cd frontend + npm install + cd .. + ``` -``` -from sqlalchemy import create_engine -import pandas as pd +3. **Start Services** + Run the full stack (db, backend, frontend) using Docker Compose: + ```bash + docker compose up --build + ``` -engine = create_engine("postgresql+psycopg2://balancer:balancer@localhost:5433/balancer_dev") +4. **Access the Application** + * **Frontend**: [http://localhost:3000](http://localhost:3000) + * **Backend API**: [http://localhost:8000](http://localhost:8000) + * **Django Admin**: [http://localhost:8000/admin](http://localhost:8000/admin) -query = "SELECT * FROM api_embeddings;" + > **Default Superuser Credentials:** + > * **Email**: `admin@example.com` + > * **Password**: `adminpassword` + > * *(Defined in `server/api/management/commands/createsu.py`)* -df = pd.read_sql(query, engine) -``` +--- -#### Django REST -- The email and password are set in `server/api/management/commands/createsu.py` +## ☸️ Advanced: Local Kubernetes Deployment -## Local Kubernetes Deployment +Use this workflow if you are working on DevOps tasks, Helm charts, or Kubernetes manifests. -### Prereqs +### 1. Configure Hostname +We map a local domain to your machine to simulate production routing. -- Fill the configmap with the [env vars](./deploy/manifests/balancer/base/configmap.yml) -- Install [Devbox](https://www.jetify.com/devbox) -- Run the following script with admin privileges: +Run this script to update your `/etc/hosts` file (requires `sudo`): ```bash +#!/bin/bash HOSTNAME="balancertestsite.com" LOCAL_IP="127.0.0.1" -# Check if the correct line already exists if grep -q "^$LOCAL_IP[[:space:]]\+$HOSTNAME" /etc/hosts; then - echo "Entry for $HOSTNAME with IP $LOCAL_IP already exists in /etc/hosts" + echo "βœ… Entry for $HOSTNAME already exists." else - echo "Updating /etc/hosts for $HOSTNAME" - sudo sed -i "/[[:space:]]$HOSTNAME/d" /etc/hosts + echo "Updating /etc/hosts..." echo "$LOCAL_IP $HOSTNAME" | sudo tee -a /etc/hosts fi ``` -### Steps to reproduce - -Inside root dir of balancer +### 2. Deploy with Devbox +We use `devbox` to manage the local Kind cluster and deployments. ```bash devbox shell @@ -102,14 +147,62 @@ devbox create:cluster devbox run deploy:balancer ``` -The website should be available in [https://balancertestsite.com:30219/](https://balancertestsite.com:30219/) +The application will be available at: **[https://balancertestsite.com:30219/](https://balancertestsite.com:30219/)** + +--- + +## πŸ’Ύ Data Layer + +Balancer supports multiple PostgreSQL configurations depending on the environment: + +| Environment | Database Technology | Description | +| :--- | :--- | :--- | +| **Local Dev** | **Docker Compose** | Standard postgres container. Access at `localhost:5433`. | +| **Kubernetes** | **CloudNativePG** | Operator-managed HA cluster. Used in Kind and Prod. | +| **AWS** | **RDS** | Managed PostgreSQL for scalable cloud deployments. | + +### Querying the Local Database +You can connect via any SQL client using: +* **Host**: `localhost` +* **Port**: `5433` +* **User/Pass**: `balancer` / `balancer` +* **DB Name**: `balancer_dev` + +**Python Example (Jupyter):** +```python +from sqlalchemy import create_engine +import pandas as pd + +# Connect to local docker database +engine = create_engine("postgresql+psycopg2://balancer:balancer@localhost:5433/balancer_dev") + +# Query embeddings table +df = pd.read_sql("SELECT * FROM api_embeddings;", engine) +print(df.head()) +``` + +--- + +## 🀝 Contributing + +We welcome contributors of all skill levels! -## Architecture +1. **Join the Community**: + * Join the [Code for Philly Slack](https://codeforphilly.org/chat). + * Say hello in the **#balancer** channel. +2. **Find a Task**: + * Check our [GitHub Project Board](https://github.com/orgs/CodeForPhilly/projects/2). +3. **Code of Conduct**: + * Please review the [Code for Philly Code of Conduct](https://codeforphilly.org/pages/code_of_conduct/). -The Balancer website is a Postgres, Django REST, and React project. The source code layout is: +### Pull Request Workflow +1. Fork the repo. +2. Create a feature branch (`git checkout -b feature/amazing-feature`). +3. Commit your changes. +4. Open a Pull Request against the `develop` branch. -![Architecture Drawing](Architecture.png) +--- -## License +## πŸ“„ License -Balancer is licensed under the [AGPL-3.0 license](https://choosealicense.com/licenses/agpl-3.0/) +Balancer is open-source software licensed under the **[AGPL-3.0 License](https://choosealicense.com/licenses/agpl-3.0/)**. \ No newline at end of file diff --git a/db/Dockerfile b/db/Dockerfile deleted file mode 100644 index 71264cbd..00000000 --- a/db/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# Use the official PostgreSQL 15 image as a parent image -FROM postgres:15 - -# Install build dependencies and update CA certificates -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - ca-certificates \ - git \ - build-essential \ - postgresql-server-dev-15 \ - && update-ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -# Clone, build and install pgvector -RUN cd /tmp \ - && git clone --branch v0.6.1 https://github.com/pgvector/pgvector.git \ - && cd pgvector \ - && make \ - && make install - -# Clean up unnecessary packages and files -RUN apt-get purge -y --auto-remove git build-essential postgresql-server-dev-15 \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* /tmp/pgvector - -COPY init-vector-extension.sql /docker-entrypoint-initdb.d/ diff --git a/docker-compose.yml b/docker-compose.yml index 5d2d5884..000960d6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,9 @@ services: db: - # Workaround for PostgreSQL crash with pgvector v0.6.1 on ARM64 - # image: pgvector/pgvector:pg15 - # volumes: - # - postgres_data:/var/lib/postgresql/data/ - # - ./db/init-vector-extension.sql:/docker-entrypoint-initdb.d/init-vector-extension.sql - build: - context: ./db - dockerfile: Dockerfile + image: pgvector/pgvector:pg15 volumes: - postgres_data:/var/lib/postgresql/data/ + - ./db/init-vector-extension.sql:/docker-entrypoint-initdb.d/init-vector-extension.sql environment: - POSTGRES_USER=balancer - POSTGRES_PASSWORD=balancer @@ -19,17 +13,12 @@ services: networks: app_net: ipv4_address: 192.168.0.2 - # pgadmin: - # container_name: pgadmin4 - # image: dpage/pgadmin4 - # environment: - # PGADMIN_DEFAULT_EMAIL: balancer-noreply@codeforphilly.org - # PGADMIN_DEFAULT_PASSWORD: balancer - # ports: - # - "5050:80" - # networks: - # app_net: - # ipv4_address: 192.168.0.4 + healthcheck: + test: ["CMD-SHELL", "pg_isready -U balancer -d balancer_dev"] + interval: 5s + timeout: 5s + retries: 5 + backend: image: balancer-backend build: ./server @@ -39,12 +28,20 @@ services: env_file: - ./config/env/dev.env depends_on: - - db + db: + condition: service_healthy volumes: - ./server:/usr/src/server networks: app_net: ipv4_address: 192.168.0.3 + healthcheck: + test: ["CMD-SHELL", "python3 -c 'import http.client;conn=http.client.HTTPConnection(\"localhost:8000\");conn.request(\"GET\",\"/admin/login/\");res=conn.getresponse();exit(0 if res.status in [200,301,302,401] else 1)'"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + frontend: image: balancer-frontend build: @@ -60,10 +57,17 @@ services: - "./frontend:/usr/src/app:delegated" - "/usr/src/app/node_modules/" depends_on: - - backend + backend: + condition: service_healthy networks: app_net: ipv4_address: 192.168.0.5 + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:3000 || exit 1"] + interval: 10s + timeout: 5s + retries: 5 + volumes: postgres_data: networks: @@ -72,4 +76,4 @@ networks: driver: default config: - subnet: "192.168.0.0/24" - gateway: 192.168.0.1 + gateway: 192.168.0.1 \ No newline at end of file diff --git a/server/balancer_backend/settings.py b/server/balancer_backend/settings.py index 9f917a94..bdc465ca 100644 --- a/server/balancer_backend/settings.py +++ b/server/balancer_backend/settings.py @@ -180,9 +180,10 @@ # https://docs.djangoproject.com/en/4.2/howto/static-files/ STATIC_URL = "/static/" -STATICFILES_DIRS = [ - os.path.join(BASE_DIR, "build/static"), -] +STATICFILES_DIRS = [] +if os.path.exists(os.path.join(BASE_DIR, "build/static")): + STATICFILES_DIRS.append(os.path.join(BASE_DIR, "build/static")) + STATIC_ROOT = os.path.join(BASE_DIR, "static") AUTHENTICATION_BACKENDS = [ diff --git a/server/balancer_backend/urls.py b/server/balancer_backend/urls.py index d34c532f..5a1fdcde 100644 --- a/server/balancer_backend/urls.py +++ b/server/balancer_backend/urls.py @@ -51,8 +51,12 @@ path("api/", include(api_urlpatterns)), ] +import os +from django.conf import settings + # Add a catch-all URL pattern for handling SPA (Single Page Application) routing # Serve 'index.html' for any unmatched URL (must come after /api/ routes) -urlpatterns += [ - re_path(r"^.*$", TemplateView.as_view(template_name="index.html")), -] +if os.path.exists(os.path.join(settings.BASE_DIR, "build", "index.html")): + urlpatterns += [ + re_path(r"^(?!api|admin|static).*$", TemplateView.as_view(template_name="index.html")), + ]