mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-07 19:58:24 +00:00
Add the North America managed services role to the resume
Implico role becomes "Team Lead, BTP Cloud & Mobile Apps and North America Managed Services" with a bullet for the practice Greg took on in June 2026. The resume PDF now tightens spacing when it just spills onto a second page so it stays on one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
This commit is contained in:
@@ -55,7 +55,7 @@ interface Body {
|
|||||||
* (and line spacing by a little less), so a short resume can be opened up to
|
* (and line spacing by a little less), so a short resume can be opened up to
|
||||||
* fill its one page instead of leaving the bottom empty.
|
* fill its one page instead of leaving the bottom empty.
|
||||||
*/
|
*/
|
||||||
function renderBody(r: ResumeData, spread = 1): Promise<Body> {
|
export function renderBody(r: ResumeData, spread = 1): Promise<Body> {
|
||||||
const gapDown = (n: number) => doc.moveDown(n * spread);
|
const gapDown = (n: number) => doc.moveDown(n * spread);
|
||||||
const lg = (n: number) => n + (spread - 1) * 1.2;
|
const lg = (n: number) => n + (spread - 1) * 1.2;
|
||||||
const doc = new PDFDocument({
|
const doc = new PDFDocument({
|
||||||
@@ -281,11 +281,20 @@ const LIMIT = 792 - PAGE.bottom;
|
|||||||
/**
|
/**
|
||||||
* A one-page resume that ends well above the footer is laid out again with
|
* A one-page resume that ends well above the footer is laid out again with
|
||||||
* wider gaps, up to twice as wide, so the page reads as full; the widest
|
* wider gaps, up to twice as wide, so the page reads as full; the widest
|
||||||
* spread that still fits on one page wins. Longer resumes are left as they are.
|
* spread that still fits on one page wins. One that just spills onto a second
|
||||||
|
* page is laid out with gaps down to 70% when that brings it back to one.
|
||||||
|
* Longer resumes are left as they are.
|
||||||
*/
|
*/
|
||||||
export async function fitBody(r: ResumeData): Promise<Body> {
|
export async function fitBody(r: ResumeData): Promise<Body> {
|
||||||
const first = await renderBody(r);
|
const first = await renderBody(r);
|
||||||
if (first.pages > 1 || LIMIT - first.end < 24) return first;
|
if (first.pages > 1) {
|
||||||
|
for (const spread of [0.9, 0.8, 0.7]) {
|
||||||
|
const tight = await renderBody(r, spread);
|
||||||
|
if (tight.pages === 1) return tight;
|
||||||
|
}
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
if (LIMIT - first.end < 24) return first;
|
||||||
let best = first;
|
let best = first;
|
||||||
for (let spread = 1.1; spread <= 2.001; spread += 0.1) {
|
for (let spread = 1.1; spread <= 2.001; spread += 0.1) {
|
||||||
const next = await renderBody(r, spread);
|
const next = await renderBody(r, spread);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { fitBody } from "../src/lib/resume-pdf.ts";
|
import { fitBody, renderBody } from "../src/lib/resume-pdf.ts";
|
||||||
import type { ResumeData } from "../src/lib/resume.ts";
|
import type { ResumeData } from "../src/lib/resume.ts";
|
||||||
|
|
||||||
const role = (n: number) =>
|
const role = (n: number) =>
|
||||||
@@ -31,7 +31,15 @@ describe("resume PDF fit", () => {
|
|||||||
expect(fitted.end).toBeGreaterThan(560);
|
expect(fitted.end).toBeGreaterThan(560);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("leaves a resume that runs past one page alone", async () => {
|
it("tightens a resume that just spills onto a second page", async () => {
|
||||||
|
const ten = { ...base, roles: Array.from({ length: 10 }, (_, i) => role(i)) };
|
||||||
|
expect((await renderBody(ten)).pages).toBe(2);
|
||||||
|
const fitted = await fitBody(ten);
|
||||||
|
expect(fitted.pages).toBe(1);
|
||||||
|
expect(fitted.end).toBeLessThanOrEqual(792 - 70);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("leaves a resume that runs well past one page alone", async () => {
|
||||||
const long = await fitBody({ ...base, roles: Array.from({ length: 12 }, (_, i) => role(i)) });
|
const long = await fitBody({ ...base, roles: Array.from({ length: 12 }, (_, i) => role(i)) });
|
||||||
expect(long.pages).toBeGreaterThan(1);
|
expect(long.pages).toBeGreaterThan(1);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ export const skills = {
|
|||||||
*/
|
*/
|
||||||
export const experience: Record<string, Record<string, unknown>> = {
|
export const experience: Record<string, Record<string, unknown>> = {
|
||||||
implico: {
|
implico: {
|
||||||
role: "Team Lead, BTP Cloud & Mobile Apps and US Managed Services",
|
role: "Team Lead, BTP Cloud & Mobile Apps and North America Managed Services",
|
||||||
previousRoles: ["BTP Cloud and Mobile App Lead", "Lead Developer", "Software Developer"],
|
previousRoles: ["BTP Cloud and Mobile App Lead", "Lead Developer", "Software Developer"],
|
||||||
location: "Remote",
|
location: "Remote",
|
||||||
focus: "Leading the team behind Carrier Connect, an SAP logistics platform for third-party carriers.",
|
focus: "Leading the team behind Carrier Connect, an SAP logistics platform for third-party carriers.",
|
||||||
@@ -286,6 +286,7 @@ export const experience: Record<string, Record<string, unknown>> = {
|
|||||||
"Architected Carrier Connect, an SDM for SAP platform that connects third-party carriers to a central purchase order system; it handles 2,000+ orders a day for 500+ active users at 50+ companies with 99.999% uptime.",
|
"Architected Carrier Connect, an SDM for SAP platform that connects third-party carriers to a central purchase order system; it handles 2,000+ orders a day for 500+ active users at 50+ companies with 99.999% uptime.",
|
||||||
"Save about 72 hours per delivery by tracking loading and delivery events and streamlining documents in one system.",
|
"Save about 72 hours per delivery by tracking loading and delivery events and streamlining documents in one system.",
|
||||||
"Built the core delivery interface, the file transfer interfaces and the inventory polling and delivery system, and designed the REST API, Excel and CSV, and Driver App integration paths that feed it.",
|
"Built the core delivery interface, the file transfer interfaces and the inventory polling and delivery system, and designed the REST API, Excel and CSV, and Driver App integration paths that feed it.",
|
||||||
|
"Since June 2026, manage, lead and develop Implico's managed services practice in North America.",
|
||||||
"Lead a 4-person development team and 6 extended US Managed Services members, owning career paths, yearly reviews and resource planning.",
|
"Lead a 4-person development team and 6 extended US Managed Services members, owning career paths, yearly reviews and resource planning.",
|
||||||
"Run the customer side: sales demos, presentations, requirement gathering, training and ongoing communication. Also handle network engineering and administration.",
|
"Run the customer side: sales demos, presentations, requirement gathering, training and ongoing communication. Also handle network engineering and administration.",
|
||||||
"Established the team's engineering standards on Google's published best practices: code review, pull requests, testing and code coverage.",
|
"Established the team's engineering standards on Google's published best practices: code review, pull requests, testing and code coverage.",
|
||||||
|
|||||||
@@ -48,9 +48,10 @@ describe("migration", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("takes the resume from Greg's interview answers", () => {
|
it("takes the resume from Greg's interview answers", () => {
|
||||||
const implico = find("experience", "Team Lead, BTP Cloud & Mobile Apps and US Managed Services at Implico Inc.")!.data as any;
|
const implico = find("experience", "Team Lead, BTP Cloud & Mobile Apps and North America Managed Services at Implico Inc.")!.data as any;
|
||||||
expect(implico.bullets.join(" ")).toContain("500+ active users at 50+ companies");
|
expect(implico.bullets.join(" ")).toContain("500+ active users at 50+ companies");
|
||||||
expect(implico.bullets.join(" ")).toContain("sales demos");
|
expect(implico.bullets.join(" ")).toContain("sales demos");
|
||||||
|
expect(implico.bullets.join(" ")).toContain("managed services practice in North America");
|
||||||
expect(result.notes.filter((n) => n.data.type === "experience" && (n.data as any).earlier)).toHaveLength(2);
|
expect(result.notes.filter((n) => n.data.type === "experience" && (n.data as any).earlier)).toHaveLength(2);
|
||||||
expect(find("page", "Resume")?.data).toMatchObject({ page: "/resume", stats: expect.any(Array), projects: expect.any(Array) });
|
expect(find("page", "Resume")?.data).toMatchObject({ page: "/resume", stats: expect.any(Array), projects: expect.any(Array) });
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user