@pranavraut033/ats-checker 0.1.0 → 0.2.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.d.mts +358 -1
- package/dist/index.d.ts +358 -1
- package/dist/index.js +631 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +619 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -715,6 +715,519 @@ var SuggestionEngine = class {
|
|
|
715
715
|
}
|
|
716
716
|
};
|
|
717
717
|
|
|
718
|
+
// src/llm/llm.budget.ts
|
|
719
|
+
var LLMBudgetManager = class {
|
|
720
|
+
constructor(limits) {
|
|
721
|
+
this.callCount = 0;
|
|
722
|
+
this.totalTokensUsed = 0;
|
|
723
|
+
this.limits = limits;
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Check if we can make a call with the given token estimate
|
|
727
|
+
* Throws if budget would be exceeded
|
|
728
|
+
*/
|
|
729
|
+
assertCanCall(requestedTokens) {
|
|
730
|
+
if (this.callCount >= this.limits.maxCalls) {
|
|
731
|
+
throw new Error(
|
|
732
|
+
`LLM call limit exceeded: ${this.callCount}/${this.limits.maxCalls} calls used`
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
if (requestedTokens > this.limits.maxTokensPerCall) {
|
|
736
|
+
throw new Error(
|
|
737
|
+
`Requested tokens ${requestedTokens} exceeds per-call limit ${this.limits.maxTokensPerCall}`
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
if (this.totalTokensUsed + requestedTokens > this.limits.maxTotalTokens) {
|
|
741
|
+
throw new Error(
|
|
742
|
+
`Total token budget exceeded: ${this.totalTokensUsed + requestedTokens}/${this.limits.maxTotalTokens}`
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Record actual token usage from a completed call
|
|
748
|
+
*/
|
|
749
|
+
recordUsage(tokensUsed) {
|
|
750
|
+
this.callCount += 1;
|
|
751
|
+
this.totalTokensUsed += tokensUsed;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Get current budget state
|
|
755
|
+
*/
|
|
756
|
+
getStats() {
|
|
757
|
+
return {
|
|
758
|
+
callsUsed: this.callCount,
|
|
759
|
+
callsRemaining: Math.max(0, this.limits.maxCalls - this.callCount),
|
|
760
|
+
tokensUsed: this.totalTokensUsed,
|
|
761
|
+
tokensRemaining: Math.max(0, this.limits.maxTotalTokens - this.totalTokensUsed),
|
|
762
|
+
totalCalls: this.limits.maxCalls,
|
|
763
|
+
totalTokens: this.limits.maxTotalTokens
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Check if budget is exhausted
|
|
768
|
+
*/
|
|
769
|
+
isExhausted() {
|
|
770
|
+
return this.callCount >= this.limits.maxCalls || this.totalTokensUsed >= this.limits.maxTotalTokens;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Reset budget (for testing)
|
|
774
|
+
*/
|
|
775
|
+
reset() {
|
|
776
|
+
this.callCount = 0;
|
|
777
|
+
this.totalTokensUsed = 0;
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
|
|
781
|
+
// src/llm/llm.manager.ts
|
|
782
|
+
var LLMManager = class {
|
|
783
|
+
constructor(config) {
|
|
784
|
+
this.warnings = [];
|
|
785
|
+
this.config = config;
|
|
786
|
+
this.client = config.client;
|
|
787
|
+
this.budgetManager = new LLMBudgetManager(config.limits);
|
|
788
|
+
this.timeoutMs = config.timeoutMs ?? 3e4;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Structured call to LLM with timeout and budget protection
|
|
792
|
+
*/
|
|
793
|
+
async callLLM(systemPrompt, userPrompt, schema, options = {}) {
|
|
794
|
+
try {
|
|
795
|
+
const estimatedTokens = this.estimateTokens(systemPrompt, userPrompt, options.requestedTokens);
|
|
796
|
+
try {
|
|
797
|
+
this.budgetManager.assertCanCall(estimatedTokens);
|
|
798
|
+
} catch (e) {
|
|
799
|
+
const msg = `LLM budget exhausted: ${e.message}`;
|
|
800
|
+
this.warnings.push(msg);
|
|
801
|
+
return { success: false, fallback: true, error: msg };
|
|
802
|
+
}
|
|
803
|
+
if (!this.isValidJsonSchema(schema)) {
|
|
804
|
+
const msg = "Invalid JSON schema provided";
|
|
805
|
+
this.warnings.push(msg);
|
|
806
|
+
return { success: false, fallback: true, error: msg };
|
|
807
|
+
}
|
|
808
|
+
const strictUserPrompt = `${userPrompt}
|
|
809
|
+
|
|
810
|
+
Return ONLY valid JSON matching the schema below.
|
|
811
|
+
No explanations. No markdown. No additional text.`;
|
|
812
|
+
const response = await Promise.race([
|
|
813
|
+
this.client.createCompletion({
|
|
814
|
+
model: options.useThinking ? this.config.models?.thinking || this.config.models?.default || "gpt-4o" : this.config.models?.default || "gpt-4o",
|
|
815
|
+
messages: [
|
|
816
|
+
{ role: "system", content: systemPrompt },
|
|
817
|
+
{ role: "user", content: strictUserPrompt }
|
|
818
|
+
],
|
|
819
|
+
max_tokens: options.requestedTokens || 2e3,
|
|
820
|
+
response_format: schema
|
|
821
|
+
}),
|
|
822
|
+
this.createTimeout(this.timeoutMs)
|
|
823
|
+
]);
|
|
824
|
+
if (!response || !response.content) {
|
|
825
|
+
return { success: false, fallback: true, error: "Empty response from LLM" };
|
|
826
|
+
}
|
|
827
|
+
let parsedContent;
|
|
828
|
+
try {
|
|
829
|
+
if (typeof response.content === "string") {
|
|
830
|
+
parsedContent = JSON.parse(response.content);
|
|
831
|
+
} else {
|
|
832
|
+
parsedContent = response.content;
|
|
833
|
+
}
|
|
834
|
+
} catch (e) {
|
|
835
|
+
const msg = `Invalid JSON in LLM response: ${e.message}`;
|
|
836
|
+
this.warnings.push(msg);
|
|
837
|
+
return { success: false, fallback: true, error: msg };
|
|
838
|
+
}
|
|
839
|
+
if (!this.validateAgainstSchema(parsedContent, schema)) {
|
|
840
|
+
const msg = "LLM response does not match schema";
|
|
841
|
+
this.warnings.push(msg);
|
|
842
|
+
return { success: false, fallback: true, error: msg };
|
|
843
|
+
}
|
|
844
|
+
const tokensUsed = response.usage?.total_tokens || estimatedTokens;
|
|
845
|
+
this.budgetManager.recordUsage(tokensUsed);
|
|
846
|
+
return {
|
|
847
|
+
success: true,
|
|
848
|
+
fallback: false,
|
|
849
|
+
data: parsedContent,
|
|
850
|
+
tokensUsed
|
|
851
|
+
};
|
|
852
|
+
} catch (e) {
|
|
853
|
+
const msg = `LLM call failed: ${e.message}`;
|
|
854
|
+
this.warnings.push(msg);
|
|
855
|
+
return { success: false, fallback: true, error: msg };
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Get list of warnings from LLM operations
|
|
860
|
+
*/
|
|
861
|
+
getWarnings() {
|
|
862
|
+
return [...this.warnings];
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Get budget stats
|
|
866
|
+
*/
|
|
867
|
+
getBudgetStats() {
|
|
868
|
+
return this.budgetManager.getStats();
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Check if features are enabled
|
|
872
|
+
*/
|
|
873
|
+
isFeatureEnabled(feature) {
|
|
874
|
+
return this.config.enable?.[feature] === true;
|
|
875
|
+
}
|
|
876
|
+
// ============ Private helpers ============
|
|
877
|
+
/**
|
|
878
|
+
* Create a timeout promise
|
|
879
|
+
*/
|
|
880
|
+
createTimeout(ms) {
|
|
881
|
+
return new Promise((_, reject) => {
|
|
882
|
+
globalThis.setTimeout(() => reject(new Error(`LLM call timeout after ${ms}ms`)), ms);
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Estimate tokens for a call (rough approximation)
|
|
887
|
+
* 1 token ≈ 4 characters average
|
|
888
|
+
*/
|
|
889
|
+
estimateTokens(systemPrompt, userPrompt, requestedTokens) {
|
|
890
|
+
if (requestedTokens) {
|
|
891
|
+
return requestedTokens;
|
|
892
|
+
}
|
|
893
|
+
const totalChars = systemPrompt.length + userPrompt.length;
|
|
894
|
+
const estimatedInputTokens = Math.ceil(totalChars / 4);
|
|
895
|
+
return estimatedInputTokens + Math.ceil(estimatedInputTokens / 2);
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Validate that schema looks like valid JSON schema
|
|
899
|
+
*/
|
|
900
|
+
isValidJsonSchema(schema) {
|
|
901
|
+
return schema && schema.type === "object" && !!(schema.properties || schema.required);
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Simple schema validation - check required fields exist
|
|
905
|
+
*/
|
|
906
|
+
validateAgainstSchema(data, schema) {
|
|
907
|
+
if (typeof data !== "object" || data === null) {
|
|
908
|
+
return false;
|
|
909
|
+
}
|
|
910
|
+
const obj = data;
|
|
911
|
+
if (schema.required) {
|
|
912
|
+
for (const field of schema.required) {
|
|
913
|
+
if (!(field in obj)) {
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
return true;
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
// src/llm/llm.schemas.ts
|
|
923
|
+
var skillNormalizationSchema = {
|
|
924
|
+
type: "object",
|
|
925
|
+
properties: {
|
|
926
|
+
canonicalSkills: {
|
|
927
|
+
type: "array",
|
|
928
|
+
items: {
|
|
929
|
+
type: "object",
|
|
930
|
+
properties: {
|
|
931
|
+
input: {
|
|
932
|
+
type: "string",
|
|
933
|
+
description: "Original skill name from input"
|
|
934
|
+
},
|
|
935
|
+
normalized: {
|
|
936
|
+
type: "string",
|
|
937
|
+
description: "Canonical/normalized skill name"
|
|
938
|
+
},
|
|
939
|
+
confidence: {
|
|
940
|
+
type: "number",
|
|
941
|
+
description: "Confidence score 0-1 that this is correct"
|
|
942
|
+
}
|
|
943
|
+
},
|
|
944
|
+
required: ["input", "normalized"]
|
|
945
|
+
},
|
|
946
|
+
description: "Array of skill normalizations"
|
|
947
|
+
}
|
|
948
|
+
},
|
|
949
|
+
required: ["canonicalSkills"]
|
|
950
|
+
};
|
|
951
|
+
var sectionClassificationSchema = {
|
|
952
|
+
type: "object",
|
|
953
|
+
properties: {
|
|
954
|
+
sections: {
|
|
955
|
+
type: "array",
|
|
956
|
+
items: {
|
|
957
|
+
type: "object",
|
|
958
|
+
properties: {
|
|
959
|
+
header: {
|
|
960
|
+
type: "string",
|
|
961
|
+
description: "The section header text"
|
|
962
|
+
},
|
|
963
|
+
classification: {
|
|
964
|
+
type: "string",
|
|
965
|
+
enum: ["summary", "experience", "skills", "education", "projects", "certifications", "other"],
|
|
966
|
+
description: "Classified section type"
|
|
967
|
+
},
|
|
968
|
+
confidence: {
|
|
969
|
+
type: "number",
|
|
970
|
+
description: "Confidence 0-1 in the classification"
|
|
971
|
+
}
|
|
972
|
+
},
|
|
973
|
+
required: ["header", "classification"]
|
|
974
|
+
},
|
|
975
|
+
description: "Array of section classifications"
|
|
976
|
+
}
|
|
977
|
+
},
|
|
978
|
+
required: ["sections"]
|
|
979
|
+
};
|
|
980
|
+
var suggestionEnhancementSchema = {
|
|
981
|
+
type: "object",
|
|
982
|
+
properties: {
|
|
983
|
+
suggestions: {
|
|
984
|
+
type: "array",
|
|
985
|
+
items: {
|
|
986
|
+
type: "object",
|
|
987
|
+
properties: {
|
|
988
|
+
original: {
|
|
989
|
+
type: "string",
|
|
990
|
+
description: "Original suggestion from deterministic engine"
|
|
991
|
+
},
|
|
992
|
+
enhanced: {
|
|
993
|
+
type: "string",
|
|
994
|
+
description: "Improved phrasing of the suggestion"
|
|
995
|
+
},
|
|
996
|
+
actionable: {
|
|
997
|
+
type: "boolean",
|
|
998
|
+
description: "Whether the suggestion is concrete and actionable"
|
|
999
|
+
}
|
|
1000
|
+
},
|
|
1001
|
+
required: ["original", "enhanced"]
|
|
1002
|
+
},
|
|
1003
|
+
description: "Array of enhanced suggestions"
|
|
1004
|
+
}
|
|
1005
|
+
},
|
|
1006
|
+
required: ["suggestions"]
|
|
1007
|
+
};
|
|
1008
|
+
var jdClarificationSchema = {
|
|
1009
|
+
type: "object",
|
|
1010
|
+
properties: {
|
|
1011
|
+
implicitSkills: {
|
|
1012
|
+
type: "array",
|
|
1013
|
+
items: { type: "string" },
|
|
1014
|
+
description: "Skills implied but not explicitly mentioned"
|
|
1015
|
+
},
|
|
1016
|
+
implicitExperience: {
|
|
1017
|
+
type: "object",
|
|
1018
|
+
properties: {
|
|
1019
|
+
minYears: {
|
|
1020
|
+
type: "number",
|
|
1021
|
+
description: "Inferred minimum experience years"
|
|
1022
|
+
},
|
|
1023
|
+
domains: {
|
|
1024
|
+
type: "array",
|
|
1025
|
+
items: { type: "string" },
|
|
1026
|
+
description: "Industry domains implied"
|
|
1027
|
+
}
|
|
1028
|
+
},
|
|
1029
|
+
description: "Inferred experience requirements"
|
|
1030
|
+
},
|
|
1031
|
+
clarityScore: {
|
|
1032
|
+
type: "number",
|
|
1033
|
+
description: "0-1 score indicating JD clarity"
|
|
1034
|
+
}
|
|
1035
|
+
},
|
|
1036
|
+
required: ["implicitSkills", "clarityScore"]
|
|
1037
|
+
};
|
|
1038
|
+
var validationSchema = {
|
|
1039
|
+
type: "object",
|
|
1040
|
+
properties: {
|
|
1041
|
+
valid: {
|
|
1042
|
+
type: "boolean",
|
|
1043
|
+
description: "Whether the input is valid"
|
|
1044
|
+
},
|
|
1045
|
+
message: {
|
|
1046
|
+
type: "string",
|
|
1047
|
+
description: "Validation message"
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
required: ["valid"]
|
|
1051
|
+
};
|
|
1052
|
+
var LLMSchemas = {
|
|
1053
|
+
skillNormalization: skillNormalizationSchema,
|
|
1054
|
+
sectionClassification: sectionClassificationSchema,
|
|
1055
|
+
suggestionEnhancement: suggestionEnhancementSchema,
|
|
1056
|
+
jdClarification: jdClarificationSchema,
|
|
1057
|
+
validation: validationSchema
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
// src/llm/llm.prompts.ts
|
|
1061
|
+
var LLMPrompts = {
|
|
1062
|
+
/**
|
|
1063
|
+
* System prompt for skill normalization
|
|
1064
|
+
*/
|
|
1065
|
+
skillNormalizationSystem: `You are a technical skill normalization expert.
|
|
1066
|
+
Your task is to normalize and canonicalize technical skill names.
|
|
1067
|
+
Handle aliases, abbreviations, and variations.
|
|
1068
|
+
Be conservative - only group skills that are genuinely synonymous.
|
|
1069
|
+
Return ONLY valid JSON.`,
|
|
1070
|
+
/**
|
|
1071
|
+
* User prompt for skill normalization
|
|
1072
|
+
*/
|
|
1073
|
+
skillNormalizationUser: (skills) => `Normalize these skills:
|
|
1074
|
+
${skills.map((s) => `- ${s}`).join("\n")}
|
|
1075
|
+
|
|
1076
|
+
Return the canonical names with confidence scores.`,
|
|
1077
|
+
/**
|
|
1078
|
+
* System prompt for section classification
|
|
1079
|
+
*/
|
|
1080
|
+
sectionClassificationSystem: `You are a resume section classifier.
|
|
1081
|
+
Classify ambiguous section headers into standard resume categories.
|
|
1082
|
+
Be strict - if uncertain, classify as "other".
|
|
1083
|
+
Provide confidence scores.
|
|
1084
|
+
Return ONLY valid JSON.`,
|
|
1085
|
+
/**
|
|
1086
|
+
* User prompt for section classification
|
|
1087
|
+
*/
|
|
1088
|
+
sectionClassificationUser: (headers) => `Classify these resume section headers:
|
|
1089
|
+
${headers.map((h) => `- "${h}"`).join("\n")}
|
|
1090
|
+
|
|
1091
|
+
Use categories: summary, experience, skills, education, projects, certifications, other.`,
|
|
1092
|
+
/**
|
|
1093
|
+
* System prompt for suggestion enhancement
|
|
1094
|
+
*/
|
|
1095
|
+
suggestionEnhancementSystem: `You are a resume advice expert.
|
|
1096
|
+
Improve suggestions to be more actionable and specific.
|
|
1097
|
+
Maintain the core message but make it more concrete.
|
|
1098
|
+
Return ONLY valid JSON.`,
|
|
1099
|
+
/**
|
|
1100
|
+
* User prompt for suggestion enhancement
|
|
1101
|
+
*/
|
|
1102
|
+
suggestionEnhancementUser: (suggestions) => `Enhance these suggestions for clarity and actionability:
|
|
1103
|
+
${suggestions.map((s) => `- ${s}`).join("\n")}
|
|
1104
|
+
|
|
1105
|
+
Make them specific and measurable where possible.`,
|
|
1106
|
+
/**
|
|
1107
|
+
* System prompt for JD clarification
|
|
1108
|
+
*/
|
|
1109
|
+
jdClarificationSystem: `You are a job description analyzer.
|
|
1110
|
+
Extract implicit requirements not explicitly stated.
|
|
1111
|
+
Be conservative - stick to reasonable inferences.
|
|
1112
|
+
Rate the clarity of the job description.
|
|
1113
|
+
Return ONLY valid JSON.`,
|
|
1114
|
+
/**
|
|
1115
|
+
* User prompt for JD clarification
|
|
1116
|
+
*/
|
|
1117
|
+
jdClarificationUser: (jd) => `Analyze this job description for implicit requirements:
|
|
1118
|
+
|
|
1119
|
+
${jd}
|
|
1120
|
+
|
|
1121
|
+
What skills are implied but not explicitly mentioned?
|
|
1122
|
+
What experience domains are indicated?
|
|
1123
|
+
How clear is this job description (0-1)?`
|
|
1124
|
+
};
|
|
1125
|
+
function createPrompt(systemBase, userBuilder, input) {
|
|
1126
|
+
return {
|
|
1127
|
+
system: systemBase,
|
|
1128
|
+
user: userBuilder(input)
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// src/llm/llm.adapters.ts
|
|
1133
|
+
function adaptSkillNormalizationResponse(data) {
|
|
1134
|
+
if (!data || typeof data !== "object") {
|
|
1135
|
+
return [];
|
|
1136
|
+
}
|
|
1137
|
+
const obj = data;
|
|
1138
|
+
const canonicalSkills = obj.canonicalSkills;
|
|
1139
|
+
if (!Array.isArray(canonicalSkills)) {
|
|
1140
|
+
return [];
|
|
1141
|
+
}
|
|
1142
|
+
const results = [];
|
|
1143
|
+
for (const item of canonicalSkills) {
|
|
1144
|
+
if (typeof item !== "object" || item === null) continue;
|
|
1145
|
+
const skill = item;
|
|
1146
|
+
const input = skill.input;
|
|
1147
|
+
const normalized = skill.normalized;
|
|
1148
|
+
const confidence = typeof skill.confidence === "number" ? skill.confidence : void 0;
|
|
1149
|
+
if (input && normalized) {
|
|
1150
|
+
results.push({ input, normalized, confidence });
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
return results;
|
|
1154
|
+
}
|
|
1155
|
+
function adaptSectionClassificationResponse(data) {
|
|
1156
|
+
if (!data || typeof data !== "object") {
|
|
1157
|
+
return [];
|
|
1158
|
+
}
|
|
1159
|
+
const obj = data;
|
|
1160
|
+
const sections = obj.sections;
|
|
1161
|
+
if (!Array.isArray(sections)) {
|
|
1162
|
+
return [];
|
|
1163
|
+
}
|
|
1164
|
+
const results = [];
|
|
1165
|
+
for (const item of sections) {
|
|
1166
|
+
if (typeof item !== "object" || item === null) continue;
|
|
1167
|
+
const section = item;
|
|
1168
|
+
const header = section.header;
|
|
1169
|
+
const classification = section.classification;
|
|
1170
|
+
const confidence = typeof section.confidence === "number" ? section.confidence : void 0;
|
|
1171
|
+
if (header && classification) {
|
|
1172
|
+
results.push({ header, classification, confidence });
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
return results;
|
|
1176
|
+
}
|
|
1177
|
+
function adaptSuggestionEnhancementResponse(data) {
|
|
1178
|
+
if (!data || typeof data !== "object") {
|
|
1179
|
+
return [];
|
|
1180
|
+
}
|
|
1181
|
+
const obj = data;
|
|
1182
|
+
const suggestions = obj.suggestions;
|
|
1183
|
+
if (!Array.isArray(suggestions)) {
|
|
1184
|
+
return [];
|
|
1185
|
+
}
|
|
1186
|
+
const results = [];
|
|
1187
|
+
for (const item of suggestions) {
|
|
1188
|
+
if (typeof item !== "object" || item === null) continue;
|
|
1189
|
+
const suggestion = item;
|
|
1190
|
+
const original = suggestion.original;
|
|
1191
|
+
const enhanced = suggestion.enhanced;
|
|
1192
|
+
const actionable = typeof suggestion.actionable === "boolean" ? suggestion.actionable : void 0;
|
|
1193
|
+
if (original && enhanced) {
|
|
1194
|
+
results.push({ original, enhanced, actionable });
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
return results;
|
|
1198
|
+
}
|
|
1199
|
+
function adaptJdClarificationResponse(data) {
|
|
1200
|
+
if (!data || typeof data !== "object") {
|
|
1201
|
+
return { implicitSkills: [] };
|
|
1202
|
+
}
|
|
1203
|
+
const obj = data;
|
|
1204
|
+
const implicitSkills = Array.isArray(obj.implicitSkills) ? obj.implicitSkills.filter((s) => typeof s === "string") : [];
|
|
1205
|
+
const implicitExperience = obj.implicitExperience && typeof obj.implicitExperience === "object" ? obj.implicitExperience : void 0;
|
|
1206
|
+
const minYears = implicitExperience && typeof implicitExperience.minYears === "number" ? implicitExperience.minYears : void 0;
|
|
1207
|
+
const domains = implicitExperience && Array.isArray(implicitExperience.domains) ? implicitExperience.domains : void 0;
|
|
1208
|
+
const clarityScore = typeof obj.clarityScore === "number" ? obj.clarityScore : void 0;
|
|
1209
|
+
return {
|
|
1210
|
+
implicitSkills,
|
|
1211
|
+
implicitExperience: minYears || domains ? { minYears, domains } : void 0,
|
|
1212
|
+
clarityScore
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
function safeExtractString(obj, key) {
|
|
1216
|
+
if (typeof obj !== "object" || obj === null) return void 0;
|
|
1217
|
+
const value = obj[key];
|
|
1218
|
+
return typeof value === "string" ? value : void 0;
|
|
1219
|
+
}
|
|
1220
|
+
function safeExtractArray(obj, key) {
|
|
1221
|
+
if (typeof obj !== "object" || obj === null) return [];
|
|
1222
|
+
const value = obj[key];
|
|
1223
|
+
return Array.isArray(value) ? value : [];
|
|
1224
|
+
}
|
|
1225
|
+
function safeExtractNumber(obj, key) {
|
|
1226
|
+
if (typeof obj !== "object" || obj === null) return void 0;
|
|
1227
|
+
const value = obj[key];
|
|
1228
|
+
return typeof value === "number" ? value : void 0;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
718
1231
|
// src/index.ts
|
|
719
1232
|
function analyzeResume(input) {
|
|
720
1233
|
const resolvedConfig = resolveConfig(input.config ?? {});
|
|
@@ -736,6 +1249,15 @@ function analyzeResume(input) {
|
|
|
736
1249
|
score: scoring,
|
|
737
1250
|
ruleWarnings: ruleResult.warnings
|
|
738
1251
|
});
|
|
1252
|
+
let suggestions = suggestionResult.suggestions;
|
|
1253
|
+
const llmWarnings = [];
|
|
1254
|
+
if (input.llm && suggestionResult.suggestions.length > 0) {
|
|
1255
|
+
const llmResult = enhanceSuggestionsWithLLM(input.llm, suggestionResult.suggestions);
|
|
1256
|
+
if (llmResult.success) {
|
|
1257
|
+
suggestions = llmResult.enhancedSuggestions || suggestions;
|
|
1258
|
+
}
|
|
1259
|
+
llmWarnings.push(...llmResult.warnings);
|
|
1260
|
+
}
|
|
739
1261
|
const finalScore = clamp(scoring.score - ruleResult.totalPenalty, 0, 100);
|
|
740
1262
|
return {
|
|
741
1263
|
score: finalScore,
|
|
@@ -743,11 +1265,105 @@ function analyzeResume(input) {
|
|
|
743
1265
|
matchedKeywords: scoring.matchedKeywords,
|
|
744
1266
|
missingKeywords: scoring.missingKeywords,
|
|
745
1267
|
overusedKeywords: scoring.overusedKeywords,
|
|
746
|
-
suggestions
|
|
747
|
-
warnings: suggestionResult.warnings
|
|
1268
|
+
suggestions,
|
|
1269
|
+
warnings: [...suggestionResult.warnings, ...llmWarnings]
|
|
748
1270
|
};
|
|
749
1271
|
}
|
|
1272
|
+
function enhanceSuggestionsWithLLM(config, suggestions) {
|
|
1273
|
+
if (!config.enable?.suggestions) {
|
|
1274
|
+
return { success: false, warnings: [] };
|
|
1275
|
+
}
|
|
1276
|
+
const warnings = [];
|
|
1277
|
+
try {
|
|
1278
|
+
const llmManager = new LLMManager(config);
|
|
1279
|
+
warnings.push(
|
|
1280
|
+
"LLM suggestion enhancement skipped - use async analyzeResumeAsync for LLM features"
|
|
1281
|
+
);
|
|
1282
|
+
return { success: false, warnings };
|
|
1283
|
+
} catch (e) {
|
|
1284
|
+
warnings.push(`Failed to enhance suggestions: ${e.message}`);
|
|
1285
|
+
return { success: false, warnings };
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
async function analyzeResumeAsync(input) {
|
|
1289
|
+
const resolvedConfig = resolveConfig(input.config ?? {});
|
|
1290
|
+
const parsedResume = parseResume(input.resumeText, resolvedConfig);
|
|
1291
|
+
const parsedJob = parseJobDescription(input.jobDescription, resolvedConfig);
|
|
1292
|
+
const scoring = calculateScore(parsedResume, parsedJob, resolvedConfig);
|
|
1293
|
+
const ruleEngine = new RuleEngine(resolvedConfig);
|
|
1294
|
+
const ruleResult = ruleEngine.evaluate({
|
|
1295
|
+
resume: parsedResume,
|
|
1296
|
+
job: parsedJob,
|
|
1297
|
+
breakdown: scoring.breakdown,
|
|
1298
|
+
matchedKeywords: scoring.matchedKeywords,
|
|
1299
|
+
overusedKeywords: scoring.overusedKeywords
|
|
1300
|
+
});
|
|
1301
|
+
const suggestionEngine = new SuggestionEngine();
|
|
1302
|
+
const suggestionResult = suggestionEngine.generate({
|
|
1303
|
+
resume: parsedResume,
|
|
1304
|
+
job: parsedJob,
|
|
1305
|
+
score: scoring,
|
|
1306
|
+
ruleWarnings: ruleResult.warnings
|
|
1307
|
+
});
|
|
1308
|
+
let suggestions = suggestionResult.suggestions;
|
|
1309
|
+
const llmWarnings = [];
|
|
1310
|
+
if (input.llm && suggestionResult.suggestions.length > 0) {
|
|
1311
|
+
const llmResult = await enhanceSuggestionsWithLLMAsync(
|
|
1312
|
+
input.llm,
|
|
1313
|
+
suggestionResult.suggestions
|
|
1314
|
+
);
|
|
1315
|
+
if (llmResult.success) {
|
|
1316
|
+
suggestions = llmResult.enhancedSuggestions || suggestions;
|
|
1317
|
+
}
|
|
1318
|
+
llmWarnings.push(...llmResult.warnings);
|
|
1319
|
+
}
|
|
1320
|
+
const finalScore = clamp(scoring.score - ruleResult.totalPenalty, 0, 100);
|
|
1321
|
+
return {
|
|
1322
|
+
score: finalScore,
|
|
1323
|
+
breakdown: scoring.breakdown,
|
|
1324
|
+
matchedKeywords: scoring.matchedKeywords,
|
|
1325
|
+
missingKeywords: scoring.missingKeywords,
|
|
1326
|
+
overusedKeywords: scoring.overusedKeywords,
|
|
1327
|
+
suggestions,
|
|
1328
|
+
warnings: [...suggestionResult.warnings, ...llmWarnings]
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
async function enhanceSuggestionsWithLLMAsync(config, suggestions) {
|
|
1332
|
+
if (!config.enable?.suggestions) {
|
|
1333
|
+
return { success: false, warnings: [] };
|
|
1334
|
+
}
|
|
1335
|
+
const warnings = [];
|
|
1336
|
+
try {
|
|
1337
|
+
const llmManager = new LLMManager(config);
|
|
1338
|
+
const result = await llmManager.callLLM(
|
|
1339
|
+
LLMPrompts.suggestionEnhancementSystem,
|
|
1340
|
+
LLMPrompts.suggestionEnhancementUser(suggestions),
|
|
1341
|
+
LLMSchemas.suggestionEnhancement,
|
|
1342
|
+
{ requestedTokens: 2e3 }
|
|
1343
|
+
);
|
|
1344
|
+
if (!result.success || !result.data) {
|
|
1345
|
+
if (result.error) {
|
|
1346
|
+
warnings.push(`LLM suggestion enhancement failed: ${result.error}`);
|
|
1347
|
+
}
|
|
1348
|
+
return { success: false, warnings: [...warnings, ...llmManager.getWarnings()] };
|
|
1349
|
+
}
|
|
1350
|
+
const enhanced = adaptSuggestionEnhancementResponse(result.data);
|
|
1351
|
+
const enhancedSuggestions = enhanced.filter((e) => e.actionable !== false).map((e) => e.enhanced);
|
|
1352
|
+
if (enhancedSuggestions.length === 0) {
|
|
1353
|
+
warnings.push("LLM returned no actionable enhanced suggestions");
|
|
1354
|
+
return { success: false, warnings: [...warnings, ...llmManager.getWarnings()] };
|
|
1355
|
+
}
|
|
1356
|
+
return {
|
|
1357
|
+
success: true,
|
|
1358
|
+
enhancedSuggestions,
|
|
1359
|
+
warnings: llmManager.getWarnings()
|
|
1360
|
+
};
|
|
1361
|
+
} catch (e) {
|
|
1362
|
+
warnings.push(`Unexpected error in LLM enhancement: ${e.message}`);
|
|
1363
|
+
return { success: false, warnings };
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
750
1366
|
|
|
751
|
-
export { analyzeResume, defaultProfiles, defaultSkillAliases };
|
|
1367
|
+
export { LLMBudgetManager, LLMManager, LLMPrompts, LLMSchemas, adaptJdClarificationResponse, adaptSectionClassificationResponse, adaptSkillNormalizationResponse, adaptSuggestionEnhancementResponse, analyzeResume, analyzeResumeAsync, createPrompt, defaultProfiles, defaultSkillAliases, safeExtractArray, safeExtractNumber, safeExtractString };
|
|
752
1368
|
//# sourceMappingURL=index.mjs.map
|
|
753
1369
|
//# sourceMappingURL=index.mjs.map
|