@singi-labs/sifa-sdk 0.19.111 → 0.20.0
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.
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/resume/funder-cv.js +1 -1
- package/dist/resume/funder-cv.js.map +1 -1
- package/dist/resume/index.cjs +149 -25
- package/dist/resume/index.cjs.map +1 -1
- package/dist/resume/index.d.cts +20 -5
- package/dist/resume/index.d.ts +20 -5
- package/dist/resume/json-resume.js +67 -5
- package/dist/resume/json-resume.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -8044,7 +8044,7 @@ function describeSifaRecord(collection, value) {
|
|
|
8044
8044
|
}
|
|
8045
8045
|
|
|
8046
8046
|
// src/index.ts
|
|
8047
|
-
var SIFA_SDK_VERSION = "0.
|
|
8047
|
+
var SIFA_SDK_VERSION = "0.20.0";
|
|
8048
8048
|
|
|
8049
8049
|
exports.ACTIVITY_TIERS = ACTIVITY_TIERS;
|
|
8050
8050
|
exports.ACTIVITY_VERBS = ACTIVITY_VERBS;
|
package/dist/index.js
CHANGED
|
@@ -393,7 +393,7 @@ import {
|
|
|
393
393
|
roadmapIssueUrl
|
|
394
394
|
} from "./roadmap/index.js";
|
|
395
395
|
import { SIFA_REPO_GROUPS, describeSifaRecord, repoGroupForCollection } from "./repo/index.js";
|
|
396
|
-
var SIFA_SDK_VERSION = "0.
|
|
396
|
+
var SIFA_SDK_VERSION = "0.20.0";
|
|
397
397
|
export {
|
|
398
398
|
ACTIVITY_TIERS,
|
|
399
399
|
ACTIVITY_VERBS,
|
package/dist/resume/funder-cv.js
CHANGED
|
@@ -22,7 +22,7 @@ var FUNDER_CV_GROUPS = {
|
|
|
22
22
|
function funderCvItemId(kind, rkey) {
|
|
23
23
|
return `${kind}:${rkey}`;
|
|
24
24
|
}
|
|
25
|
-
var ITEM_ID_RE = /^(publication|presentation|honor):[A-Za-z0-9._
|
|
25
|
+
var ITEM_ID_RE = /^(publication|presentation|honor):[A-Za-z0-9._~:/-]{1,512}$/;
|
|
26
26
|
function kindOf(id) {
|
|
27
27
|
const kind = id.slice(0, id.indexOf(":"));
|
|
28
28
|
return FUNDER_CV_ITEM_KINDS.includes(kind) ? kind : void 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/resume/funder-cv.ts"],"sourcesContent":["/**\n * Funder-format CV documents: the NIH Biographical Sketch (Common Form plus\n * NIH Supplement) and the ERC Part B1 \"Curriculum vitae and Track Record\".\n *\n * A pure mapper from a Sifa profile and the owner's pick of items to a plain\n * JSON document a typesetter lays out. Like the JSON Resume emitter next door,\n * nothing here fetches or renders.\n *\n * Funders cap how many items a CV may list. The caps live in\n * `FUNDER_CV_GROUPS`, so the export page that offers the choice and the server\n * that enforces it read one number. Sections a funder asks for that Sifa holds\n * no records for (a personal statement, contributions to science, career\n * breaks) are emitted as `null` or empty, for the template to show as a\n * placeholder the applicant fills in.\n */\n\nimport { z } from 'zod';\n\nimport { formatLocation } from '../format/location-utils.js';\nimport { formatStructuredName } from '../format/pds-utils.js';\nimport { filterHidden } from '../profile/section-model.js';\nimport { isTeachingCourse } from '../taxonomy/course-role.js';\nimport type {\n ExternalAccount,\n ProfileCourse,\n ProfileEducation,\n ProfileHonor,\n ProfilePosition,\n ProfilePresentation,\n ProfilePublication,\n ProfileVolunteering,\n} from '../types/index.js';\nimport { normalizeExportContact, type ExportContact } from './export-contact.js';\nimport { toResumeDate } from './json-resume.js';\n\nexport const FUNDER_CV_FORMATS = ['nih', 'erc'] as const;\nexport type FunderCvFormat = (typeof FUNDER_CV_FORMATS)[number];\n\nexport const FUNDER_CV_ITEM_KINDS = ['publication', 'presentation', 'honor'] as const;\nexport type FunderCvItemKind = (typeof FUNDER_CV_ITEM_KINDS)[number];\n\nexport interface FunderCvGroupSpec {\n readonly kinds: readonly FunderCvItemKind[];\n /** Most items the funder's form takes for this group. */\n readonly max: number;\n}\n\n/**\n * The pickable groups per format, with the funder's item limits.\n *\n * - NIH Common Form \"Products\": up to 5 closely related to the proposed\n * project and up to 5 other significant products. Publications, conference\n * papers and presentations all count. NIH Supplement \"Honors\": up to 15.\n * - ERC Part B1 \"Research achievements\": up to ten research outputs. \"Peer\n * recognition\" is \"selected examples\" with no number; 15 is Sifa's own cap,\n * to keep the section inside the 4-page limit.\n */\nexport const FUNDER_CV_GROUPS = {\n nih: {\n related: { kinds: ['publication', 'presentation'], max: 5 },\n other: { kinds: ['publication', 'presentation'], max: 5 },\n honors: { kinds: ['honor'], max: 15 },\n },\n erc: {\n outputs: { kinds: ['publication'], max: 10 },\n recognition: { kinds: ['honor', 'presentation'], max: 15 },\n },\n} as const satisfies Record<FunderCvFormat, Record<string, FunderCvGroupSpec>>;\n\nexport type FunderCvGroupName<F extends FunderCvFormat> = keyof (typeof FUNDER_CV_GROUPS)[F] &\n string;\n\n/** A picked item: `<kind>:<rkey>`, e.g. `publication:3kxyz`. */\nexport function funderCvItemId(kind: FunderCvItemKind, rkey: string): string {\n return `${kind}:${rkey}`;\n}\n\n/** Record key syntax (atproto): 1-512 of A-Z a-z 0-9 . - _ : ~ */\nconst ITEM_ID_RE = /^(publication|presentation|honor):[A-Za-z0-9._~:-]{1,512}$/;\n\nfunction kindOf(id: string): FunderCvItemKind | undefined {\n const kind = id.slice(0, id.indexOf(':'));\n return (FUNDER_CV_ITEM_KINDS as readonly string[]).includes(kind)\n ? (kind as FunderCvItemKind)\n : undefined;\n}\n\nfunction groupSchema(spec: FunderCvGroupSpec) {\n return z\n .array(\n z\n .string()\n .regex(ITEM_ID_RE)\n .refine((id) => spec.kinds.includes(kindOf(id) as FunderCvItemKind), {\n message: `Only ${spec.kinds.join(', ')} items fit here`,\n }),\n )\n .max(spec.max)\n .default([]);\n}\n\nfunction noRepeats(lists: readonly string[][]): boolean {\n const all = lists.flat();\n return new Set(all).size === all.length;\n}\n\nconst nihSelectionSchema = z\n .object({\n format: z.literal('nih'),\n related: groupSchema(FUNDER_CV_GROUPS.nih.related),\n other: groupSchema(FUNDER_CV_GROUPS.nih.other),\n honors: groupSchema(FUNDER_CV_GROUPS.nih.honors),\n })\n .refine((s) => noRepeats([s.related, s.other, s.honors]), {\n message: 'An item can be listed once',\n });\n\nconst ercSelectionSchema = z\n .object({\n format: z.literal('erc'),\n outputs: groupSchema(FUNDER_CV_GROUPS.erc.outputs),\n recognition: groupSchema(FUNDER_CV_GROUPS.erc.recognition),\n })\n .refine((s) => noRepeats([s.outputs, s.recognition]), {\n message: 'An item can be listed once',\n });\n\n/**\n * The owner's pick for one format. Enforces the shape and the funder's limits;\n * whether each id is one of the owner's own visible records is a separate\n * check against the profile, `findUnknownFunderCvItems`.\n */\nexport const funderCvSelectionSchema = z.discriminatedUnion('format', [\n nihSelectionSchema,\n ercSelectionSchema,\n]);\n\nexport type FunderCvSelection = z.infer<typeof funderCvSelectionSchema>;\n\n/** The subset of `Profile` the funder mapper reads. */\nexport interface FunderCvProfileInput {\n readonly handle: string;\n readonly displayName?: string;\n readonly givenName?: string;\n readonly familyName?: string;\n readonly headline?: string;\n readonly website?: string;\n readonly positions?: ProfilePosition[];\n readonly education?: ProfileEducation[];\n readonly publications?: ProfilePublication[];\n readonly presentations?: ProfilePresentation[];\n readonly honors?: ProfileHonor[];\n readonly courses?: ProfileCourse[];\n readonly volunteering?: ProfileVolunteering[];\n readonly externalAccounts?: ExternalAccount[];\n}\n\nexport interface FunderCvCandidate {\n readonly id: string;\n readonly kind: FunderCvItemKind;\n readonly title: string;\n /** `YYYY`, `YYYY-MM` or `YYYY-MM-DD`; absent when the record has no date. */\n readonly date?: string;\n}\n\n/** The most recent visible delivery of a talk, which dates it. */\nfunction latestDeliveryRecord(presentation: ProfilePresentation) {\n const dateOf = (d: { date?: string | null }) => toResumeDate(d.date ?? undefined) ?? '';\n return [...filterHidden(presentation.deliveries)]\n .sort((a, b) => dateOf(a).localeCompare(dateOf(b)))\n .at(-1);\n}\n\nfunction latestDelivery(presentation: ProfilePresentation): string | undefined {\n return toResumeDate(latestDeliveryRecord(presentation)?.date ?? undefined);\n}\n\nfunction candidatesOf(profile: FunderCvProfileInput, kind: FunderCvItemKind): FunderCvCandidate[] {\n switch (kind) {\n case 'publication':\n return filterHidden(profile.publications).map((p) => ({\n id: funderCvItemId(kind, p.rkey),\n kind,\n title: p.title,\n date: toResumeDate(p.date),\n }));\n case 'presentation':\n return filterHidden(profile.presentations).map((p) => ({\n id: funderCvItemId(kind, p.rkey),\n kind,\n title: p.title,\n date: latestDelivery(p),\n }));\n case 'honor':\n return filterHidden(profile.honors).map((h) => ({\n id: funderCvItemId(kind, h.rkey),\n kind,\n title: h.title,\n date: toResumeDate(h.date),\n }));\n }\n}\n\n/**\n * The owner's visible items of the given kinds, most recent first. Undated\n * items go last, in profile order. The sort is stable, so equal dates keep\n * profile order too.\n */\nexport function listFunderCvCandidates(\n profile: FunderCvProfileInput,\n kinds: readonly FunderCvItemKind[],\n): FunderCvCandidate[] {\n const all = kinds.flatMap((kind) => candidatesOf(profile, kind));\n return all.sort((a, b) => {\n if (a.date && b.date) return b.date.localeCompare(a.date);\n if (a.date) return -1;\n if (b.date) return 1;\n return 0;\n });\n}\n\n/**\n * What the export page pre-ticks: the most recent publications up to each\n * limit (for NIH, the first five as closely related and the next five as\n * other), and the most recent honors. Talks are offered but not pre-ticked.\n */\nexport function defaultFunderCvSelection(\n profile: FunderCvProfileInput,\n format: FunderCvFormat,\n): FunderCvSelection {\n const publications = listFunderCvCandidates(profile, ['publication']).map((c) => c.id);\n const honors = listFunderCvCandidates(profile, ['honor']).map((c) => c.id);\n if (format === 'nih') {\n const { related, other } = FUNDER_CV_GROUPS.nih;\n return {\n format,\n related: publications.slice(0, related.max),\n other: publications.slice(related.max, related.max + other.max),\n honors: honors.slice(0, FUNDER_CV_GROUPS.nih.honors.max),\n };\n }\n return {\n format,\n outputs: publications.slice(0, FUNDER_CV_GROUPS.erc.outputs.max),\n recognition: honors.slice(0, FUNDER_CV_GROUPS.erc.recognition.max),\n };\n}\n\nfunction selectedIds(selection: FunderCvSelection): string[] {\n return selection.format === 'nih'\n ? [...selection.related, ...selection.other, ...selection.honors]\n : [...selection.outputs, ...selection.recognition];\n}\n\n/**\n * Ids in the selection that are not visible records on this profile. The\n * profile is the owner's own, so an empty result means every pick is theirs.\n */\nexport function findUnknownFunderCvItems(\n profile: FunderCvProfileInput,\n selection: FunderCvSelection,\n): string[] {\n const known = new Set(listFunderCvCandidates(profile, FUNDER_CV_ITEM_KINDS).map((c) => c.id));\n return selectedIds(selection).filter((id) => !known.has(id));\n}\n\n// ---------------------------------------------------------------------------\n// Document shapes\n// ---------------------------------------------------------------------------\n\nexport interface FunderCvPerson {\n /** Full name as the profile shows it; the handle when there is no name. */\n readonly name: string;\n readonly givenName?: string;\n readonly familyName?: string;\n /** Bare ORCID iD, e.g. `0000-0002-1825-0097`. */\n readonly orcid?: string;\n readonly website?: string;\n}\n\n/**\n * The ERC person, with the contact and personal details the owner typed for\n * this export. The NIH Biographical Sketch has no such block, so only ERC carries them.\n */\nexport interface ErcFunderCvPerson extends FunderCvPerson {\n readonly email?: string;\n readonly phone?: string;\n /** Lines are separated by `\\n`. */\n readonly address?: string;\n readonly driversLicense?: string;\n readonly rightToWork?: string;\n readonly nationality?: string;\n}\n\nexport interface FunderCvOptions {\n /**\n * Contact details the owner typed for this export. Sifa stores none of\n * them. Used for ERC only.\n */\n readonly contact?: ExportContact;\n}\n\n/** One product or research output, ready to cite. */\nexport interface FunderCvCitation {\n readonly kind: 'publication' | 'presentation';\n readonly title: string;\n /** Authors in record order, comma separated. For a talk, the speaker. */\n readonly authors?: string;\n /** Journal, publisher or event. */\n readonly venue?: string;\n /** Year. */\n readonly date?: string;\n readonly doi?: string;\n readonly url?: string;\n}\n\nexport interface FunderCvHonor {\n readonly year?: string;\n readonly title: string;\n readonly organization?: string;\n}\n\nexport interface NihEducation {\n readonly organization: string;\n readonly location?: string;\n readonly degree?: string;\n /** Start date, `MM/YYYY` or `YYYY`. */\n readonly start?: string;\n /** Receipt date, `MM/YYYY` or `YYYY`. */\n readonly end?: string;\n readonly field?: string;\n}\n\nexport interface FunderCvAppointment {\n /** `YYYY`. */\n readonly start?: string;\n /** `YYYY`, or null for a current appointment. */\n readonly end: string | null;\n readonly title: string;\n readonly organization?: string;\n readonly location?: string;\n}\n\n/** NIH Biographical Sketch: Common Form plus NIH Supplement. */\nexport interface NihFunderCv {\n readonly format: 'nih';\n readonly person: FunderCvPerson;\n readonly positionTitle?: string;\n /** Primary organization: the current position's. */\n readonly organization?: string;\n readonly location?: string;\n readonly education: NihEducation[];\n readonly appointments: FunderCvAppointment[];\n readonly products: { readonly related: FunderCvCitation[]; readonly other: FunderCvCitation[] };\n readonly honors: FunderCvHonor[];\n /** Supplement \"Personal Statement\". Sifa holds no record for it. */\n readonly personalStatement: string | null;\n /** Supplement \"Contributions to Science\". Sifa holds no record for them. */\n readonly contributions: string[];\n}\n\nexport interface ErcEducation {\n /** Receipt date, `MM/YYYY` or `YYYY`. */\n readonly date?: string;\n readonly degree?: string;\n readonly field?: string;\n readonly institution: string;\n}\n\nexport interface ErcRecognition {\n readonly kind: 'honor' | 'presentation';\n readonly year?: string;\n readonly title: string;\n /** Awarding body, or the event a talk was given at. */\n readonly organization?: string;\n}\n\nexport interface ErcContribution {\n readonly kind: 'teaching' | 'service';\n readonly start?: string;\n readonly end: string | null;\n readonly title: string;\n readonly organization?: string;\n}\n\n/** ERC Part B1, section \"Curriculum vitae and Track Record\". */\nexport interface ErcFunderCv {\n readonly format: 'erc';\n readonly person: ErcFunderCvPerson;\n readonly education: ErcEducation[];\n readonly currentPositions: FunderCvAppointment[];\n readonly previousPositions: FunderCvAppointment[];\n readonly outputs: FunderCvCitation[];\n readonly recognition: ErcRecognition[];\n readonly communityContributions: ErcContribution[];\n /** Proposal acronym for the page header. Not a profile fact. */\n readonly acronym: string | null;\n /** \"Career breaks, diverse career paths and major life events\". */\n readonly careerBreaks: string | null;\n}\n\nexport type FunderCvDocument = NihFunderCv | ErcFunderCv;\n\n// ---------------------------------------------------------------------------\n// Mapping\n// ---------------------------------------------------------------------------\n\nfunction text(value: string | null | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\nfunction compact<T extends object>(value: T): T {\n return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined)) as T;\n}\n\nfunction year(value: string | null | undefined): string | undefined {\n return toResumeDate(value ?? undefined)?.slice(0, 4);\n}\n\n/** `MM/YYYY` when the month is known, else `YYYY`. */\nfunction monthYear(value: string | null | undefined): string | undefined {\n const date = toResumeDate(value ?? undefined);\n if (!date) return undefined;\n return date.length >= 7 ? `${date.slice(5, 7)}/${date.slice(0, 4)}` : date;\n}\n\n/** Newest start first; undated entries last, in profile order. */\nfunction byStartDesc<T>(items: T[], start: (item: T) => string | undefined): T[] {\n return [...items].sort((a, b) => {\n const da = toResumeDate(start(a));\n const db = toResumeDate(start(b));\n if (da && db) return db.localeCompare(da);\n if (da) return -1;\n if (db) return 1;\n return 0;\n });\n}\n\nconst ORCID_RE = /\\d{4}-\\d{4}-\\d{4}-\\d{3}[\\dX]/;\n\nfunction orcidOf(accounts: ExternalAccount[] | undefined): string | undefined {\n const account = filterHidden(accounts).find((a) => a.platform.toLowerCase() === 'orcid');\n return account ? ORCID_RE.exec(account.url)?.[0] : undefined;\n}\n\nfunction personOf(profile: FunderCvProfileInput): FunderCvPerson {\n return compact<FunderCvPerson>({\n name:\n formatStructuredName(profile.givenName, profile.familyName) ??\n text(profile.displayName) ??\n profile.handle,\n givenName: text(profile.givenName),\n familyName: text(profile.familyName),\n orcid: orcidOf(profile.externalAccounts),\n website: text(profile.website),\n });\n}\n\nfunction appointment(position: ProfilePosition): FunderCvAppointment {\n return compact<FunderCvAppointment>({\n start: year(position.startedAt),\n end: year(position.endedAt) ?? null,\n title: position.title,\n organization: text(position.entityName) ?? text(position.company),\n location: text(formatLocation(position.location)),\n });\n}\n\n/** The current position to name on the form: the flagged primary, else the newest. */\nfunction currentPosition(positions: ProfilePosition[]): ProfilePosition | undefined {\n const current = byStartDesc(\n positions.filter((p) => !text(p.endedAt)),\n (p) => p.startedAt,\n );\n return current.find((p) => p.primary) ?? current[0];\n}\n\nfunction lookup(profile: FunderCvProfileInput) {\n const publications = new Map(filterHidden(profile.publications).map((p) => [p.rkey, p]));\n const presentations = new Map(filterHidden(profile.presentations).map((p) => [p.rkey, p]));\n const honors = new Map(filterHidden(profile.honors).map((h) => [h.rkey, h]));\n const split = (id: string) => {\n const at = id.indexOf(':');\n return { kind: id.slice(0, at), rkey: id.slice(at + 1) };\n };\n return { publications, presentations, honors, split };\n}\n\nfunction citations(\n ids: readonly string[],\n profile: FunderCvProfileInput,\n person: FunderCvPerson,\n): FunderCvCitation[] {\n const { publications, presentations, split } = lookup(profile);\n return ids.flatMap((id): FunderCvCitation[] => {\n const { kind, rkey } = split(id);\n if (kind === 'publication') {\n const p = publications.get(rkey);\n if (!p) return [];\n const authors = (p.contributors ?? []).map((c) => c.name.trim()).filter(Boolean);\n return [\n compact<FunderCvCitation>({\n kind: 'publication',\n title: p.title,\n authors: authors.length > 0 ? authors.join(', ') : undefined,\n venue: text(p.publisher),\n date: year(p.date),\n doi: text(p.doi),\n url: text(p.url),\n }),\n ];\n }\n if (kind === 'presentation') {\n const talk = presentations.get(rkey);\n if (!talk) return [];\n const delivery = latestDeliveryRecord(talk);\n return [\n compact<FunderCvCitation>({\n kind: 'presentation',\n title: talk.title,\n authors: person.name,\n venue: text(delivery?.eventName),\n date: year(delivery?.date),\n url: text(talk.links?.[0]?.uri),\n }),\n ];\n }\n return [];\n });\n}\n\nfunction honorsOf(ids: readonly string[], profile: FunderCvProfileInput): FunderCvHonor[] {\n const { honors, split } = lookup(profile);\n return ids.flatMap((id) => {\n const { kind, rkey } = split(id);\n const h = kind === 'honor' ? honors.get(rkey) : undefined;\n if (!h) return [];\n return [\n compact<FunderCvHonor>({\n year: year(h.date),\n title: h.title,\n organization: text(h.entityName) ?? text(h.issuer),\n }),\n ];\n });\n}\n\n/**\n * Build the funder document from the profile and the owner's selection.\n *\n * Hidden records never appear. Selected ids that match no visible record are\n * skipped; callers that must refuse them check `findUnknownFunderCvItems`\n * first. Selected items keep the order they were picked in.\n */\nexport function profileToFunderCv(\n profile: FunderCvProfileInput,\n selection: FunderCvSelection,\n options: FunderCvOptions = {},\n): FunderCvDocument {\n const person = personOf(profile);\n const positions = filterHidden(profile.positions);\n const education = byStartDesc(filterHidden(profile.education), (e) => e.startedAt);\n\n if (selection.format === 'nih') {\n const current = currentPosition(positions);\n return compact<NihFunderCv>({\n format: 'nih',\n person,\n positionTitle: text(current?.title),\n organization: current ? appointment(current).organization : undefined,\n location: text(formatLocation(current?.location)),\n education: education.map((e) =>\n compact<NihEducation>({\n organization: text(e.entityName) ?? e.institution,\n degree: text(e.degree),\n start: monthYear(e.startedAt),\n end: monthYear(e.endedAt),\n field: text(e.fieldOfStudy),\n }),\n ),\n appointments: byStartDesc(positions, (p) => p.startedAt).map(appointment),\n products: {\n related: citations(selection.related, profile, person),\n other: citations(selection.other, profile, person),\n },\n honors: honorsOf(selection.honors, profile),\n personalStatement: null,\n contributions: [],\n });\n }\n\n const appointments = byStartDesc(positions, (p) => p.startedAt);\n const { presentations, split } = lookup(profile);\n\n const recognition = selection.recognition.flatMap((id): ErcRecognition[] => {\n const { kind, rkey } = split(id);\n if (kind === 'honor') {\n const [honor] = honorsOf([id], profile);\n return honor ? [{ kind: 'honor', ...honor }] : [];\n }\n const talk = kind === 'presentation' ? presentations.get(rkey) : undefined;\n if (!talk) return [];\n const delivery = latestDeliveryRecord(talk);\n return [\n compact<ErcRecognition>({\n kind: 'presentation',\n year: year(delivery?.date),\n title: talk.title,\n organization: text(delivery?.eventName),\n }),\n ];\n });\n\n const teaching = filterHidden(profile.courses)\n .filter(isTeachingCourse)\n .map((c) =>\n compact<ErcContribution>({\n kind: 'teaching',\n start: year(c.startedAt),\n end: year(c.endedAt) ?? null,\n title: c.name,\n organization: text(c.entityName) ?? text(c.institution),\n }),\n );\n const service = filterHidden(profile.volunteering).map((v) =>\n compact<ErcContribution>({\n kind: 'service',\n start: year(v.startDate),\n end: year(v.endDate) ?? null,\n title: text(v.role) ?? text(v.entityName) ?? v.organization,\n organization: text(v.entityName) ?? text(v.organization),\n }),\n );\n\n return {\n format: 'erc',\n person: { ...person, ...normalizeExportContact(options.contact ?? {}) },\n education: education.map((e) =>\n compact<ErcEducation>({\n date: monthYear(e.endedAt) ?? monthYear(e.startedAt),\n degree: text(e.degree),\n field: text(e.fieldOfStudy),\n institution: text(e.entityName) ?? e.institution,\n }),\n ),\n currentPositions: appointments.filter((p) => !text(p.endedAt)).map(appointment),\n previousPositions: appointments.filter((p) => !!text(p.endedAt)).map(appointment),\n outputs: citations(selection.outputs, profile, person),\n recognition,\n communityContributions: [...teaching, ...service],\n acronym: null,\n careerBreaks: null,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/resume/funder-cv.ts"],"sourcesContent":["/**\n * Funder-format CV documents: the NIH Biographical Sketch (Common Form plus\n * NIH Supplement) and the ERC Part B1 \"Curriculum vitae and Track Record\".\n *\n * A pure mapper from a Sifa profile and the owner's pick of items to a plain\n * JSON document a typesetter lays out. Like the JSON Resume emitter next door,\n * nothing here fetches or renders.\n *\n * Funders cap how many items a CV may list. The caps live in\n * `FUNDER_CV_GROUPS`, so the export page that offers the choice and the server\n * that enforces it read one number. Sections a funder asks for that Sifa holds\n * no records for (a personal statement, contributions to science, career\n * breaks) are emitted as `null` or empty, for the template to show as a\n * placeholder the applicant fills in.\n */\n\nimport { z } from 'zod';\n\nimport { formatLocation } from '../format/location-utils.js';\nimport { formatStructuredName } from '../format/pds-utils.js';\nimport { filterHidden } from '../profile/section-model.js';\nimport { isTeachingCourse } from '../taxonomy/course-role.js';\nimport type {\n ExternalAccount,\n ProfileCourse,\n ProfileEducation,\n ProfileHonor,\n ProfilePosition,\n ProfilePresentation,\n ProfilePublication,\n ProfileVolunteering,\n} from '../types/index.js';\nimport { normalizeExportContact, type ExportContact } from './export-contact.js';\nimport { toResumeDate } from './json-resume.js';\n\nexport const FUNDER_CV_FORMATS = ['nih', 'erc'] as const;\nexport type FunderCvFormat = (typeof FUNDER_CV_FORMATS)[number];\n\nexport const FUNDER_CV_ITEM_KINDS = ['publication', 'presentation', 'honor'] as const;\nexport type FunderCvItemKind = (typeof FUNDER_CV_ITEM_KINDS)[number];\n\nexport interface FunderCvGroupSpec {\n readonly kinds: readonly FunderCvItemKind[];\n /** Most items the funder's form takes for this group. */\n readonly max: number;\n}\n\n/**\n * The pickable groups per format, with the funder's item limits.\n *\n * - NIH Common Form \"Products\": up to 5 closely related to the proposed\n * project and up to 5 other significant products. Publications, conference\n * papers and presentations all count. NIH Supplement \"Honors\": up to 15.\n * - ERC Part B1 \"Research achievements\": up to ten research outputs. \"Peer\n * recognition\" is \"selected examples\" with no number; 15 is Sifa's own cap,\n * to keep the section inside the 4-page limit.\n */\nexport const FUNDER_CV_GROUPS = {\n nih: {\n related: { kinds: ['publication', 'presentation'], max: 5 },\n other: { kinds: ['publication', 'presentation'], max: 5 },\n honors: { kinds: ['honor'], max: 15 },\n },\n erc: {\n outputs: { kinds: ['publication'], max: 10 },\n recognition: { kinds: ['honor', 'presentation'], max: 15 },\n },\n} as const satisfies Record<FunderCvFormat, Record<string, FunderCvGroupSpec>>;\n\nexport type FunderCvGroupName<F extends FunderCvFormat> = keyof (typeof FUNDER_CV_GROUPS)[F] &\n string;\n\n/** A picked item: `<kind>:<rkey>`, e.g. `publication:3kxyz`. */\nexport function funderCvItemId(kind: FunderCvItemKind, rkey: string): string {\n return `${kind}:${rkey}`;\n}\n\n/**\n * Record key syntax (atproto: 1-512 of A-Z a-z 0-9 . - _ : ~), plus `/`:\n * publications from Standard.site are keyed by their AT URI (`std-at://...`).\n */\nconst ITEM_ID_RE = /^(publication|presentation|honor):[A-Za-z0-9._~:/-]{1,512}$/;\n\nfunction kindOf(id: string): FunderCvItemKind | undefined {\n const kind = id.slice(0, id.indexOf(':'));\n return (FUNDER_CV_ITEM_KINDS as readonly string[]).includes(kind)\n ? (kind as FunderCvItemKind)\n : undefined;\n}\n\nfunction groupSchema(spec: FunderCvGroupSpec) {\n return z\n .array(\n z\n .string()\n .regex(ITEM_ID_RE)\n .refine((id) => spec.kinds.includes(kindOf(id) as FunderCvItemKind), {\n message: `Only ${spec.kinds.join(', ')} items fit here`,\n }),\n )\n .max(spec.max)\n .default([]);\n}\n\nfunction noRepeats(lists: readonly string[][]): boolean {\n const all = lists.flat();\n return new Set(all).size === all.length;\n}\n\nconst nihSelectionSchema = z\n .object({\n format: z.literal('nih'),\n related: groupSchema(FUNDER_CV_GROUPS.nih.related),\n other: groupSchema(FUNDER_CV_GROUPS.nih.other),\n honors: groupSchema(FUNDER_CV_GROUPS.nih.honors),\n })\n .refine((s) => noRepeats([s.related, s.other, s.honors]), {\n message: 'An item can be listed once',\n });\n\nconst ercSelectionSchema = z\n .object({\n format: z.literal('erc'),\n outputs: groupSchema(FUNDER_CV_GROUPS.erc.outputs),\n recognition: groupSchema(FUNDER_CV_GROUPS.erc.recognition),\n })\n .refine((s) => noRepeats([s.outputs, s.recognition]), {\n message: 'An item can be listed once',\n });\n\n/**\n * The owner's pick for one format. Enforces the shape and the funder's limits;\n * whether each id is one of the owner's own visible records is a separate\n * check against the profile, `findUnknownFunderCvItems`.\n */\nexport const funderCvSelectionSchema = z.discriminatedUnion('format', [\n nihSelectionSchema,\n ercSelectionSchema,\n]);\n\nexport type FunderCvSelection = z.infer<typeof funderCvSelectionSchema>;\n\n/** The subset of `Profile` the funder mapper reads. */\nexport interface FunderCvProfileInput {\n readonly handle: string;\n readonly displayName?: string;\n readonly givenName?: string;\n readonly familyName?: string;\n readonly headline?: string;\n readonly website?: string;\n readonly positions?: ProfilePosition[];\n readonly education?: ProfileEducation[];\n readonly publications?: ProfilePublication[];\n readonly presentations?: ProfilePresentation[];\n readonly honors?: ProfileHonor[];\n readonly courses?: ProfileCourse[];\n readonly volunteering?: ProfileVolunteering[];\n readonly externalAccounts?: ExternalAccount[];\n}\n\nexport interface FunderCvCandidate {\n readonly id: string;\n readonly kind: FunderCvItemKind;\n readonly title: string;\n /** `YYYY`, `YYYY-MM` or `YYYY-MM-DD`; absent when the record has no date. */\n readonly date?: string;\n}\n\n/** The most recent visible delivery of a talk, which dates it. */\nfunction latestDeliveryRecord(presentation: ProfilePresentation) {\n const dateOf = (d: { date?: string | null }) => toResumeDate(d.date ?? undefined) ?? '';\n return [...filterHidden(presentation.deliveries)]\n .sort((a, b) => dateOf(a).localeCompare(dateOf(b)))\n .at(-1);\n}\n\nfunction latestDelivery(presentation: ProfilePresentation): string | undefined {\n return toResumeDate(latestDeliveryRecord(presentation)?.date ?? undefined);\n}\n\nfunction candidatesOf(profile: FunderCvProfileInput, kind: FunderCvItemKind): FunderCvCandidate[] {\n switch (kind) {\n case 'publication':\n return filterHidden(profile.publications).map((p) => ({\n id: funderCvItemId(kind, p.rkey),\n kind,\n title: p.title,\n date: toResumeDate(p.date),\n }));\n case 'presentation':\n return filterHidden(profile.presentations).map((p) => ({\n id: funderCvItemId(kind, p.rkey),\n kind,\n title: p.title,\n date: latestDelivery(p),\n }));\n case 'honor':\n return filterHidden(profile.honors).map((h) => ({\n id: funderCvItemId(kind, h.rkey),\n kind,\n title: h.title,\n date: toResumeDate(h.date),\n }));\n }\n}\n\n/**\n * The owner's visible items of the given kinds, most recent first. Undated\n * items go last, in profile order. The sort is stable, so equal dates keep\n * profile order too.\n */\nexport function listFunderCvCandidates(\n profile: FunderCvProfileInput,\n kinds: readonly FunderCvItemKind[],\n): FunderCvCandidate[] {\n const all = kinds.flatMap((kind) => candidatesOf(profile, kind));\n return all.sort((a, b) => {\n if (a.date && b.date) return b.date.localeCompare(a.date);\n if (a.date) return -1;\n if (b.date) return 1;\n return 0;\n });\n}\n\n/**\n * What the export page pre-ticks: the most recent publications up to each\n * limit (for NIH, the first five as closely related and the next five as\n * other), and the most recent honors. Talks are offered but not pre-ticked.\n */\nexport function defaultFunderCvSelection(\n profile: FunderCvProfileInput,\n format: FunderCvFormat,\n): FunderCvSelection {\n const publications = listFunderCvCandidates(profile, ['publication']).map((c) => c.id);\n const honors = listFunderCvCandidates(profile, ['honor']).map((c) => c.id);\n if (format === 'nih') {\n const { related, other } = FUNDER_CV_GROUPS.nih;\n return {\n format,\n related: publications.slice(0, related.max),\n other: publications.slice(related.max, related.max + other.max),\n honors: honors.slice(0, FUNDER_CV_GROUPS.nih.honors.max),\n };\n }\n return {\n format,\n outputs: publications.slice(0, FUNDER_CV_GROUPS.erc.outputs.max),\n recognition: honors.slice(0, FUNDER_CV_GROUPS.erc.recognition.max),\n };\n}\n\nfunction selectedIds(selection: FunderCvSelection): string[] {\n return selection.format === 'nih'\n ? [...selection.related, ...selection.other, ...selection.honors]\n : [...selection.outputs, ...selection.recognition];\n}\n\n/**\n * Ids in the selection that are not visible records on this profile. The\n * profile is the owner's own, so an empty result means every pick is theirs.\n */\nexport function findUnknownFunderCvItems(\n profile: FunderCvProfileInput,\n selection: FunderCvSelection,\n): string[] {\n const known = new Set(listFunderCvCandidates(profile, FUNDER_CV_ITEM_KINDS).map((c) => c.id));\n return selectedIds(selection).filter((id) => !known.has(id));\n}\n\n// ---------------------------------------------------------------------------\n// Document shapes\n// ---------------------------------------------------------------------------\n\nexport interface FunderCvPerson {\n /** Full name as the profile shows it; the handle when there is no name. */\n readonly name: string;\n readonly givenName?: string;\n readonly familyName?: string;\n /** Bare ORCID iD, e.g. `0000-0002-1825-0097`. */\n readonly orcid?: string;\n readonly website?: string;\n}\n\n/**\n * The ERC person, with the contact and personal details the owner typed for\n * this export. The NIH Biographical Sketch has no such block, so only ERC carries them.\n */\nexport interface ErcFunderCvPerson extends FunderCvPerson {\n readonly email?: string;\n readonly phone?: string;\n /** Lines are separated by `\\n`. */\n readonly address?: string;\n readonly driversLicense?: string;\n readonly rightToWork?: string;\n readonly nationality?: string;\n}\n\nexport interface FunderCvOptions {\n /**\n * Contact details the owner typed for this export. Sifa stores none of\n * them. Used for ERC only.\n */\n readonly contact?: ExportContact;\n}\n\n/** One product or research output, ready to cite. */\nexport interface FunderCvCitation {\n readonly kind: 'publication' | 'presentation';\n readonly title: string;\n /** Authors in record order, comma separated. For a talk, the speaker. */\n readonly authors?: string;\n /** Journal, publisher or event. */\n readonly venue?: string;\n /** Year. */\n readonly date?: string;\n readonly doi?: string;\n readonly url?: string;\n}\n\nexport interface FunderCvHonor {\n readonly year?: string;\n readonly title: string;\n readonly organization?: string;\n}\n\nexport interface NihEducation {\n readonly organization: string;\n readonly location?: string;\n readonly degree?: string;\n /** Start date, `MM/YYYY` or `YYYY`. */\n readonly start?: string;\n /** Receipt date, `MM/YYYY` or `YYYY`. */\n readonly end?: string;\n readonly field?: string;\n}\n\nexport interface FunderCvAppointment {\n /** `YYYY`. */\n readonly start?: string;\n /** `YYYY`, or null for a current appointment. */\n readonly end: string | null;\n readonly title: string;\n readonly organization?: string;\n readonly location?: string;\n}\n\n/** NIH Biographical Sketch: Common Form plus NIH Supplement. */\nexport interface NihFunderCv {\n readonly format: 'nih';\n readonly person: FunderCvPerson;\n readonly positionTitle?: string;\n /** Primary organization: the current position's. */\n readonly organization?: string;\n readonly location?: string;\n readonly education: NihEducation[];\n readonly appointments: FunderCvAppointment[];\n readonly products: { readonly related: FunderCvCitation[]; readonly other: FunderCvCitation[] };\n readonly honors: FunderCvHonor[];\n /** Supplement \"Personal Statement\". Sifa holds no record for it. */\n readonly personalStatement: string | null;\n /** Supplement \"Contributions to Science\". Sifa holds no record for them. */\n readonly contributions: string[];\n}\n\nexport interface ErcEducation {\n /** Receipt date, `MM/YYYY` or `YYYY`. */\n readonly date?: string;\n readonly degree?: string;\n readonly field?: string;\n readonly institution: string;\n}\n\nexport interface ErcRecognition {\n readonly kind: 'honor' | 'presentation';\n readonly year?: string;\n readonly title: string;\n /** Awarding body, or the event a talk was given at. */\n readonly organization?: string;\n}\n\nexport interface ErcContribution {\n readonly kind: 'teaching' | 'service';\n readonly start?: string;\n readonly end: string | null;\n readonly title: string;\n readonly organization?: string;\n}\n\n/** ERC Part B1, section \"Curriculum vitae and Track Record\". */\nexport interface ErcFunderCv {\n readonly format: 'erc';\n readonly person: ErcFunderCvPerson;\n readonly education: ErcEducation[];\n readonly currentPositions: FunderCvAppointment[];\n readonly previousPositions: FunderCvAppointment[];\n readonly outputs: FunderCvCitation[];\n readonly recognition: ErcRecognition[];\n readonly communityContributions: ErcContribution[];\n /** Proposal acronym for the page header. Not a profile fact. */\n readonly acronym: string | null;\n /** \"Career breaks, diverse career paths and major life events\". */\n readonly careerBreaks: string | null;\n}\n\nexport type FunderCvDocument = NihFunderCv | ErcFunderCv;\n\n// ---------------------------------------------------------------------------\n// Mapping\n// ---------------------------------------------------------------------------\n\nfunction text(value: string | null | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\nfunction compact<T extends object>(value: T): T {\n return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined)) as T;\n}\n\nfunction year(value: string | null | undefined): string | undefined {\n return toResumeDate(value ?? undefined)?.slice(0, 4);\n}\n\n/** `MM/YYYY` when the month is known, else `YYYY`. */\nfunction monthYear(value: string | null | undefined): string | undefined {\n const date = toResumeDate(value ?? undefined);\n if (!date) return undefined;\n return date.length >= 7 ? `${date.slice(5, 7)}/${date.slice(0, 4)}` : date;\n}\n\n/** Newest start first; undated entries last, in profile order. */\nfunction byStartDesc<T>(items: T[], start: (item: T) => string | undefined): T[] {\n return [...items].sort((a, b) => {\n const da = toResumeDate(start(a));\n const db = toResumeDate(start(b));\n if (da && db) return db.localeCompare(da);\n if (da) return -1;\n if (db) return 1;\n return 0;\n });\n}\n\nconst ORCID_RE = /\\d{4}-\\d{4}-\\d{4}-\\d{3}[\\dX]/;\n\nfunction orcidOf(accounts: ExternalAccount[] | undefined): string | undefined {\n const account = filterHidden(accounts).find((a) => a.platform.toLowerCase() === 'orcid');\n return account ? ORCID_RE.exec(account.url)?.[0] : undefined;\n}\n\nfunction personOf(profile: FunderCvProfileInput): FunderCvPerson {\n return compact<FunderCvPerson>({\n name:\n formatStructuredName(profile.givenName, profile.familyName) ??\n text(profile.displayName) ??\n profile.handle,\n givenName: text(profile.givenName),\n familyName: text(profile.familyName),\n orcid: orcidOf(profile.externalAccounts),\n website: text(profile.website),\n });\n}\n\nfunction appointment(position: ProfilePosition): FunderCvAppointment {\n return compact<FunderCvAppointment>({\n start: year(position.startedAt),\n end: year(position.endedAt) ?? null,\n title: position.title,\n organization: text(position.entityName) ?? text(position.company),\n location: text(formatLocation(position.location)),\n });\n}\n\n/** The current position to name on the form: the flagged primary, else the newest. */\nfunction currentPosition(positions: ProfilePosition[]): ProfilePosition | undefined {\n const current = byStartDesc(\n positions.filter((p) => !text(p.endedAt)),\n (p) => p.startedAt,\n );\n return current.find((p) => p.primary) ?? current[0];\n}\n\nfunction lookup(profile: FunderCvProfileInput) {\n const publications = new Map(filterHidden(profile.publications).map((p) => [p.rkey, p]));\n const presentations = new Map(filterHidden(profile.presentations).map((p) => [p.rkey, p]));\n const honors = new Map(filterHidden(profile.honors).map((h) => [h.rkey, h]));\n const split = (id: string) => {\n const at = id.indexOf(':');\n return { kind: id.slice(0, at), rkey: id.slice(at + 1) };\n };\n return { publications, presentations, honors, split };\n}\n\nfunction citations(\n ids: readonly string[],\n profile: FunderCvProfileInput,\n person: FunderCvPerson,\n): FunderCvCitation[] {\n const { publications, presentations, split } = lookup(profile);\n return ids.flatMap((id): FunderCvCitation[] => {\n const { kind, rkey } = split(id);\n if (kind === 'publication') {\n const p = publications.get(rkey);\n if (!p) return [];\n const authors = (p.contributors ?? []).map((c) => c.name.trim()).filter(Boolean);\n return [\n compact<FunderCvCitation>({\n kind: 'publication',\n title: p.title,\n authors: authors.length > 0 ? authors.join(', ') : undefined,\n venue: text(p.publisher),\n date: year(p.date),\n doi: text(p.doi),\n url: text(p.url),\n }),\n ];\n }\n if (kind === 'presentation') {\n const talk = presentations.get(rkey);\n if (!talk) return [];\n const delivery = latestDeliveryRecord(talk);\n return [\n compact<FunderCvCitation>({\n kind: 'presentation',\n title: talk.title,\n authors: person.name,\n venue: text(delivery?.eventName),\n date: year(delivery?.date),\n url: text(talk.links?.[0]?.uri),\n }),\n ];\n }\n return [];\n });\n}\n\nfunction honorsOf(ids: readonly string[], profile: FunderCvProfileInput): FunderCvHonor[] {\n const { honors, split } = lookup(profile);\n return ids.flatMap((id) => {\n const { kind, rkey } = split(id);\n const h = kind === 'honor' ? honors.get(rkey) : undefined;\n if (!h) return [];\n return [\n compact<FunderCvHonor>({\n year: year(h.date),\n title: h.title,\n organization: text(h.entityName) ?? text(h.issuer),\n }),\n ];\n });\n}\n\n/**\n * Build the funder document from the profile and the owner's selection.\n *\n * Hidden records never appear. Selected ids that match no visible record are\n * skipped; callers that must refuse them check `findUnknownFunderCvItems`\n * first. Selected items keep the order they were picked in.\n */\nexport function profileToFunderCv(\n profile: FunderCvProfileInput,\n selection: FunderCvSelection,\n options: FunderCvOptions = {},\n): FunderCvDocument {\n const person = personOf(profile);\n const positions = filterHidden(profile.positions);\n const education = byStartDesc(filterHidden(profile.education), (e) => e.startedAt);\n\n if (selection.format === 'nih') {\n const current = currentPosition(positions);\n return compact<NihFunderCv>({\n format: 'nih',\n person,\n positionTitle: text(current?.title),\n organization: current ? appointment(current).organization : undefined,\n location: text(formatLocation(current?.location)),\n education: education.map((e) =>\n compact<NihEducation>({\n organization: text(e.entityName) ?? e.institution,\n degree: text(e.degree),\n start: monthYear(e.startedAt),\n end: monthYear(e.endedAt),\n field: text(e.fieldOfStudy),\n }),\n ),\n appointments: byStartDesc(positions, (p) => p.startedAt).map(appointment),\n products: {\n related: citations(selection.related, profile, person),\n other: citations(selection.other, profile, person),\n },\n honors: honorsOf(selection.honors, profile),\n personalStatement: null,\n contributions: [],\n });\n }\n\n const appointments = byStartDesc(positions, (p) => p.startedAt);\n const { presentations, split } = lookup(profile);\n\n const recognition = selection.recognition.flatMap((id): ErcRecognition[] => {\n const { kind, rkey } = split(id);\n if (kind === 'honor') {\n const [honor] = honorsOf([id], profile);\n return honor ? [{ kind: 'honor', ...honor }] : [];\n }\n const talk = kind === 'presentation' ? presentations.get(rkey) : undefined;\n if (!talk) return [];\n const delivery = latestDeliveryRecord(talk);\n return [\n compact<ErcRecognition>({\n kind: 'presentation',\n year: year(delivery?.date),\n title: talk.title,\n organization: text(delivery?.eventName),\n }),\n ];\n });\n\n const teaching = filterHidden(profile.courses)\n .filter(isTeachingCourse)\n .map((c) =>\n compact<ErcContribution>({\n kind: 'teaching',\n start: year(c.startedAt),\n end: year(c.endedAt) ?? null,\n title: c.name,\n organization: text(c.entityName) ?? text(c.institution),\n }),\n );\n const service = filterHidden(profile.volunteering).map((v) =>\n compact<ErcContribution>({\n kind: 'service',\n start: year(v.startDate),\n end: year(v.endDate) ?? null,\n title: text(v.role) ?? text(v.entityName) ?? v.organization,\n organization: text(v.entityName) ?? text(v.organization),\n }),\n );\n\n return {\n format: 'erc',\n person: { ...person, ...normalizeExportContact(options.contact ?? {}) },\n education: education.map((e) =>\n compact<ErcEducation>({\n date: monthYear(e.endedAt) ?? monthYear(e.startedAt),\n degree: text(e.degree),\n field: text(e.fieldOfStudy),\n institution: text(e.entityName) ?? e.institution,\n }),\n ),\n currentPositions: appointments.filter((p) => !text(p.endedAt)).map(appointment),\n previousPositions: appointments.filter((p) => !!text(p.endedAt)).map(appointment),\n outputs: citations(selection.outputs, profile, person),\n recognition,\n communityContributions: [...teaching, ...service],\n acronym: null,\n careerBreaks: null,\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/resume/index.cjs
CHANGED
|
@@ -198,6 +198,91 @@ var sortCertifications = (items) => sortByDateDesc(items, certDateExtractor);
|
|
|
198
198
|
var sortHonors = (items) => sortByDateDesc(items, singleDateExtractor);
|
|
199
199
|
var sortLanguages = (items) => sortLanguagesByProficiency(items);
|
|
200
200
|
|
|
201
|
+
// src/taxonomy/course-role.ts
|
|
202
|
+
var COURSE_ROLE_TAKEN = "id.sifa.defs#courseTaken";
|
|
203
|
+
var COURSE_ROLE_TAUGHT = "id.sifa.defs#courseTaught";
|
|
204
|
+
var COURSE_ROLE_TEACHING_ASSISTANT = "id.sifa.defs#courseTeachingAssistant";
|
|
205
|
+
var COURSE_ROLE_OPTIONS = [
|
|
206
|
+
{ value: COURSE_ROLE_TAKEN, label: "Student" },
|
|
207
|
+
{ value: COURSE_ROLE_TAUGHT, label: "Instructor" },
|
|
208
|
+
{ value: COURSE_ROLE_TEACHING_ASSISTANT, label: "Teaching assistant" }
|
|
209
|
+
];
|
|
210
|
+
Object.fromEntries(
|
|
211
|
+
COURSE_ROLE_OPTIONS.map((o) => [o.value, o.label])
|
|
212
|
+
);
|
|
213
|
+
var TEACHING_ROLES = /* @__PURE__ */ new Set([COURSE_ROLE_TAUGHT, COURSE_ROLE_TEACHING_ASSISTANT]);
|
|
214
|
+
function isTeachingCourse(course) {
|
|
215
|
+
return !!course.role && TEACHING_ROLES.has(course.role);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/taxonomy/involvement-kind.ts
|
|
219
|
+
var INVOLVEMENT_KIND_OPTIONS = [
|
|
220
|
+
{ value: "id.sifa.defs#involvementOpenSource", label: "Open source", heading: "Open Source" },
|
|
221
|
+
{ value: "id.sifa.defs#involvementCommunity", label: "Community", heading: "Community" },
|
|
222
|
+
{ value: "id.sifa.defs#involvementCharity", label: "Volunteering", heading: "Volunteering" },
|
|
223
|
+
{ value: "id.sifa.defs#involvementCivic", label: "Civic", heading: "Civic" },
|
|
224
|
+
{ value: "id.sifa.defs#involvementOther", label: "Other", heading: "Other" }
|
|
225
|
+
];
|
|
226
|
+
var INVOLVEMENT_KIND_LABELS = Object.fromEntries(
|
|
227
|
+
INVOLVEMENT_KIND_OPTIONS.map((o) => [o.value, o.label])
|
|
228
|
+
);
|
|
229
|
+
Object.fromEntries(
|
|
230
|
+
INVOLVEMENT_KIND_OPTIONS.map((o) => [o.value, o.heading])
|
|
231
|
+
);
|
|
232
|
+
function getInvolvementKindLabel(value) {
|
|
233
|
+
if (!value) return void 0;
|
|
234
|
+
return INVOLVEMENT_KIND_LABELS[value] ?? value;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/taxonomy/skill-categories.ts
|
|
238
|
+
var SKILL_CATEGORIES = [
|
|
239
|
+
{ value: "technical", label: "Technical" },
|
|
240
|
+
{ value: "business", label: "Business" },
|
|
241
|
+
{ value: "creative", label: "Creative" },
|
|
242
|
+
{ value: "interpersonal", label: "Interpersonal" },
|
|
243
|
+
{ value: "industry", label: "Industry" },
|
|
244
|
+
{ value: "community", label: "Community" },
|
|
245
|
+
{ value: "security", label: "Security" }
|
|
246
|
+
];
|
|
247
|
+
var CATEGORY_ORDER = SKILL_CATEGORIES.map((c) => c.value);
|
|
248
|
+
var CATEGORY_LABELS = {
|
|
249
|
+
...Object.fromEntries(SKILL_CATEGORIES.map((c) => [c.value, c.label])),
|
|
250
|
+
other: "Other"
|
|
251
|
+
};
|
|
252
|
+
function normalizeSkillCategory(value) {
|
|
253
|
+
if (typeof value !== "string") return void 0;
|
|
254
|
+
const afterRef = value.includes("#") ? value.slice(value.lastIndexOf("#") + 1) : value;
|
|
255
|
+
const token = afterRef.trim().toLowerCase();
|
|
256
|
+
return token === "" ? void 0 : token;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/taxonomy/skill-grouping.ts
|
|
260
|
+
function groupSkillsByCategory(skills) {
|
|
261
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
262
|
+
for (const skill of skills) {
|
|
263
|
+
const normalised = normalizeSkillCategory(skill.category) ?? "";
|
|
264
|
+
const key = CATEGORY_ORDER.includes(normalised) ? normalised : "other";
|
|
265
|
+
const bucket = grouped.get(key) ?? [];
|
|
266
|
+
bucket.push(skill);
|
|
267
|
+
grouped.set(key, bucket);
|
|
268
|
+
}
|
|
269
|
+
for (const [, groupSkills] of grouped) {
|
|
270
|
+
groupSkills.sort((a, b) => {
|
|
271
|
+
const countDiff = (b.endorsementCount ?? 0) - (a.endorsementCount ?? 0);
|
|
272
|
+
if (countDiff !== 0) return countDiff;
|
|
273
|
+
return compareText(a.name, b.name);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
const ordered = [];
|
|
277
|
+
for (const cat of CATEGORY_ORDER) {
|
|
278
|
+
const group = grouped.get(cat);
|
|
279
|
+
if (group?.length) ordered.push([cat, group]);
|
|
280
|
+
}
|
|
281
|
+
const otherGroup = grouped.get("other");
|
|
282
|
+
if (otherGroup?.length) ordered.push(["other", otherGroup]);
|
|
283
|
+
return ordered;
|
|
284
|
+
}
|
|
285
|
+
|
|
201
286
|
// src/jsonld/url.ts
|
|
202
287
|
var DEFAULT_BASE_URL = "https://sifa.id";
|
|
203
288
|
function normaliseBaseUrl(baseUrl) {
|
|
@@ -269,6 +354,41 @@ function text(value, s) {
|
|
|
269
354
|
function orgName(entityName, stored, s) {
|
|
270
355
|
return text(entityName, s) ?? text(stored, s);
|
|
271
356
|
}
|
|
357
|
+
var TALK_EVENTS_SHOWN = 3;
|
|
358
|
+
function byDateDesc(a, b) {
|
|
359
|
+
return (b.date ?? "").localeCompare(a.date ?? "");
|
|
360
|
+
}
|
|
361
|
+
function mapTalks(profile, s) {
|
|
362
|
+
const standalone = filterHidden(profile.presentationDeliveries).filter(
|
|
363
|
+
(d) => !d.presentationRkey
|
|
364
|
+
);
|
|
365
|
+
const talks = filterHidden(profile.presentations).map((talk) => {
|
|
366
|
+
const deliveries = filterHidden(talk.deliveries).sort(byDateDesc);
|
|
367
|
+
const events = [
|
|
368
|
+
...new Set(deliveries.map((d) => text(d.eventName ?? void 0, s)).filter(Boolean))
|
|
369
|
+
];
|
|
370
|
+
const shown = events.slice(0, TALK_EVENTS_SHOWN).join(", ");
|
|
371
|
+
const more = events.length - TALK_EVENTS_SHOWN;
|
|
372
|
+
return compact({
|
|
373
|
+
name: s(talk.title),
|
|
374
|
+
publisher: more > 0 ? `${shown} and ${more} more` : shown || void 0,
|
|
375
|
+
releaseDate: toResumeDate(deliveries[0]?.date ?? void 0),
|
|
376
|
+
url: link(talk.links?.[0]?.uri, s),
|
|
377
|
+
summary: text(talk.description ?? void 0, s),
|
|
378
|
+
type: "Talks"
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
const loose = standalone.map(
|
|
382
|
+
(d) => compact({
|
|
383
|
+
name: s(d.title?.trim() || d.eventName?.trim() || ""),
|
|
384
|
+
publisher: text(d.eventName ?? void 0, s),
|
|
385
|
+
releaseDate: toResumeDate(d.date ?? void 0),
|
|
386
|
+
url: link(d.links?.[0]?.uri, s),
|
|
387
|
+
type: "Talks"
|
|
388
|
+
})
|
|
389
|
+
);
|
|
390
|
+
return [...talks, ...loose].filter((t) => t.name).sort((a, b) => byDateDesc({ date: a.releaseDate }, { date: b.releaseDate }));
|
|
391
|
+
}
|
|
272
392
|
function profileToJsonResume(profile, options = {}) {
|
|
273
393
|
const s = options.sanitize ?? identity;
|
|
274
394
|
const baseUrl = normaliseBaseUrl(options.baseUrl);
|
|
@@ -302,15 +422,22 @@ function profileToJsonResume(profile, options = {}) {
|
|
|
302
422
|
endDate: toResumeDate(position.endedAt)
|
|
303
423
|
})
|
|
304
424
|
);
|
|
305
|
-
const volunteer = sortByActiveDateRange(
|
|
306
|
-
(entry) =>
|
|
425
|
+
const volunteer = sortByActiveDateRange([
|
|
426
|
+
...filterHidden(profile.volunteering).map((entry) => ({
|
|
307
427
|
organization: orgName(entry.entityName, entry.organization, s),
|
|
308
428
|
position: text(entry.role, s),
|
|
309
429
|
summary: text(entry.description, s),
|
|
310
430
|
startDate: toResumeDate(entry.startDate),
|
|
311
431
|
endDate: toResumeDate(entry.endDate)
|
|
312
|
-
})
|
|
313
|
-
|
|
432
|
+
})),
|
|
433
|
+
...filterHidden(profile.involvement).map((entry) => ({
|
|
434
|
+
organization: orgName(entry.entityName, entry.upstream, s),
|
|
435
|
+
position: text(entry.role, s) ?? text(getInvolvementKindLabel(entry.kind), s),
|
|
436
|
+
summary: text(entry.description, s),
|
|
437
|
+
startDate: toResumeDate(entry.startedAt),
|
|
438
|
+
endDate: toResumeDate(entry.endedAt)
|
|
439
|
+
}))
|
|
440
|
+
]).map((entry) => compact(entry));
|
|
314
441
|
const education = sortEducation(filterHidden(profile.education)).map(
|
|
315
442
|
(entry) => compact({
|
|
316
443
|
institution: orgName(entry.entityName, entry.institution, s),
|
|
@@ -339,6 +466,18 @@ function profileToJsonResume(profile, options = {}) {
|
|
|
339
466
|
url: link(cert.credentialUrl, s)
|
|
340
467
|
})
|
|
341
468
|
);
|
|
469
|
+
const listedCredentials = new Set(filterHidden(profile.certifications).map((c) => c.rkey));
|
|
470
|
+
for (const course of filterHidden(profile.courses)) {
|
|
471
|
+
if (isTeachingCourse(course)) continue;
|
|
472
|
+
if (course.credentialRkey && listedCredentials.has(course.credentialRkey)) continue;
|
|
473
|
+
certificates.push(
|
|
474
|
+
compact({
|
|
475
|
+
name: s(course.name),
|
|
476
|
+
date: toResumeDate(course.completedAt),
|
|
477
|
+
issuer: orgName(course.entityName, course.institution, s)
|
|
478
|
+
})
|
|
479
|
+
);
|
|
480
|
+
}
|
|
342
481
|
const publications = sortPublications(filterHidden(profile.publications)).map(
|
|
343
482
|
(publication) => compact({
|
|
344
483
|
name: s(publication.title),
|
|
@@ -348,7 +487,11 @@ function profileToJsonResume(profile, options = {}) {
|
|
|
348
487
|
summary: text(publication.description, s)
|
|
349
488
|
})
|
|
350
489
|
);
|
|
351
|
-
|
|
490
|
+
publications.push(...mapTalks(profile, s));
|
|
491
|
+
const skills = groupSkillsByCategory(profile.skills ?? []).map(([category, items]) => ({
|
|
492
|
+
name: CATEGORY_LABELS[category] ?? category,
|
|
493
|
+
keywords: items.map((skill) => s(skill.name))
|
|
494
|
+
}));
|
|
352
495
|
const languages = sortLanguages(filterHidden(profile.languages)).map(
|
|
353
496
|
(language) => compact({
|
|
354
497
|
language: s(language.language),
|
|
@@ -378,25 +521,6 @@ function profileToJsonResume(profile, options = {}) {
|
|
|
378
521
|
meta: { canonical, version: SCHEMA_VERSION }
|
|
379
522
|
};
|
|
380
523
|
}
|
|
381
|
-
|
|
382
|
-
// src/taxonomy/course-role.ts
|
|
383
|
-
var COURSE_ROLE_TAKEN = "id.sifa.defs#courseTaken";
|
|
384
|
-
var COURSE_ROLE_TAUGHT = "id.sifa.defs#courseTaught";
|
|
385
|
-
var COURSE_ROLE_TEACHING_ASSISTANT = "id.sifa.defs#courseTeachingAssistant";
|
|
386
|
-
var COURSE_ROLE_OPTIONS = [
|
|
387
|
-
{ value: COURSE_ROLE_TAKEN, label: "Student" },
|
|
388
|
-
{ value: COURSE_ROLE_TAUGHT, label: "Instructor" },
|
|
389
|
-
{ value: COURSE_ROLE_TEACHING_ASSISTANT, label: "Teaching assistant" }
|
|
390
|
-
];
|
|
391
|
-
Object.fromEntries(
|
|
392
|
-
COURSE_ROLE_OPTIONS.map((o) => [o.value, o.label])
|
|
393
|
-
);
|
|
394
|
-
var TEACHING_ROLES = /* @__PURE__ */ new Set([COURSE_ROLE_TAUGHT, COURSE_ROLE_TEACHING_ASSISTANT]);
|
|
395
|
-
function isTeachingCourse(course) {
|
|
396
|
-
return !!course.role && TEACHING_ROLES.has(course.role);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// src/resume/funder-cv.ts
|
|
400
524
|
var FUNDER_CV_FORMATS = ["nih", "erc"];
|
|
401
525
|
var FUNDER_CV_ITEM_KINDS = ["publication", "presentation", "honor"];
|
|
402
526
|
var FUNDER_CV_GROUPS = {
|
|
@@ -413,7 +537,7 @@ var FUNDER_CV_GROUPS = {
|
|
|
413
537
|
function funderCvItemId(kind, rkey) {
|
|
414
538
|
return `${kind}:${rkey}`;
|
|
415
539
|
}
|
|
416
|
-
var ITEM_ID_RE = /^(publication|presentation|honor):[A-Za-z0-9._
|
|
540
|
+
var ITEM_ID_RE = /^(publication|presentation|honor):[A-Za-z0-9._~:/-]{1,512}$/;
|
|
417
541
|
function kindOf(id) {
|
|
418
542
|
const kind = id.slice(0, id.indexOf(":"));
|
|
419
543
|
return FUNDER_CV_ITEM_KINDS.includes(kind) ? kind : void 0;
|