# Project export: U.S. Eco Glass

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: CruzHacks 2026
- Tagline: Up to date US climate policy dashboard aggregating up-to-date data in an interactive UI. Helping citizens gain transparency and visibility into our complex legislative system.
- Devpost: https://devpost.com/software/u-s-eco-glass
- GitHub: https://github.com/eolaruhagen/cruzhacks-26-environmental-transparency
- Demo: https://us-ecoglass.vercel.app/
- Video: https://www.youtube.com/embed/mdlI3KHWfeU?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Justice Hacks; [Sponsor - Unwrap] Best Insights Extracted from any public data source)
- Team: 3 GitHub contributor(s) — Ethan Kuhlken (23 commits), Gaurav Gupta (10 commits), Eric Olaru-Hagen (3 commits)

## Devpost submission (written by the team)

### Inspiration

In our current political climate, it is incredibly difficult to decipher what our politicians and officials are supporting and what bills are being passed and put into law. With how little the US is currently invested in environmental policy this aspect of legislation is the least exposed to the public. We realized that most Americans might not actually be up to date with what US environmental policy is being enacted or looked at by the house and senate.

### What it does

EcoGlass aims to make the US legislative process more accessible to the public, the very people that the system is supposed to be serving. With a focus on environmental policy, the website is able to give a streamlined look into the current climate policy, in limiting the scope the information is much more digestible by the average user.

### How we built it

We started with an idea, this became a large structured plan on the scope of the website, what features we wanted to add and what our end goal should look like. From that point we chose our tech stack and modeled our data so we could process vast amount of data from the congress.gov API. At this point we delegated jobs and began building.

### Challenges we ran into

Data ingestion.

### Accomplishments we're proud of

Our bill similarity graph. Our usage of vector embeddings of bill data along with cosine similarity on subcategories allowed us to make digestible visualizations of what areas any given congress official is voting on and sponsoring. We implemented an unsupervised machine learning K-Means clustering algorithm to reduce the data from hundreds of data points to under 20, making visualization clear and easy to understand. This feature also allows the user to compare past congresses to the current congress and how US Climate Policy has changed and morphed over the last 30 years. The "Your Representatives" makes accountability a focus of our site. We believe being able to view the actions of the people that directly represent you, the user, is a tool of accountability and a force of good.

### What we learned

Reliable data ingestion is HARD. When using managed services, running past server hardware limits is easy, and inefficient data pipeline design will break easy and is incredibly error prone. We eventually landed on a multi-step message queue system. This was a first for all of us. ##

### What's next

We hope to improve our backend pipelines for data ingestion through things like improved fallbacks, logging and observability to make sure our data is always up to date. We also want to keep going back through the congress API to bring in data from before ~1990, as that is the limit of our current dataset. Other 3rd party integrations such as NewsAPI to bring in up to date data about public perception and coverage in environmental policy. Document stores. We want people to be able to use this site as a one-stop-shop, allowing people to directly view important documents through this site can push us towards this goal.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 212 recognized source files, 1202 KB.
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- PostgreSQL (technology) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers
- AI coding agent: Cursor — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (120 of 252)

```
.cursor/mcp.json
.github/workflows/bill-enrich.yml
.github/workflows/bill-pipeline.yml
.github/workflows/bill-references.yml
.github/workflows/bill-sync.yml
.github/workflows/deploy-edge-functions.yml
.github/workflows/deploy-migrations.yml
.github/workflows/news-pipeline.yml
.gitignore
all_bills.csv
analytics/csv_analyzer.py
bill-pipline/.env.example
bill-pipline/.gitignore
bill-pipline/bun.lock
bill-pipline/package.json
bill-pipline/python/.gitignore
bill-pipline/python/conftest.py
bill-pipline/python/extractor_types.py
bill-pipline/python/extractor.py
bill-pipline/python/lib/__init__.py
bill-pipline/python/lib/extract.py
bill-pipline/python/lib/patterns.py
bill-pipline/python/requirements-dev.txt
bill-pipline/python/requirements.txt
bill-pipline/python/tests/__init__.py
bill-pipline/python/tests/test_extract.py
bill-pipline/python/tests/test_extractor_ipc.py
bill-pipline/python/tests/test_patterns.py
bill-pipline/src/config.ts
bill-pipline/src/lib/enrich/bill-enrich.test.ts
bill-pipline/src/lib/enrich/bill-enrich.ts
bill-pipline/src/lib/enrich/bill-fetch.test.ts
bill-pipline/src/lib/enrich/bill-fetch.ts
bill-pipline/src/lib/enrich/make-classify.ts
bill-pipline/src/lib/enrich/make-embed.ts
bill-pipline/src/lib/enrich/make-fetch-backend.ts
bill-pipline/src/lib/enrich/process-bill-enrichment.test.ts
bill-pipline/src/lib/enrich/process-bill-enrichment.ts
bill-pipline/src/lib/pipeline/bill-text-clean.test.ts
bill-pipline/src/lib/pipeline/bill-text-clean.ts
bill-pipline/src/lib/pipeline/bill-text-fetch.ts
bill-pipline/src/lib/pipeline/bill-write.test.ts
bill-pipline/src/lib/pipeline/bill-write.ts
bill-pipline/src/lib/pipeline/make-bill-write-backend.ts
bill-pipline/src/lib/pipeline/process-bill.test.ts
bill-pipline/src/lib/pipeline/process-bill.ts
bill-pipline/src/lib/references/bill-references.ts
bill-pipline/src/lib/references/extract.ts
bill-pipline/src/lib/references/make-references-backend.ts
bill-pipline/src/lib/references/tests/bill-references.test.ts
bill-pipline/src/lib/references/tests/extract-subprocess.test.ts
bill-pipline/src/lib/references/types.ts
bill-pipline/src/lib/runtime/blackout.test.ts
bill-pipline/src/lib/runtime/blackout.ts
bill-pipline/src/lib/runtime/congress-sync-state.test.ts
bill-pipline/src/lib/runtime/congress-sync-state.ts
bill-pipline/src/lib/runtime/observability.ts
bill-pipline/src/lib/runtime/pgmq-interactions.ts
bill-pipline/src/lib/runtime/queue-partition.test.ts
bill-pipline/src/lib/runtime/queue-partition.ts
bill-pipline/src/lib/runtime/supabase-client.ts
bill-pipline/src/lib/runtime/time-budget.test.ts
bill-pipline/src/lib/runtime/time-budget.ts
bill-pipline/src/lib/sync/csv-bill-source.test.ts
bill-pipline/src/lib/sync/csv-bill-source.ts
bill-pipline/src/lib/sync/csv-parse.test.ts
bill-pipline/src/lib/sync/csv-parse.ts
bill-pipline/src/main-enrich.ts
bill-pipline/src/main-pipeline.ts
bill-pipline/src/main-references.ts
bill-pipline/src/main-sync.ts
bill-pipline/tsconfig.json
bun.lock
CLANKERRULES.md
CLAUDE.md
client/.eslintrc.json
client/components.json
client/eslint.config.js
client/next.config.js
client/package.json
client/postcss.config.js
client/public/images/executivecab.jpg:Zone.Identifier
client/public/images/Florida-Keys.jpg:Zone.Identifier
client/public/images/paperbackground.webp:Zone.Identifier
client/public/images/papertexture.jpg:Zone.Identifier
client/public/images/USHouse-photo.avif
client/public/images/USHouse-photo.avif:Zone.Identifier
client/public/images/Yosemite.jpg:Zone.Identifier
client/src/app/api/airnow/route.ts
client/src/app/api/representatives/route.ts
client/src/app/api/revalidate-graph/route.ts
client/src/app/climate-impact/ClimateCharts.tsx
client/src/app/climate-impact/page.tsx
client/src/app/environmental-protection/page.tsx
client/src/app/executive-branch/page.tsx
client/src/app/globals.css
client/src/app/graph/graph_client.tsx
client/src/app/graph/page.tsx
client/src/app/landing_page.md
client/src/app/layout.tsx
client/src/app/legislative-process/page.tsx
client/src/app/news/page.tsx
client/src/app/news/search/page.tsx
client/src/app/news/test/page.tsx
client/src/app/news/visualize/page.tsx
client/src/app/page.tsx
client/src/app/representatives/MyRepClient.tsx
client/src/app/representatives/page.tsx
client/src/app/search/page.tsx
client/src/app/search/SearchClient.tsx
client/src/components/EcoGlassMark.tsx
client/src/components/landing/ImageCard.tsx
client/src/components/landing/LandingImages.tsx
client/src/components/landing/LandingPage.tsx
client/src/components/Navbar.tsx
client/src/components/search/hooks/useSearchModal.tsx
client/src/components/search/ui/SearchModal.tsx
client/src/components/search/ui/SearchResultItem.tsx
client/src/components/threejs/MovingLeafBg.tsx
client/src/components/threejs/ThreeScene.ts
[132 more files omitted for size]
```

### Dependencies

- bill-pipline/package.json: @cruzhacks/shared@file:../packages/shared, @openrouter/sdk@^0.10.2, @supabase/supabase-js@^2.49.4, @types/bun@latest, pino@^10.3.1, pino-pretty@^13.1.3, typescript@^5, zod@^4
- bill-pipline/python/requirements.txt: click, en-core-web-sm@@ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.7.1/en_core_web_sm-3.7.1-py3-none-any.whl, spacy@>=3.7,<4.0
- client/package.json: @supabase/supabase-js@^2.90.1, @tanstack/react-virtual@^3.13.18, @testing-library/dom@^10.4.1, @testing-library/react@^16.3.2, @types/node@^20.12.0, @types/react@^18.3.0, @types/react-dom@^18.3.0, @types/three@^0.184.0, autoprefixer@^10.4.19, clsx@^2.1.1, eslint@^10.2.1, eslint-config-next@^16.2.4, global-jsdom@^29.0.0, jsdom@^29.1.1, ml-kmeans@^7.0.0, next@^16.2.4, postcss@^8.5.13, react@^18.3.0, react-dom@^18.3.0, recharts@^3.6.0, tailwind-merge@^3.4.0, tailwindcss@^3.4.3, three@^0.184.0, tsx@^4.21.0, typescript@^5.4.0
- news-pipline/package.json: @openrouter/sdk@^0.10.2, @types/bun@latest, knip@^6.3.1, networkanalysis-ts@^1.0.0, pino@^10.3.1, pino-pretty@^13.1.3, postgres@^3.4.8, typescript@^5, zod@^4.3.6
- package.json: @types/node@^25.6.0, recharts@^3.6.0, shadcn@^3.7.0, supabase@^2.78.1, typescript@^6.0.3
- packages/shared/package.json: @openrouter/sdk@^0.10.2, @types/bun@latest, zod@^4
- supabase/maintenance/package.json: @cruzhacks/shared@file:../../packages/shared, @supabase/supabase-js@^2.104.0, @types/bun@latest, bun-types@^1.3.13, typescript@^5

### Recent commits (newest first)

- Merge pull request #47 from eolaruhagen/references-deps-fix
- have requirements also specify click, required by spacy but not explicitly described as a dependency
- Merge pull request #46 from eolaruhagen/fix-ortho-camera-clip-planes
- widen ortho near/far so tilted board doesn't clip
- Merge pull request #45 from eolaruhagen/bills-table-migration
- Merge pull request #44 from eolaruhagen/congress-retry-debug-logging
- add optional injectable debug logging to HTTP retry path
- Merge pull request #43 from eolaruhagen/restyle-institutional-tracker
- Merge pull request #42 from eolaruhagen/bills-table-migration
- update DISCORD_WEBHOOK_URL to use the correct secret for bills
- trim ~190 lines: dead TypedDicts, parametrize duplicate tests, small helpers
- claude.md updates for subagent usage (smaller agents preferred)
- extract formatLegislationNumber to @cruzhacks/shared
- actually remove per-bill upsert from references phase 2
- client: restyle to flat institutional tracker theme
- Merge pull request #41 from eolaruhagen/enrich-embed-classify-fix
- throttle bill-enrich and bill-pipeline crons to every 6h
- fix bill-enrich embed input overflow and classify reasoning rejection
- add diagnostic logging + egress throttles to bill-enrich
- strip embedded whitespace from extracted act names

## Key source files (fetched from GitHub, selected and truncated for size)

### CLANKERRULES.md

```markdown
# Rules for any filthy clankers navigating our beautiful repository


## React Server Components
### graph_page.tsx
### news_page.tsx
### search_page.tsx
### landing_page.tsx

## React Client Components
### Every other file that builds interactivity is its own component and passed to one of the 4 pages as a prop



## Do not put unincripted credentials in Client components, request for them to be placed into a .env file for security reasons
## 
```

### CLAUDE.md

```markdown
# Project: Environmental Transparency Platform

## Agent collaboration

This is a long-lived codebase that the maintainer often works on across
multi-day, 24h+ sessions. To preserve the main agent's context window:

- **Prefer subagents (Opus) for code creation work** — drafting new files,
  writing test suites, scaffolding parallel components. Delegate with a tight
  spec (conventions to mirror, files to read, interface to produce) so the
  result lands ready-to-review in the main thread without consuming context on
  exploration or generation.
- Keep the main agent focused on design decisions, integration, review of
  subagent output, and edits that depend on the live conversation state.
- Research-style exploration (grep, locate symbol, "where is X defined") also
  belongs in Explore subagents — return findings, not raw tool output.
- Inline edits and small targeted changes don't need a subagent; the overhead
  isn't worth it under ~50 lines or for trivial modifications.
- IN SUBAGENT USAGE. Prefer Sonnet 4.6 with `high` effort unless explicitly told otherwise.

## Database migrations

Migrations are managed exclusively through the Supabase CLI against the
**local** Postgres instance. The maintainer never authorizes prod writes from an
agent session — production rollouts happen out-of-band through CI / the Supabase
dashboard.

- **Apply migrations only via `supabase migration up --local`.** Never
  `psql -f`, never `supabase db push`, never
  `mcp__supabase-local__apply_migration` unless the CLI path is genuinely broken
  and the user explicitly asks for the bypass. The CLI is the only path that
  updates `supabase_migrations.schema_migrations`, which is what every other
  tool (incl. `supabase migration repair`, the dashboard's drift detection,
  `gen types`) reads to know what's applied.
- **`--local` is non-negotiable.** Every migration command in this repo runs
  against the local dockerized Postgres on port 54322. If a command silently
  defaults to a linked project, that's a bug to surface, not a default to
  follow.
- **One migration per logical change.** Files are timestamped
  `YYYYMMDDHHMMSS_<kebab-name>.sql` and live in `supabase/migrations/`. Don't
  edit a migration after applying it — write a new migration to fix or revert.
- **Re-generate types after schema-touching migrations**:
  `supabase gen types --lang=typescript --local --schema public > packages/shared/src/database.types.ts`.
  This is the single source of truth for the schema; the Bun-side
  `bill-pipline/` service imports it via the `@cruzhacks/shared` alias. Stale
  types are why `supabase-js` calls start complaining about "column does not
  exist."
- **Verify on apply.** After `supabase migration up --local` succeeds, confirm
  the version was recorded:
  `psql -h 127.0.0.1 -p 54322 -U postgres -d postgres -c "SELECT version FROM supabase_migrations.schema_migrations WHERE version = '<ts>';"`
  A successful CLI run that doesn't appear in the table means the DDL was r
[truncated — 2692 more characters]
```

### package.json

```
{
  "private": true,
  "workspaces": [
    "client",
    "packages/*"
  ],
  "scripts": {
    "test": "bun run test:shared && bun run test:bills",
    "test:shared": "bun test --cwd packages/shared",
    "test:bills": "bun test --cwd bill-pipline",
    "test:shared:watch": "bun test --cwd packages/shared --watch",
    "test:bills:watch": "bun test --cwd bill-pipline --watch",
    "check": "bun run check:shared && bun run check:bills",
    "check:shared": "cd packages/shared && bunx tsc --noEmit -p .",
    "check:bills": "cd bill-pipline && bunx tsc --noEmit -p ."
  },
  "devDependencies": {
    "@types/node": "^25.6.0",
    "shadcn": "^3.7.0",
    "typescript": "^6.0.3"
  },
  "dependencies": {
    "recharts": "^3.6.0",
    "supabase": "^2.78.1"
  }
}

```

### news-pipline/package.json

```
{
  "name": "news-pipline",
  "module": "index.ts",
  "type": "module",
  "private": true,
  "devDependencies": {
    "@types/bun": "latest",
    "knip": "^6.3.1"
  },
  "peerDependencies": {
    "typescript": "^5"
  },
  "dependencies": {
    "@openrouter/sdk": "^0.10.2",
    "networkanalysis-ts": "^1.0.0",
    "pino": "^10.3.1",
    "pino-pretty": "^13.1.3",
    "postgres": "^3.4.8",
    "zod": "^4.3.6"
  }
}

```

### bill-pipline/package.json

```
{
  "name": "bill-pipline",
  "type": "module",
  "private": true,
  "scripts": {
    "enrich": "bun run src/main-enrich.ts",
    "sync": "bun run src/main-sync.ts",
    "pipeline": "bun run src/main-pipeline.ts",
    "references": "bun run src/main-references.ts",
    "test": "bun test",
    "check": "bunx tsc --noEmit -p .",
    "enrich-local": "bun --env-file=.env.local run src/main-enrich.ts",
    "sync-local": "bun --env-file=.env.local run src/main-sync.ts",
    "pipeline-local": "bun --env-file=.env.local run src/main-pipeline.ts",
    "references-local": "bun --env-file=.env.local run src/main-references.ts"
  },
  "devDependencies": {
    "@types/bun": "latest"
  },
  "peerDependencies": {
    "typescript": "^5"
  },
  "dependencies": {
    "@cruzhacks/shared": "file:../packages/shared",
    "@openrouter/sdk": "^0.10.2",
    "@supabase/supabase-js": "^2.49.4",
    "pino": "^10.3.1",
    "pino-pretty": "^13.1.3",
    "zod": "^4"
  }
}

```

### client/package.json

```
{
  "name": "cruzhacks-26-environmental-transparency",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "test": "node --import tsx --test --test-force-exit src/**/*.test.ts"
  },
  "dependencies": {
    "@supabase/supabase-js": "^2.90.1",
    "@tanstack/react-virtual": "^3.13.18",
    "@types/three": "^0.184.0",
    "clsx": "^2.1.1",
    "ml-kmeans": "^7.0.0",
    "next": "^16.2.4",
    "react": "^18.3.0",
    "react-dom": "^18.3.0",
    "recharts": "^3.6.0",
    "tailwind-merge": "^3.4.0",
    "three": "^0.184.0"
  },
  "devDependencies": {
    "@testing-library/dom": "^10.4.1",
    "@testing-library/react": "^16.3.2",
    "@types/node": "^20.12.0",
    "@types/react": "^18.3.0",
    "@types/react-dom": "^18.3.0",
    "autoprefixer": "^10.4.19",
    "eslint": "^10.2.1",
    "eslint-config-next": "^16.2.4",
    "global-jsdom": "^29.0.0",
    "jsdom": "^29.1.1",
    "postcss": "^8.5.13",
    "tailwindcss": "^3.4.3",
    "tsx": "^4.21.0",
    "typescript": "^5.4.0"
  }
}

```

### bill-pipline/python/requirements.txt

```
spacy>=3.7,<4.0
en-core-web-sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.7.1/en_core_web_sm-3.7.1-py3-none-any.whl
click
```

### supabase/maintenance/package.json

```
{
  "name": "maintenance",
  "private": true,
  "devDependencies": {
    "@types/bun": "latest",
    "bun-types": "^1.3.13"
  },
  "peerDependencies": {
    "typescript": "^5"
  },
  "dependencies": {
    "@cruzhacks/shared": "file:../../packages/shared",
    "@supabase/supabase-js": "^2.104.0"
  }
}

```

### packages/shared/package.json

```
{
  "name": "@cruzhacks/shared",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "exports": {
    ".": "./src/index.ts",
    "./api/congress": "./src/api/congress.ts",
    "./observability": "./src/observability/index.ts",
    "./utils/http": "./src/utils/http/index.ts",
    "./utils/concurrency": "./src/utils/concurrency.ts"
  },
  "scripts": {
    "test": "bun test"
  },
  "devDependencies": {
    "@types/bun": "latest"
  },
  "dependencies": {
    "@openrouter/sdk": "^0.10.2",
    "zod": "^4"
  }
}

```

### news-pipline/src/index.ts

```typescript
import pino from "pino";
import { parseArgs } from "util";
import { fetchArtifactsWorker, getFetchStrategy } from "./workers/fetch-worker";
import { close } from "./lib/database";
import { filterWorker } from "./workers/filter-worker";
import { enrichWorker } from "./workers/enrich-worker";
import { clusterPublishWorker } from "./workers/cluster-publish-worker";
import type { ArtifactType } from "./types";
import { getDocFormatSpec } from "./lib/llm";

const logger = pino({ name: "pipeline-cli" });

const VALID_ARTIFACT_TYPES: ArtifactType[] = ["article"];

const { values } = parseArgs({
    args: Bun.argv.slice(2),
    options: {
        fetch: { type: "boolean", default: false },
        filter: { type: "boolean", default: false },
        enrich: { type: "boolean", default: false },
        cluster: { type: "boolean", default: false },
        artifact_type: { type: "string", default: "article" },
    },
    strict: true,
});

function validateArtifactType(input: string): ArtifactType {
    if (!VALID_ARTIFACT_TYPES.includes(input as ArtifactType)) {
        throw new Error(`Unknown artifact type: "${input}". Available: ${VALID_ARTIFACT_TYPES.join(", ")}`);
    }
    return input as ArtifactType;
}

async function main() {
    const artifactType = validateArtifactType(values.artifact_type!);

    if (values.fetch) {
        const strategy = getFetchStrategy(artifactType);
        logger.info({ artifactType }, "starting fetch worker");
        await fetchArtifactsWorker(strategy);
    }

    if (values.filter) {
        logger.info({ artifactType }, "starting filter worker");
        const spec = getDocFormatSpec(artifactType);
        await filterWorker(spec);
    }

    if (values.enrich) {
        logger.info({ artifactType }, "starting enrich worker");
        await enrichWorker(artifactType);
    }

    if (values.cluster) {
        logger.info({ artifactType }, "starting cluster-publish worker");
        await clusterPublishWorker(artifactType);
    }
}

main()
    .then(async () => {
        await close();
        process.exit(0);
    })
    .catch(async (error) => {
        logger.fatal(error, "pipeline crashed");
        await close();
        process.exit(1);
    });
```

[204 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]