|
| 1 | +# Contributing to Benefit Decision Toolkit |
| 2 | + |
| 3 | +Thank you for your interest in contributing! BDT is a volunteer-driven open source project run by [Code for Philly](https://codeforphilly.org/projects/dmn_benefit_toolbox-including_the_philly_property_tax_relief_screener). We welcome contributions of all kinds — code, documentation, bug reports, design feedback, and DMN eligibility rules. |
| 4 | + |
| 5 | +## Table of Contents |
| 6 | + |
| 7 | +- [Ways to Contribute](#ways-to-contribute) |
| 8 | +- [Project Structure](#project-structure) |
| 9 | +- [Development Environment Setup](#development-environment-setup) |
| 10 | +- [Running the Project](#running-the-project) |
| 11 | +- [Making Changes](#making-changes) |
| 12 | +- [Testing](#testing) |
| 13 | +- [Code Style](#code-style) |
| 14 | +- [Submitting a Pull Request](#submitting-a-pull-request) |
| 15 | +- [Working with DMN Files](#working-with-dmn-files) |
| 16 | +- [Deployment (Maintainers)](#deployment-maintainers) |
| 17 | +- [Getting Help](#getting-help) |
| 18 | + |
| 19 | +--- |
| 20 | + |
| 21 | +## Ways to Contribute |
| 22 | + |
| 23 | +- **Bug reports**: Open a GitHub issue with steps to reproduce |
| 24 | +- **Feature requests**: Open a GitHub issue describing the use case |
| 25 | +- **Code**: Fix bugs, implement features, improve tests |
| 26 | +- **Documentation**: Improve guides, fix typos, add examples |
| 27 | +- **DMN rules**: Add or improve eligibility rules in `library-api` |
| 28 | +- **Testing PRs**: Review and test open pull requests — see the [Codespaces Testing Guide](docs/testing-prs-with-codespaces.md) (no local setup needed!) |
| 29 | + |
| 30 | +--- |
| 31 | + |
| 32 | +## Project Structure |
| 33 | + |
| 34 | +This is a monorepo with four distinct applications: |
| 35 | + |
| 36 | +``` |
| 37 | +benefit-decision-toolkit/ |
| 38 | +├── library-api/ # Quarkus + Kogito: DMN files → REST APIs (Java 17) |
| 39 | +├── builder-api/ # Quarkus: Admin backend, Firebase integration (Java 21) |
| 40 | +├── builder-frontend/ # Solid.js: Admin UI + public screener interface |
| 41 | +├── website/ # Public project website |
| 42 | +├── docs/ # User and developer documentation |
| 43 | +├── e2e/ # Playwright end-to-end tests |
| 44 | +├── bin/ # Developer helper scripts |
| 45 | +├── .github/ # GitHub Actions workflows |
| 46 | +├── devbox.json # Development environment declaration (Nix) |
| 47 | +└── process-compose.yml # Multi-service orchestration for local dev |
| 48 | +``` |
| 49 | + |
| 50 | +**Important**: `library-api` uses **Java 17 + Quarkus 2.16.10** (required for Kogito), while `builder-api` uses **Java 21 + Quarkus 3.23.0**. Do not mix them up. |
| 51 | + |
| 52 | +--- |
| 53 | + |
| 54 | +## Development Environment Setup |
| 55 | + |
| 56 | +There are three ways to set up your environment. **Devbox is recommended** for local development. |
| 57 | + |
| 58 | +### Option 1: GitHub Codespaces (Easiest) |
| 59 | + |
| 60 | +No local installation required. Click the badge in the [README](README.md) to open a cloud-based development environment. Wait a few minutes for the first-time build to complete. |
| 61 | + |
| 62 | +This is also a great way to test changes from a pull request — see the [Codespaces Testing Guide](docs/testing-prs-with-codespaces.md). |
| 63 | + |
| 64 | +### Option 2: Devbox (Recommended for Local Development) |
| 65 | + |
| 66 | +[Devbox](https://www.jetify.com/docs/devbox/) manages all project dependencies (Java 17, Java 21, Maven, Node.js 22, Firebase Tools, Bruno, etc.) using Nix, ensuring a consistent environment across machines. |
| 67 | + |
| 68 | +```bash |
| 69 | +# Install Devbox + Nix and run one-time setup |
| 70 | +bin/install-devbox && devbox run setup |
| 71 | +``` |
| 72 | + |
| 73 | +**Tips**: |
| 74 | +- Install the [Devbox direnv integration](https://www.jetify.com/docs/devbox/ide-configuration/direnv) to auto-activate the Devbox shell when you `cd` into the project |
| 75 | +- VS Code users: install the [Devbox + Direnv extensions](https://www.jetify.com/docs/devbox/ide-configuration/vscode) |
| 76 | +- Customize your shell by editing `.devboxrc` (not committed to git) |
| 77 | + |
| 78 | +### Option 3: Devcontainer (VS Code) |
| 79 | + |
| 80 | +Open the project in VS Code, install the `Dev Containers` extension, and run **"Dev Containers: Open Folder in Container..."** from the command palette. The container includes the full Devbox environment. Expect a longer first-time build. |
| 81 | + |
| 82 | +### Option 4: DIY (Not Recommended) |
| 83 | + |
| 84 | +Manually install the dependencies listed in `devbox.json` (JDK 17, JDK 21, Maven, Node.js 22, Firebase Tools 14.27.0, Bruno, process-compose), then run: |
| 85 | + |
| 86 | +```bash |
| 87 | +bin/setup |
| 88 | +``` |
| 89 | + |
| 90 | +--- |
| 91 | + |
| 92 | +## Running the Project |
| 93 | + |
| 94 | +### Start All Services |
| 95 | + |
| 96 | +```bash |
| 97 | +# Starts all 5 services in the correct dependency order |
| 98 | +devbox services up |
| 99 | +``` |
| 100 | + |
| 101 | +This orchestrates (in order): |
| 102 | +1. **Firebase Emulators** — Auth (9099), Firestore (8080), Storage (9199), UI (4000) |
| 103 | +2. **library-api** — REST API from DMN files at http://localhost:8083 |
| 104 | +3. **sync-library-metadata** — Syncs library check metadata to Firebase emulators |
| 105 | +4. **builder-api** — Admin API (port configured via `QUARKUS_HTTP_PORT`) |
| 106 | +5. **builder-frontend** — Vite dev server at http://localhost:5173 |
| 107 | + |
| 108 | +> **Do not start services out of order.** `builder-api` depends on Firebase emulators being fully ready, and the metadata sync step must complete before `builder-api` starts. |
| 109 | +
|
| 110 | +### Running Individual Services |
| 111 | + |
| 112 | +If you're only working on one part of the stack: |
| 113 | + |
| 114 | +```bash |
| 115 | +# library-api only (no Firebase dependency) |
| 116 | +cd library-api && quarkus dev |
| 117 | +# Swagger UI: http://localhost:8083/q/swagger-ui |
| 118 | + |
| 119 | +# Firebase emulators (required before builder-api) |
| 120 | +firebase emulators:start --project demo-bdt-dev --only auth,firestore,storage |
| 121 | + |
| 122 | +# builder-api (after emulators are running) |
| 123 | +cd builder-api && quarkus dev |
| 124 | + |
| 125 | +# builder-frontend (after builder-api is running) |
| 126 | +cd builder-frontend && npm run dev |
| 127 | +``` |
| 128 | + |
| 129 | +### Service URLs |
| 130 | + |
| 131 | +| Service | URL | |
| 132 | +|---|---| |
| 133 | +| builder-frontend | http://localhost:5173 | |
| 134 | +| library-api | http://localhost:8083 | |
| 135 | +| library-api Swagger UI | http://localhost:8083/q/swagger-ui | |
| 136 | +| Firebase Emulator UI | http://localhost:4000 | |
| 137 | + |
| 138 | +--- |
| 139 | + |
| 140 | +## Making Changes |
| 141 | + |
| 142 | +### Branching |
| 143 | + |
| 144 | +Create a feature branch from `main`: |
| 145 | + |
| 146 | +```bash |
| 147 | +git checkout main |
| 148 | +git pull |
| 149 | +git checkout -b your-feature-name |
| 150 | +``` |
| 151 | + |
| 152 | +Use descriptive branch names, e.g. `fix-dmn-evaluation-edge-case` or `add-snap-eligibility-check`. |
| 153 | + |
| 154 | +### Commits |
| 155 | + |
| 156 | +Write clear, descriptive commit messages. Focus on *what* changed and *why*: |
| 157 | + |
| 158 | +``` |
| 159 | +# Good |
| 160 | +fix: handle null income value in eligibility check |
| 161 | +
|
| 162 | +# Good |
| 163 | +feat: add SNAP benefit eligibility check for Philadelphia |
| 164 | +
|
| 165 | +# Avoid |
| 166 | +fix stuff |
| 167 | +update |
| 168 | +``` |
| 169 | + |
| 170 | +### Syncing Library Metadata After library-api Changes |
| 171 | + |
| 172 | +If you modify DMN files in `library-api`, re-sync metadata so `builder-api` picks up the changes: |
| 173 | + |
| 174 | +```bash |
| 175 | +# Re-sync metadata |
| 176 | +./bin/library/sync-metadata |
| 177 | + |
| 178 | +# Then restart builder-api (in the process-compose UI, restart the process) |
| 179 | +``` |
| 180 | + |
| 181 | +--- |
| 182 | + |
| 183 | +## Testing |
| 184 | + |
| 185 | +### Java Unit Tests |
| 186 | + |
| 187 | +```bash |
| 188 | +# builder-api |
| 189 | +cd builder-api && mvn test |
| 190 | + |
| 191 | +# library-api |
| 192 | +cd library-api && mvn test |
| 193 | +``` |
| 194 | + |
| 195 | +These run automatically in CI on every push and pull request that touches `builder-api/` or `library-api/`. |
| 196 | + |
| 197 | +### Bruno API Tests (library-api) |
| 198 | + |
| 199 | +[Bruno](https://www.usebruno.com/) is used to test the DMN-generated REST endpoints in `library-api`. The test suite lives in `library-api/test/bdt/` and mirrors the DMN file structure. |
| 200 | + |
| 201 | +```bash |
| 202 | +# Run all Bruno tests |
| 203 | +cd library-api/test/bdt && bru run |
| 204 | +``` |
| 205 | + |
| 206 | +**When adding a new benefit or check to `library-api`, add Bruno tests first** (test-driven development). Bruno tests validate DMN logic, while Java tests validate internal application behavior. |
| 207 | + |
| 208 | +### End-to-End Tests (Playwright) |
| 209 | + |
| 210 | +```bash |
| 211 | +# Run e2e tests (requires all services to be running) |
| 212 | +cd e2e && npx playwright test |
| 213 | +``` |
| 214 | + |
| 215 | +E2E tests run automatically in CI. They start all services via `devbox services up` and run against them. |
| 216 | + |
| 217 | +### Before Submitting a PR |
| 218 | + |
| 219 | +- [ ] Run Java unit tests for any service you modified |
| 220 | +- [ ] Run Bruno tests if you modified `library-api` DMN files |
| 221 | +- [ ] Verify your changes work end-to-end with all services running |
| 222 | +- [ ] Run Prettier on changed frontend files (see [Code Style](#code-style)) |
| 223 | + |
| 224 | +--- |
| 225 | + |
| 226 | +## Code Style |
| 227 | + |
| 228 | +### JavaScript / TypeScript (builder-frontend) |
| 229 | + |
| 230 | +The project uses [Prettier](https://prettier.io/) for formatting. Configuration is in `.prettierrc`: |
| 231 | + |
| 232 | +```json |
| 233 | +{ |
| 234 | + "semi": true, |
| 235 | + "singleQuote": false, |
| 236 | + "trailingComma": "all", |
| 237 | + "tabWidth": 2 |
| 238 | +} |
| 239 | +``` |
| 240 | + |
| 241 | +Format changed files before committing: |
| 242 | + |
| 243 | +```bash |
| 244 | +# Format a specific file |
| 245 | +npx prettier --write path/to/file.tsx |
| 246 | + |
| 247 | +# Format all frontend files |
| 248 | +cd builder-frontend && npx prettier --write "src/**/*.{ts,tsx,js,jsx}" |
| 249 | +``` |
| 250 | + |
| 251 | +### Java (builder-api, library-api) |
| 252 | + |
| 253 | +Follow standard Java conventions. There is no automated style enforcement beyond what Maven provides, so match the style of the surrounding code. |
| 254 | + |
| 255 | +### DMN Files |
| 256 | + |
| 257 | +- File names: `kebab-case.dmn` |
| 258 | +- Decision Service names: `{ModelName}Service` (e.g., a file named `snap-benefit.dmn` → Decision Service named `SnapBenefitService`) |
| 259 | +- Model names must be **globally unique** across the entire `library-api` — check existing names before adding a new DMN file |
| 260 | +- Imported decision services cannot share names even if in different models |
| 261 | + |
| 262 | +--- |
| 263 | + |
| 264 | +## Submitting a Pull Request |
| 265 | + |
| 266 | +1. Push your branch and open a PR against `main` |
| 267 | +2. Write a clear PR description: what changed, why, and how to test it |
| 268 | +3. All PRs require review from at least one maintainer (@Michael-Dratch or @prestoncabe — see [CODEOWNERS](.github/CODEOWNERS)) |
| 269 | +4. CI runs API unit tests and E2E tests automatically — check that they pass |
| 270 | +5. For changes to `library-api` DMN files, include Bruno test results or a description of manual testing |
| 271 | + |
| 272 | +**For non-technical contributors** testing a PR: follow the [Codespaces Testing Guide](docs/testing-prs-with-codespaces.md) — no local setup needed. |
| 273 | + |
| 274 | +--- |
| 275 | + |
| 276 | +## Working with DMN Files |
| 277 | + |
| 278 | +DMN (Decision Model and Notation) is the core of this project. If you're new to DMN, start here: |
| 279 | +- [Learn DMN in 15 minutes](https://learn-dmn-in-15-minutes.com/) |
| 280 | +- [DMN Editor for VS Code](https://marketplace.visualstudio.com/items?itemName=kie-group.dmn-vscode-extension) (strongly recommended) |
| 281 | + |
| 282 | +### Adding a Benefit to library-api |
| 283 | + |
| 284 | +Benefits represent specific programs (e.g., SNAP, property tax relief). They define eligibility logic using DMN and automatically become REST endpoints. |
| 285 | + |
| 286 | +1. **Write Bruno tests first** in `library-api/test/bdt/benefits/{jurisdiction}/{benefit-name}/` |
| 287 | +2. Create a DMN file: `library-api/src/main/resources/benefits/{jurisdiction}/{benefit-name}.dmn` |
| 288 | +3. Define a Decision Service named `{BenefitName}Service` |
| 289 | +4. Include a `checks` context and an `isEligible` boolean decision |
| 290 | +5. Save — Quarkus dev mode hot-reloads and the endpoint appears at `POST /api/v1/benefits/{jurisdiction}/{benefit-name}` |
| 291 | +6. Verify in Swagger UI: http://localhost:8083/q/swagger-ui |
| 292 | +7. Run your Bruno tests: `cd library-api/test/bdt && bru run` |
| 293 | + |
| 294 | +### Adding a Reusable Check |
| 295 | + |
| 296 | +Checks are reusable decision logic that can be shared across multiple benefits. |
| 297 | + |
| 298 | +1. Create a DMN file: `library-api/src/main/resources/checks/{category}/{check-name}.dmn` |
| 299 | +2. Define a Decision Service named `{CheckName}Service` |
| 300 | +3. Standard inputs: `situation` (tSituation type), `parameters` (check-specific context) |
| 301 | +4. Standard output: `result` (boolean) |
| 302 | +5. Endpoint: `POST /api/v1/checks/{category}/{check-name}` |
| 303 | + |
| 304 | +### Key DMN Constraints |
| 305 | + |
| 306 | +- **Decision Service name must be `{ModelName}Service`** — this is required for automatic endpoint generation |
| 307 | +- **Model names are globally unique** — if two DMN files have the same model name, things break |
| 308 | +- Imported decision services cannot share names even across different files |
| 309 | +- Import `BDT.dmn` for shared types like `tSituation` |
| 310 | + |
| 311 | +### Editing DMN Files |
| 312 | + |
| 313 | +- Use the VS Code DMN Editor extension for visual editing |
| 314 | +- To see raw XML: right-click the file → "Reopen with Text Editor" |
| 315 | +- Changes are hot-reloaded in `quarkus dev` mode — no restart needed |
| 316 | + |
| 317 | +--- |
| 318 | + |
| 319 | +## Deployment (Maintainers) |
| 320 | + |
| 321 | +### library-api |
| 322 | + |
| 323 | +Deployments are triggered by pushing a git tag matching `library-api-v*`. Use the provided script to create a release atomically: |
| 324 | + |
| 325 | +```bash |
| 326 | +cd library-api |
| 327 | + |
| 328 | +# Update pom.xml version and create git tag in one step |
| 329 | +./bin/tag-release 0.3.0 |
| 330 | + |
| 331 | +# Review the changes |
| 332 | +git show |
| 333 | + |
| 334 | +# Push the commit and the tag (triggers GitHub Actions deployment) |
| 335 | +git push origin <your-branch> |
| 336 | +git push origin library-api-v0.3.0 |
| 337 | +``` |
| 338 | + |
| 339 | +The GitHub Actions workflow validates that the tag version matches `pom.xml`, builds the Docker image, pushes to Google Artifact Registry, deploys to Cloud Run, and syncs library metadata automatically. |
| 340 | + |
| 341 | +**Version source of truth**: `pom.xml`. The git tag, Docker image tag, and Cloud Run revision name all derive from it. |
| 342 | + |
| 343 | +### builder-api |
| 344 | + |
| 345 | +Deploys automatically to Cloud Run on every push to `main` that touches `builder-api/` files. No manual versioning needed. |
| 346 | + |
| 347 | +### builder-frontend and docs |
| 348 | + |
| 349 | +Deploy automatically to Firebase Hosting on push to `main`. |
| 350 | + |
| 351 | +### Production Library Metadata Sync (Manual) |
| 352 | + |
| 353 | +If you need to sync library metadata to production outside of a `library-api` deployment: |
| 354 | + |
| 355 | +```bash |
| 356 | +export LIBRARY_API_BASE_URL=https://library-api-1034049717668.us-central1.run.app |
| 357 | +unset FIRESTORE_EMULATOR_HOST |
| 358 | +unset GCS_BUCKET_NAME |
| 359 | +unset QUARKUS_GOOGLE_CLOUD_STORAGE_HOST_OVERRIDE |
| 360 | +gcloud auth application-default login |
| 361 | +./bin/library/sync-metadata |
| 362 | +``` |
| 363 | + |
| 364 | +--- |
| 365 | + |
| 366 | +## Getting Help |
| 367 | + |
| 368 | +- **GitHub Issues**: For bugs and feature requests |
| 369 | +- **Code for Philly**: Join [codeforphilly.org](https://codeforphilly.org) to connect with the team |
| 370 | +- **Project maintainers**: @Michael-Dratch and @prestoncabe |
| 371 | + |
| 372 | +If you're stuck on setup, open an issue — improving the onboarding experience is itself a valuable contribution. |
0 commit comments