Personal academic portfolio website + curriculum vitae for Aaron D. Schroeder, Ph.D. β Research Associate Professor, Social & Decision Analytics Division, University of Virginia Biocomplexity Institute.
π Live site: https://dads2busy.github.io π Vita PDF: https://dads2busy.github.io/vita.pdf
One YAML file is the single source of truth. Tooling renders it into multiple outputs.
Data-build layer (write to SSOT)
βββββββββββββββββββββββββββββββββββββ
External sources β scripts/analyze_sources.py β β LLM-extracts CV/DOCX
CV / DOCX ββββββββββΆβ scripts/orcid_works.py β β ORCID API + diff
ORCID API β scripts/fill_missing_fields.py β β propose blanks to fill
β (all emit review diffs; β
β NEVER auto-edit profile.yaml) β
βββββββββββββββββββββββββββββββββββββ
β human paste
βΌ
βββββββββββββββββββββββββββββββββββββ
β site/content/profile.yaml (SSOT) β
β site/content/site_extras.yaml β
βββββββββββββββββββββββββββββββββββββ
β
Delivery layer (read from SSOT)
βββββββββββββββββββββββββββββββββββββ
β scripts/profile_to_content_jsons β
β β 5 site content JSONs β
β β website pages render β
β β
β rendercv render β vita.pdf β
β β
β site/src/app/page.tsx β
β β home page (reads YAML directly)β
βββββββββββββββββββββββββββββββββββββ
Full architecture details: see ARCHITECTURE.md.
Edit site/content/profile.yaml β find the matching publication-style section (Refereed Journal Articles, Book Chapters, Research / Technical Reports, etc.) and add/edit the entry. Use the existing entries' shape as a template.
Edit site/content/profile.yaml β Presentations: section. Set subcategory: to one of: Panelist, Presentations/Workshops, Committee, Lecture, Expert Forum, Expert Webinar.
Edit site/content/profile.yaml β Research Projects: section. Fields: name, funder, award, role, date, summary, slug.
Edit site/content/profile.yaml β Summary, Education, Skills, Awards & Honors sections. The home page reads these directly via js-yaml at build time.
Edit site/content/site_extras.yaml. Validated by a Zod schema at site/src/lib/site-extras-schema.ts.
Edit CATEGORY_ORDER in site/src/app/speaking/page.tsx.
- Publications:
site/src/components/WritingCitation.tsx - Presentations:
site/src/components/SpeakingCitation.tsx - Research projects:
site/src/components/ResearchPost.tsx
Edit design.theme: in profile.yaml (currently classic). RenderCV ships several built-in themes; full customization via custom RenderCV themes.
Drop the file into sources/cvs/, then run:
.venv/bin/python scripts/analyze_sources.pyReview sources/_diff.md (gitignored) and hand-paste accepted entries into profile.yaml. The script never touches profile.yaml directly.
.venv/bin/python orcid_works.pyReview orcid_diff.md (gitignored). Same paste workflow.
.venv/bin/python scripts/zenodo_works.pyReview zenodo_diff.md (gitignored). Same paste workflow β proposed entries land in profile.yaml's Data & Software section.
.venv/bin/python scripts/fill_missing_fields.pyReviews profile.yaml for blank name/category fields, suggests derivations, writes profile_fill_diff.md.
# Python venv for build tooling + RenderCV
python3 -m venv .venv
.venv/bin/pip install -r scripts/requirements.txt
# Node deps for the website
cd site && npm ciSet up .env at the repo root with:
ORCID_CLIENT_ID=APP-... # for orcid_works.py
ORCID_CLIENT_SECRET=...
ORCID_ID=0000-0003-4372-2241
ANTHROPIC_API_KEY=sk-ant-... # for analyze_sources.py
cd site && npm run dev # http://localhost:3000The predev npm script automatically regenerates the 5 content JSONs from profile.yaml before starting the dev server, so any YAML change is visible after the next page reload.
cd site && npm run buildOutput lands in site/out/. The prebuild npm script regenerates JSONs first.
cd site && npm run gen:vitaOutput: site/public/vita.pdf (gitignored). CI also runs RenderCV on every push.
.venv/bin/pytest scripts/tests/108 tests cover the data-build/delivery libraries.
| Layer | Tools |
|---|---|
| Frontend | Next.js 16 (App Router, TypeScript, static export), Tailwind CSS v4, Google Fonts (Inter / Instrument Serif / Geist Mono) |
| YAML loading | js-yaml + zod (validates site_extras.yaml) at build time |
| SSOT | site/content/profile.yaml (RenderCV-format YAML with custom keys) |
| Vita PDF | RenderCV (Python) β classic theme |
| CV / source analyzer | Anthropic Claude API (claude-sonnet-4-6) + mammoth for DOCX |
| ORCID sync | Python urllib against ORCID Public API v3.0 |
| Tests | pytest (Python side); Next.js build is the implicit test on the JS side |
| Deployment | GitHub Actions β GitHub Pages |
.
βββ site/ # Next.js project
β βββ content/
β β βββ profile.yaml # SSOT β edit this for content
β β βββ site_extras.yaml # Site-only display fields
β βββ src/
β β βββ app/ # App Router pages
β β βββ components/ # WritingCitation, SpeakingCitation, etc.
β β βββ lib/ # types, content loader, schemas
β βββ public/ # Static assets, generated vita.pdf
βββ scripts/
β βββ profile_to_content_jsons.py # SSOT β 5 site JSONs (delivery)
β βββ json_emitters.py # Per-section converters
β βββ analyze_sources.py # Plan G: CV/DOCX β review diff
β βββ analyzer_lib.py # Source analyzer helpers
β βββ orcid_diff_lib.py # Plan E: ORCID diff helpers
β βββ fill_missing_fields.py # Plan H: blank-field review tool
β βββ profile_derive_lib.py # Plan H: title/category derivation
β βββ tests/ # pytest suite (108 tests)
βββ sources/ # Drop-zone for raw artifacts
β βββ cvs/ # Old vitae (DOCX) for analyzer ingest
β βββ README.md # Drop-zone workflow
βββ orcid_works.py # Plan E: ORCID fetcher + diff
βββ orcid_works.json # ORCID snapshot (committed)
βββ docs/superpowers/plans/ # Implementation plans (Plans AβH)
βββ ARCHITECTURE.md # Data-build vs delivery layer details
βββ SSOT_PLAN.md # Higher-level SSOT vision
βββ .github/workflows/deploy.yml # CI: render vita + build + deploy
βββ old jekyll site/ # Archived original Jekyll site
- Add a new delivery output (e.g. CSL JSON for Zotero, LinkedIn paste view): write a pure Python emitter in
scripts/, mirror theprofile_to_content_jsons.pypattern, add shape-completeness tests. - Add a new section to profile.yaml: append to
cv.sections.<NewSection>. RenderCV will pick it up automatically. The website's content pages only render sections explicitly mapped inWRITING_SUBCATEGORY(inprofile_to_content_jsons.py). - Add a new delivery template: each delivery has its own format/template β RenderCV theme for the vita, React components for the website. Don't push delivery-specific formatting back into
profile.yaml.
See ARCHITECTURE.md for the design principles, and docs/superpowers/plans/ for the historical implementation plans (each documents a chunk of the migration from the original Jekyll site).
Content is licensed under Creative Commons Attribution 4.0. Code is MIT-licensed where not otherwise noted.