Scores a GitHub profile (deterministic + LLM-based) and generates recruiter-facing improvement tips. Built for a job-search SaaS's microservice layer.
cp .env.example .env # fill in GITHUB_TOKEN and LLM_API_KEY
uv sync
uv run uvicorn app.main:app --reloadGITHUB_TOKEN just needs public_repo/read scopes — it's only reading public data.
LLM_API_KEY is used with the default Gemini OpenAI-compatible endpoint.
If you swap providers, update llm_base_url and llm_model in app/config.py.
Logs are written to logs/app.log (general application logs) and logs/http_access.log
(HTTP request access logs). Both use rotating file handlers
curl -X POST http://localhost:8000/api/v1/analyze \
-H "Content-Type: application/json" \
-d '{"github_username": "torvalds", "target_role": "backend"}'- GraphQL query now pulls
contributionsCollection(real PR/review/commit activity, last 12 months), accountcreatedAt, and per-pinned-repolicenseInfo,hasIssuesEnabled,repositoryTopics, and best-effort README text. - Fixed a scoring bug: the old PR count measured PRs merged into the user's own
repos, not PRs the user actually authored. It now uses
contributionsCollection.totalPullRequestContributions. ai_servicenow accepts bothpinned_reposandrecent_repos. If a profile has no pinned repos, the AI scorer falls back to judging the most recent repos instead, providing actionable feedback on what to pin. UsesAsyncOpenAIagainst a Gemini-compatible endpoint so the LLM call no longer blocks the FastAPI event loop. Returns structuredtips(issue / action / impact) instead of a single prose string.- Logging now writes to two rotating log files:
logs/app.log: general application logs (INFO/WARNING/ERROR from all modules)logs/http_access.log: HTTP request access logs (Apache combined format)
- HTTP access logging via
AccessLogMiddleware— every request is logged with client IP, method, path, query string, status code, duration, and user-agent. github_serviceraises typed exceptions (GithubUserNotFoundError,GithubApiError) instead of throwing rawKeyErrors or 404-ing on every failure mode, and retries transient failures (timeouts, 429, 5xx) with backoff viatenacity.- Cache keys are normalized (lowercased username + target_role) so
Torvaldsandtorvaldsshare a cache entry instead of silently doubling your miss rate. - Split into
config.py/schemas.py/services//core/per the structure discussed —github_service.py,scoring_service.py, andcore/cache.pyare meant to be the stable layer.
profile_cacheis in-process memory (TTLCache). Fine for one instance.- No persistence. If you want users to track score improvement over time.
- Logging:
RotatingFileHandleris not safe across multiple OS processes writing the same file concurrently. Fine foruvicorn --reload's single worker, or a single gunicorn worker. For multi-worker deployments, either give each worker its own log file (suffix by PID), switch toQueueHandler+ a listener process, or ship logs to a centralized service. - README matching only tries
README.mdandreadme.mdat the repo root. It will miss READMEs with other casings/paths.