fix: follow-up review corrections, BIS rejection, and a gitignore miss

Second-pass review of this session's work by another agent. It corrected several
real mistakes of mine and finished F06 gaps I had left open. Reviewed, verified
and committed as-is.

THE MISS THAT MATTERS
`job_scout/state/applications.json` — the authoritative application registry
introduced by F02 — was never actually tracked. `job_scout/state/*` in
.gitignore silently excluded it, so my checkpoint commit afdef7e claimed to add
it and did not. Only `application_cohort.json`, the GENERATED projection, was
version-controlled; the source of truth and the migration history backup were
not. .gitignore now carries explicit negations for both.

MY ERRORS, CORRECTED
- "kastellaun" was in CH_LOCATION_KEYWORDS. Kastellaun is in Rhineland-Palatinate.
  I took it from a Bundeswehr reserve listing and filed it under Switzerland -
  precisely the class of bug F12 existed to fix.
- The F12 fix only widened the keyword list; SmartRecruiters still discarded the
  structured country, which was the concrete case the review named
  (city=Baar, country=CH). The adapter now preserves it, and a COUNTRY_NAMES
  mapping gives country precedence over a bare town name.
- F15 was applied to Taleo and Prospective only. Greenhouse still wrote
  `updated_at` into `posted`; it now uses `published_at` and carries provenance.
- My negation guard `\b(no|not|...)\b[^.;]{0,60}$` was too loose - an unrelated
  "not" within 60 characters could mask a real claim. Tightened to require the
  negation to govern the term.
- F18's retirement of the macro-count rule missed "Four to six compact skills
  lines" in the workflow; removed now.

F06 GAPS I LEFT OPEN, NOW CLOSED
Date-format validation on education/employment periods (the 2035-99 case named
in the review), evidence-structure validation, referential integrity on evidence
source files, and the missing-identity KeyError.

F10 TAKEN FURTHER, AND RIGHTLY
I demoted the US-GPA fields to *_orientation_only. They are now removed
entirely, with grade_output_rule saying what to do instead: use the German grade
and scale, and if a recipient requires a US GPA, use THEIR method and record it.
Keeping a number derived by a non-recognised method invites its use.

Also: the Capgemini omit-preference now sits in experience_capgemini.md itself,
alongside the instruction not to fill or explain the intentional gap.

BIS BASEL - REJECTED 2026-09-11
jr100429, applied 2026-07-10, no interview, no reason given. Recorded in the
registry with a dated event and in the decision log; the "prep interview brief
when screening lands" next action is cleared. This also settles the 2026-09-04
observation that the req had vanished from the BIS board - it was a real
rejection, not just a closed posting.

VERIFICATION I RAN MYSELF BEFORE COMMITTING
49/49 tests. Validator PASS with 0 advisory warnings. Package manifest and
registry views current. All 12 F12 location cases still correct, including
Kastellaun now rejecting. The F05 counter-proof still catches all 9 violations.
SBB, NVIDIA, RUAG and Kdo Cy resumes still scan clean. Diffs are net-additive
across every reference file - the deletions are replacements, not losses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qej5SBvhcbGXHwLqNWiskM
This commit is contained in:
dennisthiessenandClaude Opus 5 committed 2026-09-11 23:57:06 +02:00
1 parent 925a2d74a3
commit 34299bce54
25 files changed
+1427 -322

No files matched your search

@@ -66,7 +66,7 @@
| SW-3 | K8s + GitLab | **Lead bullet** — "Deployed and operated Python data applications on Kubernetes with GitLab CI/CD; drove infrastructure automation in agile DevOps team" | K8s + CI/CD ownership = core platform signal |
| SW-1 | AWS migration | "Migrated pipelines in his owned domains from legacy Teradata/Oracle processing onto Swisscom's AWS platform (Glue, Athena/Iceberg, Redshift, Airflow, CloudFormation)" | Scoped migration delivery plus AWS breadth |
| SW-2 | Component Owner | "Owned Fulfillment ETL pipelines (Oracle/Kafka → Teradata) — platform reliability, Data Governance compliance, 2nd/3rd-level support and on-call duty" | Platform SLA + on-call = reliability engineer signal |
| BS-1 | ML inference | "Containerized and orchestrated ML inference (Docker, K8s, Ansible) into a continuously operating 24/7 semiconductor production environment" | Production-grade containerization in an environment that cannot be paused. **Do not write *zero-downtime*** — canonical BS-1 evidences integration into continuous production, not a measured zero-downtime deployment property |
| BS-1 | ML inference | "Containerized and orchestrated ML inference (Docker, K8s, Ansible) into a continuously operating 24/7 semiconductor production environment" | Production-grade containerization in a continuously operating fab where a software defect could halt production. This is a failure consequence, not a deployment-window restriction. **Do not write *zero-downtime*** — canonical BS-1 evidences integration into continuous production, not a measured zero-downtime deployment property |
| BS-4 | ELK PoC | "Built an anomaly-detection **proof of concept** on ELK + Kafka (Docker) with Grafana, Prometheus and Loki monitoring, validating centralized logging and alerting for 24/7 semiconductor production" | PoC-scoped observability breadth — **the PoC framing is mandatory, not optional** |
| SW-5 | Security Champion | "2025/2026 team Security Champion; include only if the JD explicitly requires security or DevSecOps exposure" | Secondary team-role evidence, not ownership or certification |
| BS-6 | Linux/Ansible | "Administered and automated Linux servers carrying the ML platform, Docker hosts and backend services at Bosch; wrote Ansible extensions including a credential plugin with local caching" | **Infrastructure-as-Code and Linux operations evidence.** Added 2026-09-11 (review F07): BS-6 existed in `claims.json` and `experience_bosch.md` since 2026-08-27 but appeared in NO bundle, so the selection paths could not see it. Currency limit: employer-side Linux/Ansible ended Dec 2022; `PP-3` (personal Debian server) carries the current half and must be named as personal. GitOps stays forbidden — execution was push-based from Jenkins/GitLab, not agent-reconciled |
+3 -4
View File
@@ -50,12 +50,12 @@
"thesis_institution": "Tongji University, Shanghai",
"thesis_title": "Development of a Web-Based Remote Fault Diagnosis System",
"final_grade": "1.6 (German scale, 1.0 best / 4.0 lowest pass) -- user-confirmed 2026-08-26",
"final_grade_us_4pt_orientation_only": "3.4 -- modified Bavarian formula, US = 1 + 3*(4.0-G)/(4.0-1.0), which simplifies to 5.0 - G. Use ONLY when a form demands a 4.0-scale GPA; always state the German scale alongside it. Never present as a natively-awarded GPA. ORIENTATION AID, NOT A CANONICAL QUALIFICATION and NOT a recognised conversion: the modified Bavarian formula converts FOREIGN grades INTO the German system, so inverting it to produce a US GPA is not an accepted method, and the TUM source describes even its forward calculator as orientation only. The canonical fact is `final_grade` on the German scale. Supply this only when a specific recipient demands a 4.0-scale GPA; if that recipient publishes its own method, use THEIRS and record which was used. Demoted 2026-09-11, review finding F10.",
"thesis_grade": "1.0",
"thesis_relative_grade": "ECTS grade B; official certificate on file. B is formally the next 25% after the top 10%, i.e. the 10-35% band -- so the correct and safe phrasing is 'top 35%' (user-confirmed 2026-08-26). Do NOT write 'top 30%' or 'top 25%': B is a band, not a point, and narrowing it overstates the standing. The original 'top 35%' in this record was correct; a briefly-considered 30% was the error.",
"thesis_methods": "Verified against the thesis PDF 2026-08-02. Domain: vibration-based condition monitoring of CNC machine tools (piezoelectric accelerometers on spindle, tool rest, lathe body). Features: speed, load and the dimensionless waveform, peak, pulse, margin and kurtosis indices. Surveyed CBR, PSO, RBR and ANN; SELECTED a hybrid of rule-based reasoning (interpretable thresholds) plus a 7-10-3 feed-forward ANN (faultstates green/yellow/red) to handle noisy data that static rules cannot classify. Extensible plug-in architecture for swappable diagnosis methods and data collectors. Java/GWT client-server on MySQL. Evaluation measured throughput and latency, NOT model accuracy: the pipeline sustained ~500 samples/s against tri-axial vibration sensors delivering 72.9 kSPS, so the thesis concluded data reduction, filtering or batch scheduling is required for real-time use.",
"thesis_limits": "PSO was SURVEYED ONLY and NOT used in the implementation -- never claim it as an applied method. No real operational data: training data came from a cited thesis, test data was modified plus a random generator. No model-accuracy figures exist. Back-propagation retraining was listed as future work, not implemented. Frame as a methods prototype/framework, never as a validated production predictive-maintenance system.",
"transcripts_language": "English-language originals available for B.Eng. and M.Eng."
"transcripts_language": "English-language originals available for B.Eng. and M.Eng.",
"grade_output_rule": "Use the confirmed German original grade and its scale. Omit grades from industry resumes and letters unless concretely relevant. Do not convert with the inverse Bavarian formula. If a recipient requires a US GPA, use only its specified conversion method and record that method; otherwise ask the recipient how to enter a German grade."
},
{
"id": "EDU-BENG",
@@ -64,8 +64,7 @@
"start": "2009-10",
"end": "2012-10",
"final_grade": "2.4 (German scale, 1.0 best / 4.0 lowest pass) -- user-confirmed 2026-08-26",
"final_grade_us_4pt_orientation_only": "2.6 -- modified Bavarian formula (5.0 - G). Supply ONLY when a form requires a bachelor GPA; leave blank where optional. Always state the German scale alongside it, because the conversion makes German degrees read weaker to a US audience than they are: 2.4 is a solid 'gut'. Never present as a natively-awarded GPA. ORIENTATION AID, NOT A CANONICAL QUALIFICATION and NOT a recognised conversion: the modified Bavarian formula converts FOREIGN grades INTO the German system, so inverting it to produce a US GPA is not an accepted method, and the TUM source describes even its forward calculator as orientation only. The canonical fact is `final_grade` on the German scale. Supply this only when a specific recipient demands a 4.0-scale GPA; if that recipient publishes its own method, use THEIRS and record which was used. Demoted 2026-09-11, review finding F10.",
"grade_output_rule": "Neither degree grade goes on the resume or in a cover letter by default. Industry resumes omit grades; the M.Eng. thesis grade 1.0 is the only figure worth surfacing, and only where academic performance is genuinely valued. These fields exist for application-form GPA questions and credential checks. The German grades are the canonical facts; the *_orientation_only conversions are neither canonical nor a recognised method and must never be presented as an awarded GPA (F10, 2026-09-11)."
"grade_output_rule": "Use the confirmed German original grade and its scale. Omit grades from industry resumes and letters unless concretely relevant. Do not convert with the inverse Bavarian formula. If a recipient requires a US GPA, use only its specified conversion method and record that method; otherwise ask the recipient how to enter a German grade."
}
],
"employment": [
+221 -221
View File
@@ -1,12 +1,12 @@
{
"schema_version": 1,
"generated_note": "Generated by package_manifest.py (review finding F17). A PDF in a folder is not evidence that the package is reproducible. This file records what would actually happen on a rebuild. It is descriptive: nothing here authorises rewriting a submitted document.",
"schema_version": 2,
"generated_note": "Generated by package_manifest.py (review finding F17). A PDF in a folder is not evidence that the package is reproducible. Source and class hashes detect edits; optional pdf_artifacts are observations, not verified source-to-PDF mappings. Absent PDFs are allowed; present PDFs must match their recorded hash. Nothing here authorises rewriting a submitted document.",
"packages": {
"AkerBP_Data_Product_Architect": {
"sources": [
"e2e_akerbp_data_product_architect_cover_letter.tex",
"e2e_akerbp_data_product_architect_resume.tex"
],
"sources": {
"e2e_akerbp_data_product_architect_cover_letter.tex": "922759ac4bc7a52c",
"e2e_akerbp_data_product_architect_resume.tex": "d0a0f9942a882567"
},
"class_files": {
"resume.cls": "b3a032a850cd0d6d"
},
@@ -14,71 +14,71 @@
"article",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_akerbp_data_product_architect_cover_letter.pdf",
"e2e_akerbp_data_product_architect_resume.pdf"
],
"uses_today": [],
"problems": [],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "76c7dacfb00b5df3",
"Dennis_Thiessen_Resume.pdf": "0023acf689a8b4e7",
"e2e_akerbp_data_product_architect_cover_letter.pdf": "76c7dacfb00b5df3",
"e2e_akerbp_data_product_architect_resume.pdf": "0023acf689a8b4e7"
},
"reuse_classification": "recorded"
},
"Apple_Data_Engineer": {
"sources": [
"e2e_apple_data_engineer_cover_letter.tex",
"e2e_apple_data_engineer_resume.tex"
],
"sources": {
"e2e_apple_data_engineer_cover_letter.tex": "34abad48e3a82634",
"e2e_apple_data_engineer_resume.tex": "c3594ddcb32131da"
},
"class_files": {
"resume.cls": "b26f882a42d30d90"
"resume.cls": "721ed8d5e80fcc54"
},
"requires_class": [
"moderncv",
"resume"
],
"pdfs": [
"e2e_apple_data_engineer_cover_letter.pdf",
"e2e_apple_data_engineer_resume.pdf"
],
"uses_today": [
"e2e_apple_data_engineer_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_apple_data_engineer_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"e2e_apple_data_engineer_cover_letter.pdf": "06ee82291bbc9a4c",
"e2e_apple_data_engineer_resume.pdf": "37aeb66d208346f5"
},
"reuse_classification": "recorded"
},
"BIS": {
"sources": [
"e2e_bis_senior_data_analytics_ai_cover_letter.tex",
"e2e_bis_senior_data_analytics_ai_resume.tex"
],
"sources": {
"e2e_bis_senior_data_analytics_ai_cover_letter.tex": "4f037d7fcfd1ccfc",
"e2e_bis_senior_data_analytics_ai_resume.tex": "4ecc6a98ab82ea2b"
},
"class_files": {
"resume.cls": "b26f882a42d30d90"
"resume.cls": "721ed8d5e80fcc54"
},
"requires_class": [
"moderncv",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_bis_senior_data_analytics_ai_cover_letter.pdf",
"e2e_bis_senior_data_analytics_ai_resume.pdf"
],
"uses_today": [
"e2e_bis_senior_data_analytics_ai_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_bis_senior_data_analytics_ai_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "c584d131e37899fa",
"Dennis_Thiessen_Resume.pdf": "5b554ae2e658d960",
"e2e_bis_senior_data_analytics_ai_cover_letter.pdf": "c584d131e37899fa",
"e2e_bis_senior_data_analytics_ai_resume.pdf": "5b554ae2e658d960"
},
"reuse_classification": "recorded"
},
"CitadelSecurities_PlatformEngineer": {
"sources": [
"e2e_citadel_securities_platform_engineer_cover_letter.tex",
"e2e_citadel_securities_platform_engineer_resume.tex"
],
"sources": {
"e2e_citadel_securities_platform_engineer_cover_letter.tex": "4f2bf629635d3413",
"e2e_citadel_securities_platform_engineer_resume.tex": "5f2ac36737162b29"
},
"class_files": {
"resume.cls": "b3a032a850cd0d6d"
},
@@ -86,49 +86,49 @@
"article",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_citadel_securities_platform_engineer_cover_letter.pdf",
"e2e_citadel_securities_platform_engineer_resume.pdf"
],
"uses_today": [],
"problems": [],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "e95631c56fd02a71",
"Dennis_Thiessen_Resume.pdf": "cb7a13b2215796f2",
"e2e_citadel_securities_platform_engineer_cover_letter.pdf": "e95631c56fd02a71",
"e2e_citadel_securities_platform_engineer_resume.pdf": "cb7a13b2215796f2"
},
"reuse_classification": "recorded"
},
"Equinor_AI_Manager": {
"sources": [
"e2e_equinor_ai_manager_cover_letter.tex",
"e2e_equinor_ai_manager_resume.tex"
],
"sources": {
"e2e_equinor_ai_manager_cover_letter.tex": "390a7ea37836a93d",
"e2e_equinor_ai_manager_resume.tex": "f57087d53a777328"
},
"class_files": {
"resume.cls": "b26f882a42d30d90"
"resume.cls": "721ed8d5e80fcc54"
},
"requires_class": [
"moderncv",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_equinor_ai_manager_cover_letter.pdf",
"e2e_equinor_ai_manager_resume.pdf"
],
"uses_today": [
"e2e_equinor_ai_manager_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_equinor_ai_manager_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "224303daba825066",
"Dennis_Thiessen_Resume.pdf": "634e207667086dd7",
"e2e_equinor_ai_manager_cover_letter.pdf": "224303daba825066",
"e2e_equinor_ai_manager_resume.pdf": "634e207667086dd7"
},
"reuse_classification": "recorded"
},
"Google_Business_Home": {
"sources": [
"e2e_google_software_engineer_iii_business_home_cover_letter.tex",
"e2e_google_software_engineer_iii_business_home_cover_letter_v2.tex",
"e2e_google_software_engineer_iii_business_home_resume.tex",
"e2e_google_software_engineer_iii_business_home_resume_v2.tex"
],
"sources": {
"e2e_google_software_engineer_iii_business_home_cover_letter.tex": "c68e33850a6baf42",
"e2e_google_software_engineer_iii_business_home_cover_letter_v2.tex": "2fe40e60a2defb12",
"e2e_google_software_engineer_iii_business_home_resume.tex": "f47788e596d87529",
"e2e_google_software_engineer_iii_business_home_resume_v2.tex": "d895021d7b5850cb"
},
"class_files": {
"resume.cls": "b3a032a850cd0d6d"
},
@@ -136,124 +136,124 @@
"article",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_google_software_engineer_iii_business_home_cover_letter.pdf",
"e2e_google_software_engineer_iii_business_home_cover_letter_v2.pdf",
"e2e_google_software_engineer_iii_business_home_resume.pdf",
"e2e_google_software_engineer_iii_business_home_resume_v2.pdf"
],
"uses_today": [],
"problems": [],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "57c111039dc2c4ee",
"Dennis_Thiessen_Resume.pdf": "e9fe51e4661fc36c",
"e2e_google_software_engineer_iii_business_home_cover_letter.pdf": "e3643f9d623e404d",
"e2e_google_software_engineer_iii_business_home_cover_letter_v2.pdf": "57c111039dc2c4ee",
"e2e_google_software_engineer_iii_business_home_resume.pdf": "c4cb40153f7af748",
"e2e_google_software_engineer_iii_business_home_resume_v2.pdf": "e9fe51e4661fc36c"
},
"reuse_classification": "recorded"
},
"Google_Senior_Data_Engineer": {
"sources": [
"e2e_google_senior_data_engineer_cover_letter.tex",
"e2e_google_senior_data_engineer_resume.tex"
],
"sources": {
"e2e_google_senior_data_engineer_cover_letter.tex": "966fd9ff789fb4a4",
"e2e_google_senior_data_engineer_resume.tex": "1e3e56ab426172ab"
},
"class_files": {
"resume.cls": "b26f882a42d30d90"
"resume.cls": "721ed8d5e80fcc54"
},
"requires_class": [
"moderncv",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_google_senior_data_engineer_cover_letter.pdf",
"e2e_google_senior_data_engineer_resume.pdf"
],
"uses_today": [
"e2e_google_senior_data_engineer_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_google_senior_data_engineer_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "c826c22521359372",
"Dennis_Thiessen_Resume.pdf": "25121fee8915acd3",
"e2e_google_senior_data_engineer_cover_letter.pdf": "c826c22521359372",
"e2e_google_senior_data_engineer_resume.pdf": "25121fee8915acd3"
},
"reuse_classification": "recorded"
},
"Infineon": {
"sources": [
"e2e_infineon_doctoral_cover_letter.tex",
"e2e_infineon_doctoral_resume.tex"
],
"sources": {
"e2e_infineon_doctoral_cover_letter.tex": "4149a4a2ed2f619f",
"e2e_infineon_doctoral_resume.tex": "090946510d744691"
},
"class_files": {
"resume.cls": "b26f882a42d30d90"
"resume.cls": "721ed8d5e80fcc54"
},
"requires_class": [
"moderncv",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_infineon_doctoral_cover_letter.pdf",
"e2e_infineon_doctoral_resume.pdf"
],
"uses_today": [
"e2e_infineon_doctoral_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_infineon_doctoral_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "5d785ca3c4b1a903",
"Dennis_Thiessen_Resume.pdf": "a445f2351982b735",
"e2e_infineon_doctoral_cover_letter.pdf": "5d785ca3c4b1a903",
"e2e_infineon_doctoral_resume.pdf": "a445f2351982b735"
},
"reuse_classification": "recorded"
},
"Infineon_AI_Engineer": {
"sources": [
"e2e_infineon_ai_engineer_cover_letter.tex",
"e2e_infineon_ai_engineer_resume.tex"
],
"sources": {
"e2e_infineon_ai_engineer_cover_letter.tex": "a8cccec6b8d685b9",
"e2e_infineon_ai_engineer_resume.tex": "b7957e35ee342bca"
},
"class_files": {
"resume.cls": "b26f882a42d30d90"
"resume.cls": "721ed8d5e80fcc54"
},
"requires_class": [
"moderncv",
"resume"
],
"pdfs": [
"e2e_infineon_ai_engineer_resume.pdf"
],
"uses_today": [
"e2e_infineon_ai_engineer_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_infineon_ai_engineer_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"e2e_infineon_ai_engineer_resume.pdf": "810ec7007a8bfe8e"
},
"reuse_classification": "recorded"
},
"Isovalent_DataEngineer": {
"sources": [
"e2e_isovalent_data_engineer_cover_letter.tex",
"e2e_isovalent_data_engineer_resume.tex"
],
"sources": {
"e2e_isovalent_data_engineer_cover_letter.tex": "33c6a2943f288893",
"e2e_isovalent_data_engineer_resume.tex": "b8edf89223ef8fb2"
},
"class_files": {
"resume.cls": "b26f882a42d30d90"
"resume.cls": "721ed8d5e80fcc54"
},
"requires_class": [
"moderncv",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_isovalent_data_engineer_cover_letter.pdf",
"e2e_isovalent_data_engineer_resume.pdf"
],
"uses_today": [
"e2e_isovalent_data_engineer_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_isovalent_data_engineer_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "546364b3102d00b7",
"Dennis_Thiessen_Resume.pdf": "f35745cc987473fc",
"e2e_isovalent_data_engineer_cover_letter.pdf": "546364b3102d00b7",
"e2e_isovalent_data_engineer_resume.pdf": "f35745cc987473fc"
},
"reuse_classification": "recorded"
},
"KdoCy_DevOps_Data_Platform": {
"sources": [
"e2e_kdocy_devops_data_platform_cover_letter.tex",
"e2e_kdocy_devops_data_platform_resume.tex"
],
"sources": {
"e2e_kdocy_devops_data_platform_cover_letter.tex": "29d114255a1310cc",
"e2e_kdocy_devops_data_platform_resume.tex": "de3fcb42d1c5c478"
},
"class_files": {
"resume.cls": "b3a032a850cd0d6d"
},
@@ -261,151 +261,151 @@
"article",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Lebenslauf.pdf",
"Dennis_Thiessen_Motivationsschreiben.pdf",
"e2e_kdocy_devops_data_platform_cover_letter.pdf",
"e2e_kdocy_devops_data_platform_resume.pdf"
],
"uses_today": [
"e2e_kdocy_devops_data_platform_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_kdocy_devops_data_platform_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"Dennis_Thiessen_Lebenslauf.pdf": "ec9fbeb6864a43da",
"Dennis_Thiessen_Motivationsschreiben.pdf": "53be036b3ce12148",
"e2e_kdocy_devops_data_platform_cover_letter.pdf": "55d7aa1fd97b2eb6",
"e2e_kdocy_devops_data_platform_resume.pdf": "4929981906919523"
},
"reuse_classification": "recorded"
},
"Kraken_AI_Infrastructure": {
"sources": [
"e2e_kraken_ai_infra_cover_letter.tex",
"e2e_kraken_ai_infra_resume.tex"
],
"sources": {
"e2e_kraken_ai_infra_cover_letter.tex": "32eb97aedefb1702",
"e2e_kraken_ai_infra_resume.tex": "14350c33e1dbc18e"
},
"class_files": {
"resume.cls": "b26f882a42d30d90"
"resume.cls": "721ed8d5e80fcc54"
},
"requires_class": [
"moderncv",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"Senior Software Engineer – AI Infrastructure @ Kraken.pdf",
"e2e_kraken_ai_infra_cover_letter.pdf",
"e2e_kraken_ai_infra_resume.pdf"
],
"uses_today": [
"e2e_kraken_ai_infra_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_kraken_ai_infra_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "10d0deb132c3ecd8",
"Dennis_Thiessen_Resume.pdf": "0ff9a52ceb6521a3",
"e2e_kraken_ai_infra_cover_letter.pdf": "10d0deb132c3ecd8",
"e2e_kraken_ai_infra_resume.pdf": "0ff9a52ceb6521a3",
"Senior Software Engineer – AI Infrastructure @ Kraken.pdf": "716232dd1d83cbfc"
},
"reuse_classification": "recorded"
},
"Kraken_SRE_AI_Agents": {
"sources": [
"e2e_kraken_sre_ai_agents_cover_letter.tex",
"e2e_kraken_sre_ai_agents_resume.tex"
],
"sources": {
"e2e_kraken_sre_ai_agents_cover_letter.tex": "bf215661bc0e6936",
"e2e_kraken_sre_ai_agents_resume.tex": "b8d1a3cfae03186a"
},
"class_files": {
"resume.cls": "b26f882a42d30d90"
"resume.cls": "721ed8d5e80fcc54"
},
"requires_class": [
"moderncv",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_kraken_sre_ai_agents_cover_letter.pdf",
"e2e_kraken_sre_ai_agents_resume.pdf"
],
"uses_today": [
"e2e_kraken_sre_ai_agents_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_kraken_sre_ai_agents_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "a08cff46ef5f4653",
"Dennis_Thiessen_Resume.pdf": "9c04ea9735e8611a",
"e2e_kraken_sre_ai_agents_cover_letter.pdf": "a08cff46ef5f4653",
"e2e_kraken_sre_ai_agents_resume.pdf": "9c04ea9735e8611a"
},
"reuse_classification": "recorded"
},
"Microsoft_ISE_Senior_SWE": {
"sources": [
"e2e_microsoft_ise_cover_letter.tex",
"e2e_microsoft_ise_resume.tex"
],
"sources": {
"e2e_microsoft_ise_cover_letter.tex": "72ea729b7bd5c8da",
"e2e_microsoft_ise_resume.tex": "f61845e8087b5696"
},
"class_files": {
"resume.cls": "b26f882a42d30d90"
"resume.cls": "721ed8d5e80fcc54"
},
"requires_class": [
"moderncv",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_microsoft_ise_cover_letter.pdf",
"e2e_microsoft_ise_resume.pdf"
],
"uses_today": [
"e2e_microsoft_ise_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_microsoft_ise_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "7f6df9105f06f572",
"Dennis_Thiessen_Resume.pdf": "8c4f308234298916",
"e2e_microsoft_ise_cover_letter.pdf": "7f6df9105f06f572",
"e2e_microsoft_ise_resume.pdf": "8c4f308234298916"
},
"reuse_classification": "recorded"
},
"Microsoft_Principal_FDE_SWE": {
"sources": [
"e2e_microsoft_principal_fde_swe_cover_letter.tex",
"e2e_microsoft_principal_fde_swe_resume.tex"
],
"sources": {
"e2e_microsoft_principal_fde_swe_cover_letter.tex": "8095827a769ece44",
"e2e_microsoft_principal_fde_swe_resume.tex": "5be341c9dcccca28"
},
"class_files": {
"resume.cls": "b26f882a42d30d90"
"resume.cls": "721ed8d5e80fcc54"
},
"requires_class": [
"moderncv",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_microsoft_principal_fde_swe_cover_letter.pdf",
"e2e_microsoft_principal_fde_swe_resume.pdf"
],
"uses_today": [
"e2e_microsoft_principal_fde_swe_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_microsoft_principal_fde_swe_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "bf00c589a7742547",
"Dennis_Thiessen_Resume.pdf": "9a95e3452e2e3539",
"e2e_microsoft_principal_fde_swe_cover_letter.pdf": "bf00c589a7742547",
"e2e_microsoft_principal_fde_swe_resume.pdf": "9a95e3452e2e3539"
},
"reuse_classification": "recorded"
},
"NATO_AI_ENGINEER": {
"sources": [
"e2e_nato_ai_engineer_cover_letter.tex",
"e2e_nato_ai_engineer_resume.tex"
],
"sources": {
"e2e_nato_ai_engineer_cover_letter.tex": "863c590ea8a987c1",
"e2e_nato_ai_engineer_resume.tex": "e3b80997712dd9d7"
},
"class_files": {},
"requires_class": [
"moderncv",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_nato_ai_engineer_resume.pdf"
],
"uses_today": [],
"problems": [
"source distribution incomplete: requires resume.cls, which is not in the folder. A build here only succeeds if a copy happens to exist elsewhere on the machine. Do NOT repair by copying the newest class in - the repo carries several class variants and older macros differ."
],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "0239a9ca995cb825",
"Dennis_Thiessen_Resume.pdf": "3d96a1e30b0adb64",
"e2e_nato_ai_engineer_resume.pdf": "428e242a2ca7b453"
},
"reuse_classification": "recorded"
},
"NVIDIA_SrDevOps_Zurich": {
"sources": [
"e2e_nvidia_sr_devops_cover_letter.tex",
"e2e_nvidia_sr_devops_resume.tex"
],
"sources": {
"e2e_nvidia_sr_devops_cover_letter.tex": "2252b98f7a7e0ca0",
"e2e_nvidia_sr_devops_resume.tex": "69787364df7d6f02"
},
"class_files": {
"resume.cls": "b3a032a850cd0d6d"
},
@@ -413,51 +413,51 @@
"article",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_nvidia_sr_devops_cover_letter.pdf",
"e2e_nvidia_sr_devops_resume.pdf"
],
"uses_today": [
"e2e_nvidia_sr_devops_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_nvidia_sr_devops_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "275a8a10fb9bb9eb",
"Dennis_Thiessen_Resume.pdf": "03ea1f4fe89d6fbf",
"e2e_nvidia_sr_devops_cover_letter.pdf": "275a8a10fb9bb9eb",
"e2e_nvidia_sr_devops_resume.pdf": "03ea1f4fe89d6fbf"
},
"reuse_classification": "recorded"
},
"QuantCo_Cloud_Engineer": {
"sources": [
"e2e_quantco_cloud_engineer_cover_letter.tex",
"e2e_quantco_cloud_engineer_resume.tex"
],
"sources": {
"e2e_quantco_cloud_engineer_cover_letter.tex": "f8ef588da7fe1232",
"e2e_quantco_cloud_engineer_resume.tex": "a8ebc1b56f623949"
},
"class_files": {
"resume.cls": "b26f882a42d30d90"
"resume.cls": "721ed8d5e80fcc54"
},
"requires_class": [
"moderncv",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_quantco_cloud_engineer_cover_letter.pdf",
"e2e_quantco_cloud_engineer_resume.pdf"
],
"uses_today": [
"e2e_quantco_cloud_engineer_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_quantco_cloud_engineer_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "484a41d552102b4f",
"Dennis_Thiessen_Resume.pdf": "564338361852073f",
"e2e_quantco_cloud_engineer_cover_letter.pdf": "484a41d552102b4f",
"e2e_quantco_cloud_engineer_resume.pdf": "564338361852073f"
},
"reuse_classification": "recorded"
},
"RUAG_AI_Engineer_C5I": {
"sources": [
"e2e_ruag_ai_engineer_c5i_cover_letter.tex",
"e2e_ruag_ai_engineer_c5i_resume.tex"
],
"sources": {
"e2e_ruag_ai_engineer_c5i_cover_letter.tex": "2b3d966dc0590b08",
"e2e_ruag_ai_engineer_c5i_resume.tex": "2cf799344c3d91c6"
},
"class_files": {
"resume.cls": "b52a6f7123ae9067"
},
@@ -465,62 +465,62 @@
"article",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Lebenslauf.pdf",
"Dennis_Thiessen_Motivationsschreiben.pdf",
"e2e_ruag_ai_engineer_c5i_cover_letter.pdf",
"e2e_ruag_ai_engineer_c5i_resume.pdf"
],
"uses_today": [
"e2e_ruag_ai_engineer_c5i_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_ruag_ai_engineer_c5i_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"Dennis_Thiessen_Lebenslauf.pdf": "f29ab6445b4e7ff5",
"Dennis_Thiessen_Motivationsschreiben.pdf": "336d54ed07f205c7",
"e2e_ruag_ai_engineer_c5i_cover_letter.pdf": "336d54ed07f205c7",
"e2e_ruag_ai_engineer_c5i_resume.pdf": "f29ab6445b4e7ff5"
},
"reuse_classification": "recorded"
},
"SBB_DataEngineer_AssetMgmt": {
"sources": [
"e2e_sbb_data_engineer_asset_mgmt_resume.tex"
],
"sources": {
"e2e_sbb_data_engineer_asset_mgmt_resume.tex": "cd4e8a18fa0a235a"
},
"class_files": {
"resume.cls": "b3a032a850cd0d6d"
},
"requires_class": [
"resume"
],
"pdfs": [
"e2e_sbb_data_engineer_asset_mgmt_resume.pdf"
],
"uses_today": [],
"problems": [],
"pdf_artifacts": {
"e2e_sbb_data_engineer_asset_mgmt_resume.pdf": "669fedaa340a89c7"
},
"reuse_classification": "recorded"
},
"Snowflake_Observe_Enterprise": {
"sources": [
"e2e_snowflake_observe_enterprise_cover_letter.tex",
"e2e_snowflake_observe_enterprise_resume.tex"
],
"sources": {
"e2e_snowflake_observe_enterprise_cover_letter.tex": "f0d01764e943776f",
"e2e_snowflake_observe_enterprise_resume.tex": "cde2a2326fa2913e"
},
"class_files": {
"cv.cls": "a623d286e808b27e",
"resume.cls": "b26f882a42d30d90"
"cv.cls": "30bf0ca5b3d0b718",
"resume.cls": "721ed8d5e80fcc54"
},
"requires_class": [
"moderncv",
"resume"
],
"pdfs": [
"Dennis_Thiessen_Cover_Letter.pdf",
"Dennis_Thiessen_Resume.pdf",
"e2e_snowflake_observe_enterprise_cover_letter.pdf",
"e2e_snowflake_observe_enterprise_resume.pdf"
],
"uses_today": [
"e2e_snowflake_observe_enterprise_cover_letter.tex"
],
"problems": [
"rebuild is date-dependent: e2e_snowflake_observe_enterprise_cover_letter.tex use \\today, so recompiling a historical package silently restamps it with the current date. New packages must carry the explicit application date instead."
],
"pdf_artifacts": {
"Dennis_Thiessen_Cover_Letter.pdf": "555ece186371a9ef",
"Dennis_Thiessen_Resume.pdf": "122dab5f5e5ec70d",
"e2e_snowflake_observe_enterprise_cover_letter.pdf": "555ece186371a9ef",
"e2e_snowflake_observe_enterprise_resume.pdf": "122dab5f5e5ec70d"
},
"reuse_classification": "recorded"
}
}
@@ -3,7 +3,7 @@
### Cross-Position Section
**Career arc framing:** Capgemini was Dennis's first professional role after the Bundeswehr — a 6-month consulting engagement in the Technology Services APPS division, working on test automation for a transport logistics client. Despite its brevity, the Zeugnis rates performance as "sehr gut" (vollsten Zufriedenheit — top tier), and the employer deeply regrets departure. This role anchors the test automation thread that runs through Generali and Vizrt. Typically omitted from 2-page resumes; included in full CV for timeline completeness.
**Career arc framing:** Capgemini was Dennis's first professional role after the Bundeswehr — a 6-month consulting engagement in the Technology Services APPS division, working on test automation for a transport logistics client. Despite its brevity, the Zeugnis rates performance as "sehr gut" (vollsten Zufriedenheit — top tier), and the employer deeply regrets departure. This role anchors the test automation thread that runs through Generali and Vizrt. Historical record only. Standing user preference: omit from all resumes, CVs and cover letters; do not fill or explain the intentional gap.
**Resume recommendation:** Omit from 2-page resume unless targeting roles where test automation breadth from earliest career adds value. Include on full CV (5-page) under early career section.
@@ -38,5 +38,5 @@
**Resume inclusion guidance:**
- 2-page resume: **Omit entirely**
- 5-page CV: Include as single condensed bullet under "Early Career" or within timeline continuity section
- Any CV, including academic: **Omit entirely**, per standing user preference.
- Cover letter: Not relevant to mention
@@ -57,7 +57,9 @@ def update_application(data: dict, args: argparse.Namespace) -> None:
row = matches[0]
if row.get("application_date") and args.date < row["application_date"]:
raise ValueError("event date precedes application date")
outcome_dates = [event["date"] for event in row.get("events", []) if event.get("outcome") and event.get("date")]
outcome_dates = [event["date"] for event in row.get("events", [])
if (event.get("outcome") or event.get("type") in records.OUTCOMES) and event.get("date")]
outcome_dates.extend(row[key] for key in ("withdrawn", "rejection_date") if row.get(key))
if args.outcome and outcome_dates and args.date < max(outcome_dates):
raise ValueError("older outcome cannot replace current state; record it as a historical note")
event = {"type": args.event or args.outcome or "note", "date": args.date}
+49 -23
View File
@@ -6,9 +6,9 @@ A package folder that contains a PDF looks finished. It is not necessarily repro
one happens to be installed elsewhere, and its submitted PDF differs in content from the
e2e source's PDF. The existing PDF hid both problems.
This records, per package: which sources exist, the hash of the class each one would compile
against, whether a PDF is present, and whether the submission-named PDF still matches the
e2e one. It does NOT rewrite anything - historical packages are evidence.
Records source/class content hashes. Optional PDF snapshots detect changed local artifacts;
missing PDFs are allowed because generated PDFs are deliberately not tracked in Git.
Filename association is not proof that a PDF was built from the current source.
python resume_builder/helpers/package_manifest.py # write the manifest
python resume_builder/helpers/package_manifest.py --check # fail if it is stale
@@ -34,17 +34,19 @@ CTAN_CLASSES = {"article", "report", "book", "letter", "moderncv", "scrartcl", "
def digest(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()[:16]
# Git/Windows may change line endings without changing the TeX source.
data = path.read_text(encoding="utf-8").encode("utf-8") if path.suffix in {".tex", ".cls"} else path.read_bytes()
return hashlib.sha256(data).hexdigest()[:16]
def describe(folder: Path) -> dict:
sources = sorted(p.name for p in folder.glob("*.tex"))
def describe(folder: Path, record_pdfs: bool = False) -> dict:
sources = {p.name: digest(p) for p in sorted(folder.glob("*.tex"))}
classes = {p.name: digest(p) for p in folder.glob("*.cls")}
pdfs = sorted(p.name for p in folder.glob("*.pdf"))
needs = set()
uses_today = []
for name in sources:
text = (folder / name).read_text(encoding="utf-8", errors="replace")
text = re.sub(r"(?<!\\)%.*$", "", text, flags=re.M)
match = re.search(r"\\documentclass(?:\[[^\]]*\])?\{([^}]+)\}", text)
if match:
needs.add(match.group(1))
@@ -68,20 +70,19 @@ def describe(folder: Path) -> dict:
f"rebuild is date-dependent: {', '.join(uses_today)} use \\today, so recompiling a "
f"historical package silently restamps it with the current date. New packages must "
f"carry the explicit application date instead.")
if sources and not pdfs:
problems.append("no PDF present: nothing reviewable without a build")
return {
entry = {
"sources": sources,
"class_files": classes,
"requires_class": sorted(needs),
"pdfs": pdfs,
"uses_today": uses_today,
"problems": problems,
}
if record_pdfs:
entry["pdf_artifacts"] = {p.name: digest(p) for p in sorted(folder.glob("*.pdf"))}
return entry
def build() -> dict:
def build(record_pdfs: bool = False) -> dict:
register = json.loads(REGISTER.read_text(encoding="utf-8"))
classified = set()
for key in ("unsafe_do_not_reuse", "historical_revalidate", "paused_no_package"):
@@ -96,7 +97,7 @@ def build() -> dict:
# submission and has nothing to classify for reuse.
if not any(folder.glob("*resume*.tex")) and not any(folder.glob("*cover*letter*.tex")):
continue
entry = describe(folder)
entry = describe(folder, record_pdfs)
entry["reuse_classification"] = (
"recorded" if f"output/{folder.name}" in classified else "UNCLASSIFIED")
if f"output/{folder.name}" not in classified:
@@ -107,32 +108,57 @@ def build() -> dict:
packages[folder.name] = entry
return {
"schema_version": 1,
"schema_version": 2,
"generated_note": (
"Generated by package_manifest.py (review finding F17). A PDF in a folder is not "
"evidence that the package is reproducible. This file records what would actually "
"happen on a rebuild. It is descriptive: nothing here authorises rewriting a "
"evidence that the package is reproducible. Source and class hashes detect edits; "
"optional pdf_artifacts are observations, not verified source-to-PDF mappings. "
"Absent PDFs are allowed; present PDFs must match their recorded hash. "
"Nothing here authorises rewriting a "
"submitted document."),
"packages": packages,
}
def check_manifest(recorded: dict, current: dict, output: Path = OUTPUT) -> list[str]:
# Compare the portable source inventory separately from optional local build products.
baseline = json.loads(json.dumps(recorded))
errors = []
for name, entry in baseline.get("packages", {}).items():
for filename, expected in entry.pop("pdf_artifacts", {}).items():
path = output / name / filename
if path.exists() and digest(path) != expected:
errors.append(f"changed PDF artifact: {name}/{filename}; inspect before recording a new baseline")
if baseline != current:
errors.append("source manifest is stale; inspect changes, then run package_manifest.py")
return errors
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--check", action="store_true", help="fail if the manifest is stale")
parser.add_argument("--record-pdfs", action="store_true", help="explicitly snapshot current local PDF hashes (not proof of source association)")
args = parser.parse_args()
manifest = build()
text = json.dumps(manifest, indent=2, ensure_ascii=False) + "\n"
if args.check and args.record_pdfs:
parser.error("--check cannot refresh PDF baselines")
recorded = json.loads(MANIFEST.read_text(encoding="utf-8")) if MANIFEST.exists() else {}
manifest = build(record_pdfs=args.record_pdfs)
if args.check:
current = MANIFEST.read_text(encoding="utf-8") if MANIFEST.exists() else ""
if current != text:
print("ERROR: package manifest is stale; re-run package_manifest.py")
errors = check_manifest(recorded, manifest)
if errors:
for error in errors:
print("ERROR: " + error)
return 1
print("Package manifest is current.")
return 0
MANIFEST.write_text(text, encoding="utf-8")
if not args.record_pdfs:
for name, entry in manifest["packages"].items():
old = recorded.get("packages", {}).get(name, {})
if "pdf_artifacts" in old:
entry["pdf_artifacts"] = old["pdf_artifacts"]
MANIFEST.write_text(json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
flagged = {name: entry for name, entry in manifest["packages"].items() if entry["problems"]}
print(f"{len(manifest['packages'])} packages described, {len(flagged)} with findings\n")
for name, entry in flagged.items():
+12 -12
View File
@@ -8,38 +8,38 @@ The review reported ~50-55% for four packages. This reproduces that measurement
compiled PDF so a change can be checked rather than asserted. The percentage is a diagnostic,
never a target: no pixel goal, no forced page-two filling.
"""
import io
import re
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
PDFTOTEXT = Path(r"C:\Users\Dennis\AppData\Local\Programs\MiKTeX\miktex\bin\x64\pdftotext.exe")
HEADINGS = ("professional experience", "berufserfahrung", "experience")
def measure(pdf: Path) -> dict:
executable = shutil.which("pdftotext")
if not executable:
raise RuntimeError("pdftotext is required on PATH (Poppler or MiKTeX)")
with tempfile.TemporaryDirectory() as work:
out = Path(work) / "bbox.xml"
subprocess.run([str(PDFTOTEXT), "-bbox", "-f", "1", "-l", "1", str(pdf), str(out)],
subprocess.run([executable, "-bbox", "-f", "1", "-l", "1", str(pdf), str(out)],
capture_output=True, check=True)
xml = out.read_text(encoding="utf-8", errors="replace")
page = re.search(r'<page width="([\d.]+)" height="([\d.]+)"', xml)
if page is None:
raise ValueError("No page geometry found in pdftotext output")
height = float(page.group(2))
words = [(float(m.group(2)), m.group(5).lower())
for m in re.finditer(
r'<word xMin="([\d.]+)" yMin="([\d.]+)" xMax="([\d.]+)" yMax="([\d.]+)">([^<]*)</word>',
xml)]
first_y = None
for index, (y, text) in enumerate(words):
if text in ("professional", "berufserfahrung", "experience"):
following = " ".join(w for _, w in words[index:index + 2])
if any(h.startswith(following.split()[0]) for h in HEADINGS):
first_y = y
break
lines = len({round(y, 1) for y, _ in words})
rows = {}
for y, text in words:
rows.setdefault(round(y, 1), []).append(text)
first_y = next((y for y, row in sorted(rows.items()) if " ".join(row).strip() in HEADINGS), None)
lines = len(rows)
return {
"height": height,
"experience_y": first_y,
@@ -51,6 +51,16 @@ def canonical_checks(claims: dict, history: dict) -> tuple[list[str], list[str]]
if duplicates:
errors.append(f"duplicate {collection} ids: {', '.join(duplicates)}")
for collection in ("education", "employment"):
for item in claims.get(collection, []):
for period in [item, *item.get("title_history", [])]:
for field in ("start", "end"):
value = period.get(field)
if value == "present" and field == "end":
continue
if value is not None and (not isinstance(value, str) or not re.fullmatch(r"\d{4}-(?:0[1-9]|1[0-2])", value)):
errors.append(f"{item.get('id')}: invalid {field} date {value!r}; expected YYYY-MM")
skills = claims.get("skills", [])
skill_names = [item.get("name") for item in skills]
duplicates = sorted({item for item in skill_names if skill_names.count(item) > 1})
@@ -95,15 +105,26 @@ def canonical_checks(claims: dict, history: dict) -> tuple[list[str], list[str]]
missing_block = [c.get("id") for c in claim_list if not c.get("evidence")]
if missing_block:
errors.append("claims without an evidence block: " + ", ".join(map(str, missing_block)))
for claim in claim_list:
evidence = claim.get("evidence")
if not isinstance(evidence, dict):
errors.append(f"{claim.get('id')}: evidence must be an object")
continue
for field in ("source", "ownership"):
if not isinstance(evidence.get(field), str) or not evidence[field].strip():
errors.append(f"{claim.get('id')}: evidence.{field} must be a nonempty string (or 'unrecorded')")
source = evidence.get("source", "")
if isinstance(source, str) and re.fullmatch(r"[\w./-]+\.(?:md|pdf|tex|json|txt)", source) and not (ROOT / source).is_file():
errors.append(f"{claim.get('id')}: evidence source file does not exist: {source}")
undated = [c.get("id") for c in claim_list
if (c.get("evidence") or {}).get("last_verified") == "unrecorded"]
if isinstance(c.get("evidence"), dict) and c["evidence"].get("last_verified") == "unrecorded"]
if undated:
warnings.append(
f"{len(undated)} of {len(claim_list)} claims have last_verified 'unrecorded' "
f"({', '.join(map(str, undated[:6]))}{', ...' if len(undated) > 6 else ''}). "
"Record a real date when one is confirmed; never infer one to clear this hint.")
if claims["identity"].get("swiss_permit", "").startswith("UNVERIFIED"):
if (claims.get("identity") or {}).get("swiss_permit", "").startswith("UNVERIFIED"):
warnings.append("Swiss permit type remains unverified; generators must ask before naming it")
return errors, warnings
@@ -148,7 +169,9 @@ _MARKUP = re.compile(r"\\[a-zA-Z@]+\s*(?:\[[^\]]*\])?")
_PLACEHOLDER = re.compile(r"\[GENERATE[^\]]*\]|\bTODO\b|\bFIXME\b|\bXXX\b|\bLOREM IPSUM\b|<<[^>]*>>", re.I)
# "no Terraform experience", "keine Terraform-Erfahrung", "not worked with Terraform"
_NEGATION = re.compile(
r"\b(?:no|not|never|without|kein|keine|keinerlei|ohne|nicht)\b[^.;]{0,60}$", re.I)
r"\b(?:(?:no|without|keine?|keinerlei|ohne)\s+(?:(?:experience|erfahrung)\s+(?:with|in|mit)\s+)?"
r"|(?:not|never)\s+(?:used|worked with)\s+|nicht\s+(?:mit\s+)?)$", re.I)
_INLINE = re.compile(r"\\(?:textbf|textit|textnormal|textrm|textsf|texttt|emph|underline)\{([^{}]*)\}")
# LaTeX accent macros: \"a, 'e, \`e, \^o, \~n, plus \ss. German documents use these instead
@@ -179,6 +202,8 @@ def visible_text(raw: str) -> str:
* Whitespace is collapsed so a term split across a line break still matches.
"""
text = _deaccent(_COMMENT.sub("", raw))
while _INLINE.search(text):
text = _INLINE.sub(r"\1", text)
text = _MARKUP.sub(" ", text)
text = text.replace("{", " ").replace("}", " ").replace("~", " ")
return re.sub(r"\s+", " ", text)
@@ -218,6 +243,17 @@ def _negated(haystack: str, term: str) -> bool:
return bool(hits) and all(_NEGATION.search(haystack[max(0, hit.start() - 70):hit.start()]) for hit in hits)
def _employer_mention(text: str, name: str) -> bool:
"""Narrow exception for a company name, never for a bare technology list."""
if name.lower() != "snowflake":
return False
hits = list(re.finditer(r"\bsnowflake\b", text))
return bool(hits) and all(
re.search(r"\b(?:application to|applying to|position at|role at|bewerbung bei|bewerbung an)\s*$", text[max(0, hit.start()-60):hit.start()])
or re.match(r"\s+(?:inc\b|incorporated\b)", text[hit.end():])
for hit in hits)
def scan_document(path: Path, claims: dict, template: bool = False) -> list[str]:
"""Deterministic, mechanically checkable properties of a generated document.
@@ -229,9 +265,9 @@ def scan_document(path: Path, claims: dict, template: bool = False) -> list[str]
raw = read_text(path)
text = visible_text(raw)
lowered = text.lower()
# Second view with grouping braces closed up rather than spaced out, so a name split by
# markup -- extbf{Snow}flake -- cannot slip past the skill comparison.
dense = re.sub(r"\s+", " ", _MARKUP.sub(" ", _deaccent(_COMMENT.sub("", raw))).replace("{", "").replace("}", "")).lower()
# Keep context local to a sentence, bullet or skills row.
contexts = [visible_text(part).lower() for part in re.split(
r"\\(?:item|skillline|compactentry)\b|[.!?;]\s+|\n\s*\n", _COMMENT.sub("", raw))]
for pattern in claims.get("global_forbidden_output_patterns", []):
if pattern.lower() in lowered:
@@ -248,15 +284,11 @@ def scan_document(path: Path, claims: dict, template: bool = False) -> list[str]
for skill in claims.get("skills", []):
name, output = skill["name"], skill.get("output")
seen_plain = _mentions(lowered, name)
seen_dense = _mentions(dense, name)
if output == "forbidden" and (seen_plain or seen_dense) and not _negated(lowered if seen_plain else dense, name):
mentions = [part for part in contexts if _mentions(part, name)]
if output == "forbidden" and any(not _negated(part, name) and not _employer_mention(part, name) for part in mentions):
errors.append(f"{path}: forbidden or unverified skill: {name}")
if output == "certification-context-only" and _mentions(lowered, name):
window = " ".join(
lowered[max(0, m.start() - 200):m.start() + 200]
for m in re.finditer(re.escape(name.lower()), lowered))
if not re.search(r"certificat|zertifik|coursera|nanodegree|kurs|course|training|weiterbildung", window):
if output == "certification-context-only":
if any(not _negated(part, name) and not re.search(r"certificat|zertifik|coursera|nanodegree|\bkurs\b|course|weiterbildung", part) for part in mentions):
errors.append(
f"{path}: {name} is certification-context-only and appears without that context")
+9 -1
View File
@@ -1,6 +1,6 @@
# Cover Letter Reference
> A cover letter is optional by default. The resume must earn a screen on its own.
> The resume must stand alone. Decide on a letter using the employer and market context below.
## Decision Gate
@@ -20,6 +20,14 @@ Normally skip it when:
Record `Cover Letter Decision: YES/NO` and the reason in the session file before writing.
For Denmark and Norway, normally prepare a short, specific letter when the process permits it;
do not automatically skip it merely because the field is optional. Explain the actual connection
to the employer, relevant evidence and any confirmed country/location motivation. One page is
the normal limit. Employer instructions against letters or motivation questions in the portal
take precedence. If no specific additional value exists, record why the letter is omitted.
Sources reviewed 2026-09-11: [Workindenmark](https://www.workindenmark.dk/job-search-in-denmark/your-cover-letter),
[NAV](https://arbeidsplassen.nav.no/tips-til-jobbsoknaden).
## Evidence Rules
1. Read `resume_builder/canonical/claims.json` first.
@@ -16,7 +16,7 @@ Use current outcome and next action below. Imported legacy notes are historical,
| Aker BP ASA — Data Product Architect, AI-ready Data Products (FINN 469067315) | rejected_no_interview | not recorded | Closed; no pending application action. Read recorded history before reopening. |
| Google - Software Engineer III, Business Home, Zurich (req 93922217108087494) | rejected_no_interview | not recorded | Closed; no pending application action. Read recorded history before reopening. |
| Microsoft — Principal Forward Deployed Engineer, SWE (German Speaking), Zürich (req 200043897) | applied | not recorded | Done — await response |
| BIS Basel — Senior Data & Analytics Engineer, AI (jr100429, 3-yr term) | applied | not recorded | Prep interview brief when screening lands |
| BIS Basel — Senior Data & Analytics Engineer, AI (jr100429, 3-yr term) | rejected_no_interview | not recorded | Abgeschlossen: Absage erhalten; keine weitere Aktion offen. |
| NATO JWC Stavanger — Staff Officer, AI Engineer (2030 Digitalisation, G15, 3-yr PLN) | applied | not recorded | Await screening; no action until contact |
| Microsoft — Senior SWE, Industry Solutions Engineering (ISE), Zürich (req 200040836) | rejected_no_interview | not recorded | Closed; no pending application action. Read recorded history before reopening. |
| Kraken (Payward) — SRE, AI Agents (remote, CH-eligible) | rejected_no_interview | not recorded | Closed; no pending application action. Read recorded history before reopening. |
+8 -4
View File
@@ -11,9 +11,13 @@ Choose before planning content:
|---|---|---|
| International Tech | FAANG, US-tech, international scale-up, English-language engineering role | 2 pages; no photo or demographic data; conventional headers |
| Swiss/DACH | Traditional Swiss/German/Austrian employer or explicit local dossier request | 2 pages normally; optional photo only by user choice; references/certificates separate |
| Denmark | Danish employer without a more specific format | Normally 2 pages; tailored professional profile (roughly 4–8 lines), explain unfamiliar employers and relevant collaboration; assess a specific motivation letter |
| Norway | Norwegian employer without a more specific format | Normally 2 pages; tailored key qualifications (roughly 4–6 lines), clear responsibilities and results; assess a specific motivation letter |
| Employer-specific | NATO, public sector, academic or a mandated portal format | Follow the employer's explicit rules |
International Tech is the default even when the role is physically located in Europe.
International Tech remains the default for international tech recruiting. Use the local Denmark/Norway profile where appropriate; explicit employer requirements and user preferences take precedence. Profile lengths are guidance, not gates. Keep the standing preferences against demographic details; no photo is required.
Market guidance: [Workindenmark profile](https://www.workindenmark.dk/job-search-in-denmark/your-cv/a-good-personal-profile-counts) and [Tekna CV](https://www.tekna.no/en/career/cv-and-cover-letter/how-to-write-a-good-cv/), reviewed 2026-09-11. Only state relocation or local-language plans if confirmed.
## 2. Evidence Preflight
@@ -34,7 +38,7 @@ The reader must see these items in this order:
1. Name, professional identity, contact/location/work authorization.
2. Optional 2--3 line summary.
3. Four to six compact skills lines.
3. Compact skills block: normally 4–8 rendered lines, checked in the PDF.
4. Professional Experience, reverse chronological.
5. Education and relevant certifications once.
@@ -70,14 +74,14 @@ A strong shorter resume is better than a full page padded with weak evidence. Wh
The summary is optional. When used:
- Keep it to 2--3 rendered lines and normally two sentences.
- International Tech/Swiss-DACH: normally 2–3 rendered lines. Denmark/Norway: use the profile guidance above, without padding.
- State the demonstrated professional identity and two strongest proof points.
- Do not state the desired role as if it were prior experience.
- Avoid credential lists and unsupported scale claims.
## 6. Skills
- Use 4--6 compact lines from `skills_taxonomy.md`.
- Use roughly 4–8 rendered lines from `skills_taxonomy.md`, not a count of source macros.
- List only canonical `allowed` or context-appropriate `allowed-with-context` skills.
- Certification-only skills stay in certification context.
- Do not use Expert/Proficient/Familiar self-ratings.
+1 -1
View File
@@ -33,7 +33,7 @@ Read `resume_builder/reference/application_strategy.md` and `critique_framework.
## Audience and Cover-Letter Decisions
Select International Tech, Swiss/DACH or Employer-specific format before writing. International Tech is default.
Select the appropriate profile in `resume_reference.md` before writing, including Denmark/Norway when applicable. International Tech is the default for international tech recruiting; employer requirements take precedence.
Apply `cl_reference.md` and record `Cover Letter Decision: YES/NO`. A deliberately omitted letter is a valid complete package.
@@ -25,6 +25,7 @@ If the only possible content repeats resume bullets or paraphrases company news,
- Use the selected audience profile and employer-requested language.
- International Tech/industry: normally 200--300 words in two or three paragraphs.
- Swiss/DACH: one A4 page, appropriate formality.
- Denmark/Norway: normally one page with specific employer and country motivation; follow `cl_reference.md` when deciding whether to write one.
- Employer-specific: follow the requested format or portal limit.
- Lead with candidate-role connection.
- Add motivation, decision context or a genuine connection.
@@ -38,14 +38,14 @@ Proceed on a no-go only after the user explicitly overrides the stated reason. A
## Phase 2 — Audience and Content Plan
Select International Tech by default. Use Swiss/DACH or Employer-specific format only when the employer/context supports it.
Select the audience profile from `resume_reference.md`: International Tech, Swiss/DACH, Denmark, Norway or Employer-specific. Employer requirements take precedence.
Read the matching role bundle, `skills_taxonomy.md`, `achievement_reframing_guide.md`, and only the experience files needed for selected claims.
Plan:
- Optional 2--3 line summary.
- Four to six evidence-backed skills lines.
- Optional summary sized to the selected audience profile.
- Compact evidence-backed skills block, normally 4–8 rendered lines, verified in the PDF.
- Swisscom 4--5 bullets.
- Bosch 3--4 bullets.
- Zero or one bullet for each older role.
@@ -286,7 +286,7 @@ Each achievement has a **Significance** line (why it matters to any reader) and
| Role Type | Priority | Lead Verb | Framing Angle |
|-----------|----------|-----------|---------------|
| All roles | LOW | Implemented | CV only — do not include on 2-page resume |
| All roles | EXCLUDED | — | Historical record only; omit Capgemini from every document |
---
+5 -4
View File
@@ -40,7 +40,6 @@ Staff data engineer with over ten years of production software experience across
\skillline{Languages}{Python, SQL; Java and C\# (professional, historical); C++ and JavaScript (limited historical use)}
\skillline{Data}{Apache Kafka, Airflow, PySpark, Oracle, Teradata, Hadoop/Impala, data products, metadata and governance}
\skillline{Cloud and platform}{AWS (S3, Glue, Athena/Iceberg, Redshift), CloudFormation, Kubernetes, Docker, GitLab CI/CD, Ansible}
\skillline{ML delivery}{Production inference deployment and support, on-call operations; ELK, Grafana and Prometheus proof-of-concept work}
\skillline{Certifications}{AWS Solutions Architect -- Associate; Data Engineering with AWS; iSAQB CPSA-F; ITIL Foundation}
\end{rSection}
@@ -87,10 +86,12 @@ Staff data engineer with over ten years of production software experience across
% Grades and thesis only where they carry concrete relevance; see claims.json
% grade_output_rule, which keeps grades off by default.
\begin{rSection}{Education}
\compactentry{M.Eng. Computer Aided Engineering (Software Design \& Engineering), Universität der Bundeswehr München}{Apr 2012 -- Oct 2013}
Thesis at Tongji University, Shanghai: \textit{Development of a Web-Based Remote Fault Diagnosis System}; grade 1.0.
\compactentry{M.Eng. Computer Aided Engineering}{Apr 2012 -- Oct 2013}
Universität der Bundeswehr München; Software Design \& Engineering.
% Add thesis or grade only when relevant to this application, using the canonical facts.
\compactentry{B.Eng. Information and Telecommunication Technologies, Universität der Bundeswehr München}{Oct 2009 -- Oct 2012}
\compactentry{B.Eng. Information and Telecommunication Technologies}{Oct 2009 -- Oct 2012}
Universität der Bundeswehr München.
\end{rSection}
\end{document}