Skip to content

sidc26/LeanInCompass

Repository files navigation

Compass API

FastAPI backend for the Lean In Compass product — circle matching, progressive onboarding, One Action tracking, and AI-assisted industry classification.

Stack

Layer Choice
Backend FastAPI + Python 3.11
Frontend Vanilla JS SPA (no build step)
Database PostgreSQL (Neon free tier recommended)
ORM SQLAlchemy 2.0 async (asyncpg)
Migrations Alembic
Validation Pydantic v2
AI Groq (llama-3.1-8b-instant)
Tests pytest

Short Note

My intro call with Ms. Griswold, CEO, shaped my approach on this project. She emphasized that Lean In’s priority is scaling users. I explored LeanIn.org and noticed the current onboarding, while clean and accessible, captures limited signal about who users are or what they need. I saw an opportunity to build on top of this by adding lightweight personalization early on.

That led to Compass. From the moment someone signs up, it gathers just enough context to guide them toward a relevant starting point, whether they are starting a circle, joining one, or exploring. It uses inputs like industry, career stage, and goals to surface matched circles and resources, so the experience feels tailored rather than generic.

Compass also supports engagement between meetings. Members commit to a One Action and share brief weekly reflections. This creates a structured but low-friction data layer that can power tools like AI-generated pre-meeting briefs for circle leaders, highlighting progress, blockers, and discussion topics. Over time, this can also provide useful signals around circle health through engagement trends.

My implementation took just over 3 hours.

Tools: I used Anthropic’s Claude for research, architecture, and backend implementation. Groq’s API, running LLaMA models, powers the industry classifier in this version. In production, I would use Claude Haiku for classification and Sonnet for summaries.

Next: Next steps would be completing the meeting summary endpoint, adding circle health scoring based on engagement, and building a proper auth layer with JWT and role-based access.

Quick start

Note for reviewers: This project was built and tested against a Neon hosted Postgres database. If you don't have Docker installed, you can create a free Neon account and connect in under two minutes — no local setup required. If you do have Docker, a docker-compose.yml is included and works equally well. Both paths are documented below.

1. Get a Postgres database

Option A — Neon (no Docker required, recommended for quick setup)

  1. Go to neon.tech and create a free account
  2. Create a new project — call it compass
  3. On the dashboard, copy the connection string (it looks like postgresql://user:pass@ep-xxx.neon.tech/neondb)
  4. Change postgresql:// to postgresql+asyncpg:// — that's your DATABASE_URL

Option B — Docker (local Postgres)

docker compose up -d

Use this as your DATABASE_URL:

DATABASE_URL=postgresql+asyncpg://compass:compass@localhost:5432/compass

2. Get a free Groq API key

  1. Go to console.groq.com and sign up
  2. Go to API Keys and create a new key

3. Configure environment variables

cp .env.example .env

Open .env and fill in your values:

DATABASE_URL=postgresql+asyncpg://user:pass@ep-xxx.neon.tech/neondb
GROQ_API_KEY=your_groq_key_here

4. Install Python dependencies

python -m venv .venv

# Mac/Linux:
source .venv/bin/activate

# Windows:
.venv\Scripts\activate

pip install -r requirements.txt

5. Run database migrations

alembic upgrade head

6. Load seed data

python -m app.seed

You'll see five meeting IDs printed — these can be used with POST /meetings/{id}/actions to commit new One Actions via the API.

7. Start the dev server

uvicorn app.main:app --reload

8. Open the app

Surface URL
Frontend (SPA) http://localhost:8000
Swagger UI http://localhost:8000/docs
ReDoc http://localhost:8000/redoc

Demo accounts

All accounts use passphrase compass.

Email What it demonstrates
sarah.chen@example.com 5-week engagement streak · in-progress One Action
maria.rodriguez@example.com 3-week streak · committed action with reflections
jennifer.kim@example.com Finance Power Circle · committed negotiation action
alex.thompson@example.com Incomplete profile (red dot) · Campus Connect
lisa.park@example.com Two circles (Campus Connect + Nonprofit Warriors) · two committed actions
priya.patel@example.com 0-week streak · committed action — ideal for the 0→1 reflection demo

What to try

Onboarding flow

  1. Click Get Started on the landing page and complete the three-step onboarding
  2. After finishing, the dashboard defaults to the explore view — click Find a Circle to Join or Start My Own Circle to set your intent

Matching & joining

  1. Log in as sarah.chen@example.com (passphrase: compass)
  2. Go to Find Matches — see ranked circles with score breakdowns
  3. Join a circle — it immediately appears in My Circles on the Profile tab

One Action & reflections

  1. Log in as priya.patel@example.com
  2. Go to the Profile tab → My Circles — she has a committed action with zero reflections
  3. Click + Add reflection and submit one — her streak jumps from 0 to 1 week
  4. Post a second reflection in a later week to see the streak grow

Committing a new One Action

  1. Join any circle that doesn't already have an active action for your account
  2. The circle card shows + Commit a One Action — click it, fill in the description and due date
  3. The action immediately appears with tracking controls

Profile completeness

  1. Log in as alex.thompson@example.com — profile is incomplete (red dot in navbar)
  2. Go to the Profile tab and fill in the missing fields — the score updates in real time

Running tests

pytest tests/ -v

The matching algorithm tests are pure unit tests — no database or API key needed.


API overview

Onboarding

Method Path Description
POST /onboarding/start Create user + profile (step 1)
PATCH /users/{id}/profile Update profile fields, recalculate completeness
GET /users/{id}/profile-completeness Get score + missing fields (drives "red dot")

Dashboard

Method Path Description
GET /users/{id}/dashboard Personalised dashboard, branches on intent

Auth

Method Path Description
POST /auth/login Authenticate with email + passphrase

Circles

Method Path Description
GET /circles List circles with optional filters
GET /users/{id}/circle-matches Ranked matches with score breakdown
GET /users/{id}/circles Circles the user belongs to, with active One Action + engagement streak
POST /circles/{id}/join-requests Join a circle

Actions & Engagement

Method Path Description
POST /meetings/{id}/actions Commit a One Action after a meeting
GET /actions/{id} Get a single action with all updates
PATCH /actions/{id} Update status or add a completion note
POST /actions/{id}/updates Post a free-text progress update
POST /actions/{id}/reflections Post a structured weekly reflection (progress, what worked, challenge, next step, confidence 1–5)

Matching algorithm

Located in app/services/matching.py. Deterministic and explainable — no ML.

total_score = (
    goal_overlap_score  * w1 +   # fraction of user goals covered by circle
    career_stage_score  * w2 +   # proximity of user stage to member distribution
    industry_score      * w3 +   # user industry vs circle industry focus
    format_match_score  * w4     # virtual / in-person / hybrid alignment
)

Weights w1–w4 are personalised per user via importance toggles (low / medium / high) set on their profile. Unset dimensions fall back to algorithm defaults. Weights are always normalised to sum to 1.0.

spots_remaining is returned as a separate informational field and does not affect ranking.

Profile completeness

Located in app/services/completeness.py.

Field Points
intent 20
career_stage 15
industry 15
goals (all 3) 20
location 10
open_to_networking 10
linkedin_url 10
Total 100

is_complete = score >= 80. This drives the red-dot UI indicator.


Engagement streak

Located in app/services/engagement.py.

A streak counts consecutive ISO weeks (Monday–Sunday) where the user posted at least one reflection or action update. Starting from the most recent week with activity and counting backwards — the current week is not penalised if it hasn't been posted to yet.

streak = 0
anchor = most-recent week with an update
while anchor in weeks_with_updates:
    streak += 1
    anchor = previous week

The streak is returned on GET /users/{id}/circles and displayed on the profile page next to each circle.


Structured One Action reflections

POST /actions/{id}/reflections accepts:

{
  "user_id": "...",
  "progress":    "What I did this week toward my action",
  "what_worked": "Optional — what went well",
  "challenge":   "Optional — what was hard",
  "next_step":   "What I'll do before next week",
  "confidence":  4
}

These are formatted into a human-readable update_text and stored in action_updates. The format is deliberately structured so the planned AI summary endpoint can ingest individual reflections and produce richer circle-health summaries (see "What I'd build with more time").


What I'd build with more time

AI meeting summary

After each meeting, a circle leader should be able to generate a structured summary of all members' One Actions and reflections — wins, blockers, confidence trends, and suggested discussion topics for the next session.

The structured reflection format (--- Weekly Reflection --- / Progress / What worked / Challenge / Next step / Confidence) is deliberately machine-readable so a prompt can ingest it directly. The GROQ_API_KEY environment variable is already wired into the app config.

The implementation would be:

  1. A POST /meetings/{id}/summary/generate endpoint that aggregates all actions + reflection updates for the meeting, sends them to Groq (llama-3.3-70b-versatile), and stores the structured result
  2. A GET /meetings/{id}/summary endpoint to retrieve the stored summary
  3. A Meeting Summary tab inside each circle card — circle leader taps "Generate summary" after the meeting, result is visible to all members

AI Circle Health Summary (stretch)

Status: Not yet implemented — designed for the next milestone.

The structured reflection format is intentionally built to feed an automated circle-health pipeline:

  1. Aggregate reflections across all circle members between meetings.
  2. Send to Groq (llama-3.3-70b-versatile) with a prompt that synthesises themes, blockers, confidence trends, and standout wins.
  3. Return to the circle leader a pre-meeting brief: what the group has been working on, common challenges, suggested discussion topics, and momentum indicators.

This extends the planned POST /meetings/{id}/summary/generate endpoint described above — the same action aggregation pipeline would include per-reflection themes and produce a proactive brief before the meeting rather than after.


Features in the codebase not yet surfaced in the UI

These are fully implemented on the backend and exist intentionally — I built them to support future milestones but didn't reach them on the frontend within the scope of this project.

Feature Where it lives Why it's there
GET /actions/{action_id} app/routers/actions.py Fetching a single action with all its updates. Supports a future action-detail page or deep-link from notifications.
POST /actions/{id}/updates (plain text) app/routers/actions.py Free-text progress update, separate from structured reflections. Reflections are the preferred path; plain updates remain for lightweight check-ins.
GET /circles (with filters) app/routers/circles.py Now exposed via the Circle Directory page.
ActionStatusEnum.missed app/models/enums.py Reserved for a scheduled task that auto-transitions overdue actions. No Celery worker yet.
Meeting.curriculum_topic, Meeting.notes app/models/meeting.py Fields for structured meeting agendas. No create-meeting endpoint exists yet; populated via seed only.
CircleMemberRoleEnum.leader app/models/circle.py Role is stored and returned but not yet used for authorization — all members can currently take the same actions. Role-gating is a production prerequisite.
UserProfile.onboarding_step app/models/user.py Incremented on each profile update. Intended to drive a resume-onboarding flow (e.g. deep-link sends user back to the right step). Frontend navigates by view state instead.
JoinRequestIn.message app/schemas/circle.py Accepted in the join request body and echoed in the response, but not stored — the stub goes straight to membership. The production path stores it in a pending-requests table for leader review.

What's stubbed

Feature Current stub Production path
Authentication Email + plaintext passphrase JWT + OAuth2 (e.g. Auth0); bcrypt for passphrase
Authorization No per-request user check Middleware that verifies the token's sub matches the user_id in the path
Join requests Direct membership creation Pending-request table + leader notification email
Action reminders Not implemented Celery + Redis scheduled task to mark overdue actions missed
Dashboard content Hardcoded in router CMS (Contentful / Sanity)
CORS origins * Restrict to Lean In frontend domain
Circle creation No endpoint Full CRUD for circles with leader assignment
Pagination No limits on list endpoints limit / offset query params on /circles, /users/{id}/circles, action updates
Circle health AI Not implemented See "Planned" section above

Project structure

compass-api/
├── app/
│   ├── main.py              # FastAPI app + router registration
│   ├── config.py            # pydantic-settings env config
│   ├── database.py          # Async engine + session factory
│   ├── models/
│   │   ├── enums.py         # All shared enum definitions
│   │   ├── user.py          # User, UserProfile, UserGoal
│   │   ├── circle.py        # Circle, CircleMember, CircleFocus
│   │   ├── meeting.py       # Meeting
│   │   └── action.py        # Action, ActionUpdate
│   ├── schemas/             # Pydantic v2 request/response models
│   ├── routers/             # FastAPI route handlers
│   ├── services/
│   │   ├── matching.py      # Pure scoring functions + DB entry point
│   │   ├── completeness.py  # Profile completeness scorer
│   │   └── industry.py      # Groq: classify free-text industry at write time
│   └── seed.py              # Realistic demo data
├── alembic/                 # Database migrations
├── tests/
│   └── test_matching.py     # Matching algorithm unit tests
├── docker-compose.yml       # Optional local Postgres
├── requirements.txt
└── .env.example

About

FastAPI + vanilla JS app for Lean In circle matching, progressive onboarding, and One Action engagement tracking.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors