cv-renderer 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cv_renderer/__init__.py +0 -0
- cv_renderer/__main__.py +5 -0
- cv_renderer/examples/data/base_en.yaml +98 -0
- cv_renderer/examples/data/base_tr.yaml +92 -0
- cv_renderer/examples/data/labels/en.yaml +12 -0
- cv_renderer/examples/data/labels/tr.yaml +12 -0
- cv_renderer/examples/profiles/ai-engineer.yaml +13 -0
- cv_renderer/examples/profiles/companies/spotify.yaml +49 -0
- cv_renderer/examples/profiles/general.yaml +9 -0
- cv_renderer/examples/tags.yaml +7 -0
- cv_renderer/exports.py +29 -0
- cv_renderer/filter.py +160 -0
- cv_renderer/lint.py +631 -0
- cv_renderer/loader.py +66 -0
- cv_renderer/models.py +128 -0
- cv_renderer/render.py +162 -0
- cv_renderer/templates/main.html.j2 +238 -0
- cv_renderer-0.1.0.dist-info/METADATA +260 -0
- cv_renderer-0.1.0.dist-info/RECORD +22 -0
- cv_renderer-0.1.0.dist-info/WHEEL +4 -0
- cv_renderer-0.1.0.dist-info/entry_points.txt +2 -0
- cv_renderer-0.1.0.dist-info/licenses/LICENSE +201 -0
cv_renderer/__init__.py
ADDED
|
File without changes
|
cv_renderer/__main__.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
meta:
|
|
2
|
+
name: Your Name
|
|
3
|
+
title:
|
|
4
|
+
default: Software Engineer
|
|
5
|
+
ai: AI Engineer
|
|
6
|
+
data: Data Engineer
|
|
7
|
+
location: City, Country
|
|
8
|
+
email: you@example.com
|
|
9
|
+
phone: "+1 555 000 0000"
|
|
10
|
+
links:
|
|
11
|
+
linkedin: linkedin.com/in/yourprofile
|
|
12
|
+
github: github.com/yourusername
|
|
13
|
+
portfolio: yourportfolio.com
|
|
14
|
+
|
|
15
|
+
about:
|
|
16
|
+
default: |
|
|
17
|
+
Brief professional summary describing your background and focus.
|
|
18
|
+
ai: |
|
|
19
|
+
AI-focused variant of your summary.
|
|
20
|
+
data: |
|
|
21
|
+
Data-focused variant of your summary.
|
|
22
|
+
|
|
23
|
+
experience:
|
|
24
|
+
- company: Company Name
|
|
25
|
+
title:
|
|
26
|
+
default: Software Engineer
|
|
27
|
+
ai: AI Engineer
|
|
28
|
+
data: Data Engineer
|
|
29
|
+
location: City
|
|
30
|
+
start: "2024"
|
|
31
|
+
end: present
|
|
32
|
+
tags: [ai, data, backend]
|
|
33
|
+
description: "Short description of the team or product."
|
|
34
|
+
bullets:
|
|
35
|
+
- text: "Accomplishment with measurable impact."
|
|
36
|
+
tags: [ai, backend]
|
|
37
|
+
- text: "Another accomplishment."
|
|
38
|
+
tags: [data]
|
|
39
|
+
|
|
40
|
+
- company: Previous Company
|
|
41
|
+
title:
|
|
42
|
+
default: Intern
|
|
43
|
+
ai: Intern
|
|
44
|
+
data: Intern
|
|
45
|
+
location: City
|
|
46
|
+
start: 2023-06
|
|
47
|
+
end: 2023-09
|
|
48
|
+
tags: [ml]
|
|
49
|
+
bullets:
|
|
50
|
+
- text: "What you did and why it mattered."
|
|
51
|
+
tags: [ml]
|
|
52
|
+
|
|
53
|
+
education:
|
|
54
|
+
- institution: University Name
|
|
55
|
+
degree: "Bachelor's Degree, Computer Science"
|
|
56
|
+
location: Country
|
|
57
|
+
start: 2020
|
|
58
|
+
end: 2024
|
|
59
|
+
tags: [always]
|
|
60
|
+
|
|
61
|
+
skills:
|
|
62
|
+
- category: Backend and API
|
|
63
|
+
tags: [backend, always]
|
|
64
|
+
items: "Python, FastAPI, ..."
|
|
65
|
+
- category: Data Engineering
|
|
66
|
+
tags: [data]
|
|
67
|
+
items: "PostgreSQL, dbt Core, ..."
|
|
68
|
+
- category: AI and LLM Systems
|
|
69
|
+
tags: [ai, llm]
|
|
70
|
+
# items can also be a list of {text, tags} to filter within a category,
|
|
71
|
+
# not just across categories — useful when a category mixes core tools
|
|
72
|
+
# (keep for every profile) with niche ones (only relevant to some jobs).
|
|
73
|
+
items:
|
|
74
|
+
- text: LangChain
|
|
75
|
+
tags: [ai]
|
|
76
|
+
- text: LangGraph
|
|
77
|
+
tags: [ai]
|
|
78
|
+
- category: ML and Evaluation
|
|
79
|
+
tags: [ml, ai]
|
|
80
|
+
items: "PyTorch, scikit-learn, ..."
|
|
81
|
+
- category: Infra and DevOps
|
|
82
|
+
tags: [devops, backend]
|
|
83
|
+
items: "Docker, CI/CD, ..."
|
|
84
|
+
|
|
85
|
+
projects:
|
|
86
|
+
- name: Project Name
|
|
87
|
+
subtitle: Open Source, GitHub
|
|
88
|
+
year: 2024
|
|
89
|
+
tags: [ai, backend]
|
|
90
|
+
bullets:
|
|
91
|
+
- text: "What you built and why it matters."
|
|
92
|
+
tags: [ai, backend]
|
|
93
|
+
|
|
94
|
+
additional:
|
|
95
|
+
languages:
|
|
96
|
+
- English (Native)
|
|
97
|
+
certifications:
|
|
98
|
+
- "Your Certification, Issuer"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
meta:
|
|
2
|
+
name: Adınız Soyadınız
|
|
3
|
+
title:
|
|
4
|
+
default: Yazılım Mühendisi
|
|
5
|
+
ai: Yapay Zeka Mühendisi
|
|
6
|
+
data: Veri Mühendisi
|
|
7
|
+
location: Şehir, Türkiye
|
|
8
|
+
email: siz@ornek.com
|
|
9
|
+
phone: "+90 5XX XXX XX XX"
|
|
10
|
+
links:
|
|
11
|
+
linkedin: linkedin.com/in/profiliniz
|
|
12
|
+
github: github.com/kullaniciadi
|
|
13
|
+
portfolio: portfolyonuz.com
|
|
14
|
+
|
|
15
|
+
about:
|
|
16
|
+
default: |
|
|
17
|
+
Genel profesyonel özetiniz.
|
|
18
|
+
ai: |
|
|
19
|
+
Yapay zeka odaklı özetiniz.
|
|
20
|
+
data: |
|
|
21
|
+
Veri odaklı özetiniz.
|
|
22
|
+
|
|
23
|
+
experience:
|
|
24
|
+
- company: Şirket Adı
|
|
25
|
+
title:
|
|
26
|
+
default: Yazılım Mühendisi
|
|
27
|
+
ai: Yapay Zeka Mühendisi
|
|
28
|
+
data: Veri Mühendisi
|
|
29
|
+
location: Şehir
|
|
30
|
+
start: "2024"
|
|
31
|
+
end: present
|
|
32
|
+
tags: [ai, data, backend]
|
|
33
|
+
description: "Ekip veya ürün kısa açıklaması."
|
|
34
|
+
bullets:
|
|
35
|
+
- text: "Ölçülebilir etkiyle bir başarı."
|
|
36
|
+
tags: [ai, backend]
|
|
37
|
+
- text: "Başka bir başarı."
|
|
38
|
+
tags: [data]
|
|
39
|
+
|
|
40
|
+
- company: Önceki Şirket
|
|
41
|
+
title:
|
|
42
|
+
default: Stajyer
|
|
43
|
+
ai: Stajyer
|
|
44
|
+
data: Stajyer
|
|
45
|
+
location: Şehir
|
|
46
|
+
start: 2023-06
|
|
47
|
+
end: 2023-09
|
|
48
|
+
tags: [ml]
|
|
49
|
+
bullets:
|
|
50
|
+
- text: "Ne yaptınız ve neden önemliydi."
|
|
51
|
+
tags: [ml]
|
|
52
|
+
|
|
53
|
+
education:
|
|
54
|
+
- institution: Üniversite Adı
|
|
55
|
+
degree: "Lisans, Bilgisayar Mühendisliği"
|
|
56
|
+
location: Türkiye
|
|
57
|
+
start: 2020
|
|
58
|
+
end: 2024
|
|
59
|
+
tags: [always]
|
|
60
|
+
|
|
61
|
+
skills:
|
|
62
|
+
- category: Backend ve API
|
|
63
|
+
tags: [backend, always]
|
|
64
|
+
items: "Python, FastAPI, ..."
|
|
65
|
+
- category: Veri Mühendisliği
|
|
66
|
+
tags: [data]
|
|
67
|
+
items: "PostgreSQL, dbt Core, ..."
|
|
68
|
+
- category: AI ve LLM Sistemleri
|
|
69
|
+
tags: [ai, llm]
|
|
70
|
+
items: "LangChain, LangGraph, ..."
|
|
71
|
+
- category: ML ve Değerlendirme
|
|
72
|
+
tags: [ml, ai]
|
|
73
|
+
items: "PyTorch, scikit-learn, ..."
|
|
74
|
+
- category: Altyapı ve DevOps
|
|
75
|
+
tags: [devops, backend]
|
|
76
|
+
items: "Docker, CI/CD, ..."
|
|
77
|
+
|
|
78
|
+
projects:
|
|
79
|
+
- name: Proje Adı
|
|
80
|
+
subtitle: Açık Kaynak, GitHub
|
|
81
|
+
year: 2024
|
|
82
|
+
tags: [ai, backend]
|
|
83
|
+
bullets:
|
|
84
|
+
- text: "Ne inşa ettiniz ve neden önemli."
|
|
85
|
+
tags: [ai, backend]
|
|
86
|
+
|
|
87
|
+
additional:
|
|
88
|
+
languages:
|
|
89
|
+
- Türkçe (Anadil)
|
|
90
|
+
- İngilizce (İş Düzeyi)
|
|
91
|
+
certifications:
|
|
92
|
+
- "Sertifikanız, Kurum"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
months: [Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
|
|
2
|
+
|
|
3
|
+
about: About Me
|
|
4
|
+
experience: Professional Experience
|
|
5
|
+
education: Education
|
|
6
|
+
skills: Skills
|
|
7
|
+
projects: Projects
|
|
8
|
+
additional: Additional Information
|
|
9
|
+
languages: Languages
|
|
10
|
+
certifications: Certificates
|
|
11
|
+
present: Present
|
|
12
|
+
to: "to"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
months: [Oca, Şub, Mar, Nis, May, Haz, Tem, Ağu, Eyl, Eki, Kas, Ara]
|
|
2
|
+
|
|
3
|
+
about: Hakkımda
|
|
4
|
+
experience: Profesyonel Deneyim
|
|
5
|
+
education: Eğitim
|
|
6
|
+
skills: Yetenekler
|
|
7
|
+
projects: Projeler
|
|
8
|
+
additional: Ek Bilgiler
|
|
9
|
+
languages: Diller
|
|
10
|
+
certifications: Sertifikalar
|
|
11
|
+
present: Günümüz
|
|
12
|
+
to: "-"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
name: AI Engineer
|
|
2
|
+
description: Foregrounds LLM systems, agentic AI, and evaluation work
|
|
3
|
+
variant: ai
|
|
4
|
+
lang: en
|
|
5
|
+
focus_tags: [ai, llm, ml]
|
|
6
|
+
deprioritize_tags: [data]
|
|
7
|
+
max_bullets_per_job: 4
|
|
8
|
+
sections: [experience, skills, education, projects, additional]
|
|
9
|
+
skill_categories:
|
|
10
|
+
- AI and LLM Systems
|
|
11
|
+
- ML and Evaluation
|
|
12
|
+
- Backend and API
|
|
13
|
+
- Infra and DevOps
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Spotify - Backend Engineer
|
|
2
|
+
description: Tailored for a specific job posting — demonstrates every override field a company-specific profile can use
|
|
3
|
+
variant: data
|
|
4
|
+
lang: en
|
|
5
|
+
|
|
6
|
+
# Overrides the headline and summary shown in the base data's `variant` lookup.
|
|
7
|
+
# Write these directly for this application; don't translate from the other
|
|
8
|
+
# language file, and don't invent facts — same rules as any other CV content.
|
|
9
|
+
# The summary follows the title -> skills & accomplishments -> career goals
|
|
10
|
+
# structure in docs/references/about-me-section.md.
|
|
11
|
+
# Never name the target company here (or anywhere else in the CV content) —
|
|
12
|
+
# that belongs in a cover letter, not a resume that might get reused or shared.
|
|
13
|
+
title_override: Backend Engineer
|
|
14
|
+
about_override: |
|
|
15
|
+
Backend Engineer with production experience building FastAPI services on top of governed data layers. Shipped a 109-model dbt warehouse layer, added automated data-correctness tests that catch reporting errors at the source, and supported BI reporting through Apache Superset. Looking to bring that same reliability focus to a data platform team.
|
|
16
|
+
|
|
17
|
+
# Replaces an entry's tag-filtered bullets outright — use when a real
|
|
18
|
+
# accomplishment needs reframing around a different angle than its default
|
|
19
|
+
# bullets take (see filter.py::apply_profile). Keyed by ExperienceEntry.company.
|
|
20
|
+
# An override also forces the entry into the CV even if its own tags would
|
|
21
|
+
# otherwise exclude it entirely — only do this when something in it is
|
|
22
|
+
# genuinely relevant; if nothing is, leave it out and let tag filtering drop it.
|
|
23
|
+
experience_overrides:
|
|
24
|
+
Company Name:
|
|
25
|
+
- "Rewritten bullet #1 for this application, angled at what this employer cares about."
|
|
26
|
+
- "Rewritten bullet #2, still real, still XYZ-formatted, still no invented numbers."
|
|
27
|
+
|
|
28
|
+
# Same idea as experience_overrides, but keyed by Project.name.
|
|
29
|
+
project_overrides:
|
|
30
|
+
Project Name:
|
|
31
|
+
- "Rewritten project bullet emphasizing the angle relevant to this posting."
|
|
32
|
+
|
|
33
|
+
# Reorders rendered projects by name — the most relevant project for this
|
|
34
|
+
# application isn't always the first one in the base data. Same mechanism as
|
|
35
|
+
# skill_categories below, just for projects instead of skill categories.
|
|
36
|
+
project_order: [Project Name]
|
|
37
|
+
|
|
38
|
+
focus_tags: [data, backend]
|
|
39
|
+
deprioritize_tags: [ai, llm, ml]
|
|
40
|
+
max_bullets_per_job: 4
|
|
41
|
+
sections: [experience, education, skills, projects, additional]
|
|
42
|
+
|
|
43
|
+
# Filters and orders which skill categories render. Skill items can also carry
|
|
44
|
+
# their own tags (see base_en.yaml's "AI and LLM Systems" category) so a single
|
|
45
|
+
# category can show a different subset of items per profile.
|
|
46
|
+
skill_categories:
|
|
47
|
+
- Data Engineering
|
|
48
|
+
- Backend and API
|
|
49
|
+
- Infra and DevOps
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ai: LLM systems, agentic workflows, evaluation, RAG
|
|
2
|
+
llm: Model-specific work — prompt engineering, tool use, evals
|
|
3
|
+
ml: Training, distillation, PyTorch, model deployment
|
|
4
|
+
data: dbt, SQL, warehouse, BI dashboards, ETL/ELT
|
|
5
|
+
backend: FastAPI, API design, auth, logging, Redis
|
|
6
|
+
devops: Docker, CI/CD, OTEL, pipeline infrastructure
|
|
7
|
+
always: Items that appear in every profile (education, contact)
|
cv_renderer/exports.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def export_pdf(html_path: Path) -> Path:
|
|
7
|
+
try:
|
|
8
|
+
from playwright.sync_api import sync_playwright
|
|
9
|
+
except ImportError as exc:
|
|
10
|
+
raise SystemExit(
|
|
11
|
+
"PDF export needs the 'pdf' extra: pip install 'cv-renderer[pdf]' "
|
|
12
|
+
"then run: playwright install chromium"
|
|
13
|
+
) from exc
|
|
14
|
+
|
|
15
|
+
pdf_path = html_path.with_suffix(".pdf")
|
|
16
|
+
|
|
17
|
+
with sync_playwright() as pw:
|
|
18
|
+
browser = pw.chromium.launch()
|
|
19
|
+
page = browser.new_page()
|
|
20
|
+
page.goto(html_path.as_uri())
|
|
21
|
+
page.emulate_media(media="print")
|
|
22
|
+
page.pdf(
|
|
23
|
+
path=str(pdf_path),
|
|
24
|
+
format="A4",
|
|
25
|
+
print_background=True,
|
|
26
|
+
)
|
|
27
|
+
browser.close()
|
|
28
|
+
|
|
29
|
+
return pdf_path
|
cv_renderer/filter.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from cv_renderer.models import Bullet, CVData, Profile
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _resolve(variants: dict[str, str], variant: str) -> str:
|
|
7
|
+
return variants.get(variant) or variants.get("default", "")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _filter_bullets(
|
|
11
|
+
bullets: list[Bullet],
|
|
12
|
+
focus: set[str],
|
|
13
|
+
deprio: set[str],
|
|
14
|
+
max_n: int,
|
|
15
|
+
) -> list[str]:
|
|
16
|
+
focused: list[str] = []
|
|
17
|
+
neutral: list[str] = []
|
|
18
|
+
for b in bullets:
|
|
19
|
+
btags = set(b.tags)
|
|
20
|
+
if btags & focus:
|
|
21
|
+
focused.append(b.text)
|
|
22
|
+
elif not btags.issubset(deprio):
|
|
23
|
+
neutral.append(b.text)
|
|
24
|
+
return (focused + neutral)[:max_n]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _resolve_bullets(
|
|
28
|
+
override: list[str] | None,
|
|
29
|
+
bullets: list[Bullet],
|
|
30
|
+
focus: set[str],
|
|
31
|
+
deprio: set[str],
|
|
32
|
+
max_n: int,
|
|
33
|
+
) -> list[str]:
|
|
34
|
+
if override is not None:
|
|
35
|
+
return override
|
|
36
|
+
return _filter_bullets(bullets, focus, deprio, max_n)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _filter_skill_items(items: list[Bullet], focus: set[str], deprio: set[str]) -> list[str]:
|
|
40
|
+
# Unlike job/project bullets, skill items are short and untagged-by-default:
|
|
41
|
+
# an item with no tags is generic and always kept, not dropped as "empty subset of deprio".
|
|
42
|
+
focused: list[str] = []
|
|
43
|
+
neutral: list[str] = []
|
|
44
|
+
for item in items:
|
|
45
|
+
tags = set(item.tags)
|
|
46
|
+
if tags & focus:
|
|
47
|
+
focused.append(item.text)
|
|
48
|
+
elif tags and tags.issubset(deprio):
|
|
49
|
+
continue
|
|
50
|
+
else:
|
|
51
|
+
neutral.append(item.text)
|
|
52
|
+
return focused + neutral
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def apply_profile(cv: CVData, profile: Profile, labels: dict[str, str]) -> dict[str, Any]:
|
|
56
|
+
variant = profile.variant
|
|
57
|
+
focus = set(profile.focus_tags)
|
|
58
|
+
deprio = set(profile.deprioritize_tags)
|
|
59
|
+
max_b = profile.max_bullets_per_job
|
|
60
|
+
include_all = not focus
|
|
61
|
+
|
|
62
|
+
meta: dict[str, Any] = {
|
|
63
|
+
"name": cv.meta.name,
|
|
64
|
+
"title": profile.title_override or _resolve(cv.meta.title, variant),
|
|
65
|
+
"location": cv.meta.location,
|
|
66
|
+
"email": cv.meta.email,
|
|
67
|
+
"phone": cv.meta.phone,
|
|
68
|
+
"links": cv.meta.links.model_dump(),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
experience: list[dict[str, Any]] = []
|
|
72
|
+
for job in cv.experience:
|
|
73
|
+
override = profile.experience_overrides.get(job.company)
|
|
74
|
+
bullets = _resolve_bullets(override, job.bullets, focus, deprio, max_b)
|
|
75
|
+
if not bullets:
|
|
76
|
+
continue
|
|
77
|
+
experience.append(
|
|
78
|
+
{
|
|
79
|
+
"company": job.company,
|
|
80
|
+
"title": _resolve(job.title, variant),
|
|
81
|
+
"location": job.location,
|
|
82
|
+
"start": job.start,
|
|
83
|
+
"end": job.end,
|
|
84
|
+
"description": job.description,
|
|
85
|
+
"bullets": bullets,
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
skills: list[dict[str, Any]]
|
|
90
|
+
if profile.skill_overrides is not None:
|
|
91
|
+
skills = [
|
|
92
|
+
{"category": category, "entries": entries}
|
|
93
|
+
for category, entries in profile.skill_overrides.items()
|
|
94
|
+
]
|
|
95
|
+
else:
|
|
96
|
+
include_tags = focus | {"always"}
|
|
97
|
+
skills = []
|
|
98
|
+
for cat in cv.skills:
|
|
99
|
+
if profile.skill_categories is not None:
|
|
100
|
+
if cat.category not in profile.skill_categories:
|
|
101
|
+
continue
|
|
102
|
+
elif not include_all and not (set(cat.tags) & include_tags):
|
|
103
|
+
continue
|
|
104
|
+
entries = _filter_skill_items(cat.items, focus, deprio)
|
|
105
|
+
if not entries:
|
|
106
|
+
continue
|
|
107
|
+
skills.append({"category": cat.category, "entries": entries})
|
|
108
|
+
|
|
109
|
+
if profile.skill_categories is not None:
|
|
110
|
+
order = {name: i for i, name in enumerate(profile.skill_categories)}
|
|
111
|
+
skills.sort(key=lambda s: order.get(s["category"], 999))
|
|
112
|
+
|
|
113
|
+
projects: list[dict[str, Any]] = []
|
|
114
|
+
for proj in cv.projects:
|
|
115
|
+
override = profile.project_overrides.get(proj.name)
|
|
116
|
+
if override is None and not include_all and not (set(proj.tags) & focus):
|
|
117
|
+
continue
|
|
118
|
+
bullets = _resolve_bullets(override, proj.bullets, focus, deprio, max_b)
|
|
119
|
+
if not bullets:
|
|
120
|
+
continue
|
|
121
|
+
projects.append(
|
|
122
|
+
{
|
|
123
|
+
"name": proj.name,
|
|
124
|
+
"subtitle": proj.subtitle,
|
|
125
|
+
"year": proj.year,
|
|
126
|
+
"start": proj.start,
|
|
127
|
+
"end": proj.end,
|
|
128
|
+
"bullets": bullets,
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if profile.project_order is not None:
|
|
133
|
+
order = {name: i for i, name in enumerate(profile.project_order)}
|
|
134
|
+
projects.sort(key=lambda p: order.get(p["name"], 999))
|
|
135
|
+
|
|
136
|
+
education: list[dict[str, Any]] = [
|
|
137
|
+
{
|
|
138
|
+
"institution": e.institution,
|
|
139
|
+
"degree": e.degree,
|
|
140
|
+
"location": e.location,
|
|
141
|
+
"start": e.start,
|
|
142
|
+
"end": e.end,
|
|
143
|
+
}
|
|
144
|
+
for e in cv.education
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
"meta": meta,
|
|
149
|
+
"about": profile.about_override or _resolve(cv.about, variant),
|
|
150
|
+
"experience": experience,
|
|
151
|
+
"skills": skills,
|
|
152
|
+
"projects": projects,
|
|
153
|
+
"education": education,
|
|
154
|
+
"additional": cv.additional.model_dump(),
|
|
155
|
+
"sections": profile.sections,
|
|
156
|
+
"lang": profile.lang,
|
|
157
|
+
"labels": labels,
|
|
158
|
+
"font_family": profile.font_family,
|
|
159
|
+
"font_sizes": profile.font_sizes.model_dump(),
|
|
160
|
+
}
|