/// <summary> /// Gets the severity level of the most severe resume quality finding. One of: /// <br/> <see cref="ResumeQualityLevel.FatalProblem"/> /// <br/> <see cref="ResumeQualityLevel.MajorIssue"/> /// <br/> <see cref="ResumeQualityLevel.DataMissing"/> /// <br/> <see cref="ResumeQualityLevel.SuggestedImprovement"/> /// <br/> null /// </summary> public static ResumeQualityLevel GetMostSevereResumeQualityFinding(this ParseResumeResponseExtensions exts) { var fatalErrors = exts.Response.Value.ResumeData?.ResumeMetadata?.ResumeQuality?.Where(r => r.Level == ResumeQualityLevel.FatalProblem.Value); var majorProblems = exts.Response.Value.ResumeData?.ResumeMetadata?.ResumeQuality?.Where(r => r.Level == ResumeQualityLevel.MajorIssue.Value); var dataMissing = exts.Response.Value.ResumeData?.ResumeMetadata?.ResumeQuality?.Where(r => r.Level == ResumeQualityLevel.DataMissing.Value); var improvements = exts.Response.Value.ResumeData?.ResumeMetadata?.ResumeQuality?.Where(r => r.Level == ResumeQualityLevel.SuggestedImprovement.Value); if (fatalErrors != null && fatalErrors.Any()) { return(ResumeQualityLevel.FatalProblem); } if (majorProblems != null && majorProblems.Any()) { return(ResumeQualityLevel.MajorIssue); } if (dataMissing != null && dataMissing.Any()) { return(ResumeQualityLevel.DataMissing); } if (improvements != null && improvements.Any()) { return(ResumeQualityLevel.SuggestedImprovement); } return(null);//no issues found (amazing) }
/// <summary> /// Gets a flat list of skills that this candidate has. For more detailed data, use /// the tree-like <see cref="Models.Resume.ParsedResume.SkillsData"/> property in /// the <see cref="Models.API.Parsing.ParseResumeResponseValue.ResumeData"/> /// </summary> public static IEnumerable <string> GetSkillNames(this ParseResumeResponseExtensions exts) { List <ResumeTaxonomyRoot> roots = exts.Response.Value.ResumeData?.SkillsData; List <string> skills = new List <string>(); if (roots != null && roots.Count > 0) { foreach (ResumeTaxonomyRoot root in roots.Where(r => r.Taxonomies != null)) { foreach (ResumeTaxonomy taxo in root.Taxonomies.Where(t => t.SubTaxonomies != null)) { foreach (ResumeSubTaxonomy subtax in taxo.SubTaxonomies.Where(st => st.Skills != null)) { foreach (ResumeSkill skill in subtax.Skills) { skills.Add(skill.Name); if (skill.Variations != null && skill.Variations.Count > 0) { skills.AddRange(skill.Variations.Select(s => s.Name)); } } } } } } return(skills); }
/// <summary> /// Gets the age of the resume, if it has a RevisionDate. Otherwise <see cref="TimeSpan.MaxValue"/> /// </summary> public static TimeSpan GetResumeAge(this ParseResumeResponseExtensions exts) { DateTime revDate = exts.GetDocumentLastModified(); if (revDate == DateTime.MinValue) { return(TimeSpan.MaxValue); } return(DateTime.UtcNow - revDate); }
/// <summary> /// Gets a list of taxonomies and their respective 'PercentOfOverall' value which represents how /// concentrated the candidate's skills were in any given taxonomy/industry /// </summary> public static List <KeyValuePair <string, int> > GetTaxonomiesPercentages(this ParseResumeResponseExtensions exts) { Dictionary <string, int> taxos = new Dictionary <string, int>(); List <ResumeTaxonomyRoot> roots = exts.Response.Value.ResumeData?.SkillsData; if (roots != null && roots.Count > 0) { foreach (ResumeTaxonomyRoot root in roots.Where(r => r.Taxonomies != null)) { foreach (ResumeTaxonomy taxo in root.Taxonomies) { taxos.Add(taxo.Name, taxo.PercentOfOverall); } } } return(taxos.OrderBy(kvp => kvp.Value).ToList()); }
/// <summary> /// Gets the full text for a specific section in <see cref="ResumeMetadata.FoundSections"/>. /// </summary> /// <param name="exts"></param> /// <param name="section">The section to get the text for</param> public static string GetSectionText(this ParseResumeResponseExtensions exts, ResumeSection section) { if (section == null) { return(null); } string[] lines = exts.Response.Value.ResumeData?.ResumeMetadata?.PlainText?.Split('\n'); if (lines != null && lines.Length > section.FirstLineNumber && lines.Length > section.LastLineNumber) { return(string.Join("\n", lines .Skip(section.FirstLineNumber) .Take((section.LastLineNumber - section.FirstLineNumber) + 1))); } return(null); }
/// <summary> /// Checks if Sovren found any possible problems in the converted text of the resume (prior to parsing). /// <br/>For more info, see <see href="https://docs.sovren.com/#document-conversion-result-codes"/> /// </summary> public static bool HasConversionWarning(this ParseResumeResponseExtensions exts) { string code = exts.Response.Value.ConversionMetadata?.OutputValidityCode; switch (code) { case "ovProbableGarbageInText": case "ovUnknown": case "ovAvgWordLengthGreaterThan20": case "ovAvgWordLengthLessThan4": case "ovTooFewLineBreaks": case "ovLinesSeemTooShort": case "ovTruncated": return(true); default: return(false); } }
/// <summary> /// Gets the current job, or the first job marked as 'current' if there are more than one /// </summary> public static Position GetCurrentJob(this ParseResumeResponseExtensions exts) { //if more than 1 'current', report the one w/ the smallest date range (most recent start date) // if the same, report the one w/ co name and title // if both have, report first one IEnumerable <Position> currentJobs = exts.Response.Value.ResumeData?.EmploymentHistory?.Positions?.Where(p => p.IsCurrent); if (currentJobs == null || currentJobs.Count() == 0) { return(null); } else if (currentJobs.Count() == 1) { //this is the easy case, only 1 job that has 'xx\xxxx - current' on it return(currentJobs.First()); } else { //there are 2+ jobs listed as 'current', we will be smart about which one to report DateTime maxStartDate = currentJobs.Max(j => j.StartDate.Date); IEnumerable <Position> jobsWithMaxStartDate = currentJobs.Where(j => j.StartDate.Date == maxStartDate); IEnumerable <Position> jobsWithCoNameAndTitle = jobsWithMaxStartDate .Where(j => !string.IsNullOrWhiteSpace(j.Employer?.Name?.Normalized) && !string.IsNullOrWhiteSpace(j.JobTitle?.Raw)); if (jobsWithMaxStartDate.Count() == 1 || jobsWithCoNameAndTitle.Count() == 0) { //there is only 1 job w/ a recent start date, or there are 2+ but none have good data return(jobsWithMaxStartDate.First()); } else { //there are 2+ jobs w/ a recent start date, return one that has company name and title (good data) return(jobsWithCoNameAndTitle.First()); } } }
/// <summary> /// Gets a flat list of skills that this candidate has experience with in the last X amount of time. /// For more detailed data, use the tree-like <see cref="Models.Resume.ParsedResume.SkillsData"/> /// property in the <see cref="Models.API.Parsing.ParseResumeResponseValue.ResumeData"/> /// </summary> /// <param name="exts"></param> /// <param name="usedSince">The skill must have been used in a job after this date to be returned</param> public static IEnumerable <string> GetRecentSkills(this ParseResumeResponseExtensions exts, DateTime usedSince) { List <ResumeTaxonomyRoot> roots = exts.Response.Value.ResumeData?.SkillsData; List <string> skills = new List <string>(); if (roots != null && roots.Count > 0) { foreach (ResumeTaxonomyRoot root in roots.Where(r => r.Taxonomies != null)) { foreach (ResumeTaxonomy taxo in root.Taxonomies.Where(t => t.SubTaxonomies != null)) { foreach (ResumeSubTaxonomy subtax in taxo.SubTaxonomies.Where(st => st.Skills != null)) { foreach (ResumeSkill skill in subtax.Skills) { if (skill.LastUsed != null && skill.LastUsed.Value >= usedSince) { skills.Add(skill.Name); } if (skill.Variations != null && skill.Variations.Count > 0) { foreach (ResumeSkillVariation variation in skill.Variations) { if (variation.LastUsed != null && variation.LastUsed.Value >= usedSince) { skills.Add(variation.Name); } } } } } } } } return(skills); }
/// <summary> /// Gets a flat list of skills that this candidate has experience with and the associated amount of experience (in months). /// For more detailed data, use the tree-like <see cref="Models.Resume.ParsedResume.SkillsData"/> /// property in the <see cref="Models.API.Parsing.ParseResumeResponseValue.ResumeData"/> /// </summary> public static IEnumerable <KeyValuePair <string, int> > GetSkillsAndMonthsExperience(this ParseResumeResponseExtensions exts) { List <ResumeTaxonomyRoot> roots = exts.Response.Value.ResumeData?.SkillsData; List <KeyValuePair <string, int> > skills = new List <KeyValuePair <string, int> >(); if (roots != null && roots.Count > 0) { foreach (ResumeTaxonomyRoot root in roots.Where(r => r.Taxonomies != null)) { foreach (ResumeTaxonomy taxo in root.Taxonomies.Where(t => t.SubTaxonomies != null)) { foreach (ResumeSubTaxonomy subtax in taxo.SubTaxonomies.Where(st => st.Skills != null)) { foreach (ResumeSkill skill in subtax.Skills) { if (skill.MonthsExperience != null) { skills.Add(new KeyValuePair <string, int>(skill.Name, skill.MonthsExperience.Value)); } if (skill.Variations != null && skill.Variations.Count > 0) { foreach (ResumeSkillVariation variation in skill.Variations) { if (variation.MonthsExperience != null) { skills.Add(new KeyValuePair <string, int>(variation.Name, variation.MonthsExperience.Value)); } } } } } } } } return(skills); }
/// <summary> /// Gets whether or not security clearance was found on the resume /// </summary> public static bool HasSecurityClearance(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.SecurityCredentials?.Any() ?? false); }
/// <summary> /// Gets the candidate's gender (if found) or <see langword="null"/> /// </summary> public static string GetGender(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.PersonalAttributes?.Gender); }
/// <summary> /// Returns the phone numbers or <see langword="null"/> /// </summary> public static IEnumerable <string> GetPhoneNumbers(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.ContactInformation?.Telephones?.Select(t => t.Normalized)); }
/// <summary> /// Gets the candidate's mother tongue (if found) or <see langword="null"/> /// </summary> public static string GetMotherTongue(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.PersonalAttributes?.MotherTongue); }
/// <summary> /// Returns the address or <see langword="null"/> /// </summary> public static Location GetAddress(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.ContactInformation?.Location); }
/// <summary> /// Returns the contact information or <see langword="null"/> /// </summary> public static ContactInformation GetContactInfo(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.ContactInformation); }
/// <summary> /// Save the resume to disk using UTF-8 encoding /// </summary> /// <param name="exts"></param> /// <param name="piiRedacted"><see langword="true"/> to save the redacted version of the resume, otherwise <see langword="false"/></param> /// <param name="filePath">The file to save to</param> /// <param name="formatted"><see langword="true"/> for pretty-printing</param> public static void SaveResumeJsonToFile(this ParseResumeResponseExtensions exts, string filePath, bool formatted, bool piiRedacted) { ParsedResume resume = piiRedacted ? exts.Response?.Value?.RedactedResumeData : exts.Response?.Value?.ResumeData; resume?.ToFile(filePath, formatted); }
/// <summary> /// Gets the ISO 639-1 language code for the language the resume was written in /// </summary> public static string GetLanguage(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.ResumeMetadata?.DocumentLanguage); }
/// <summary> /// Use this to get the resume as a JSON string (to save to disk or other data storage). /// <br/>NOTE: be sure to save with UTF-8 encoding! /// </summary> /// <param name="exts"></param> /// <param name="piiRedacted"><see langword="true"/> for the redacted version of the resume, otherwise <see langword="false"/></param> /// <param name="formatted"><see langword="true"/> for pretty-printing</param> public static string GetResumeAsJsonString(this ParseResumeResponseExtensions exts, bool formatted, bool piiRedacted) { ParsedResume resume = piiRedacted ? exts.Response?.Value?.RedactedResumeData : exts.Response?.Value?.ResumeData; return(resume?.ToJson(formatted)); }
/// <summary> /// Gets the candidate's marital status (if found) or <see langword="null"/> /// </summary> public static string GetMaritalStatus(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.PersonalAttributes?.MaritalStatus); }
/// <summary> /// Checks if the resume timed out during parsing. If <see langword="true"/>, the data in the resume may be incomplete /// </summary> public static bool DidTimeout(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ParsingMetadata?.TimedOut ?? false); }
/// <summary> /// Gets the candidate's father's name (if found) or <see langword="null"/> /// </summary> public static string GetFathersName(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.PersonalAttributes?.FathersName); }
/// <summary> /// Gets the candidate's driving license (if found) or <see langword="null"/> /// </summary> public static string GetDrivingLicense(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.PersonalAttributes?.DrivingLicense); }
/// <summary> /// Gets the candidate's family composition (if found) or <see langword="null"/> /// </summary> public static string GetFamilyComposition(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.PersonalAttributes?.FamilyComposition); }
/// <summary> /// Gets a list of ISO 639-1 codes for language competencies the candidate listed (if any) or <see langword="null"/> /// </summary> public static IEnumerable <string> GetLanguageCompetencies(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.LanguageCompetencies?.Select(c => c.LanguageCode)); }
/// <summary> /// Returns the email addresses or <see langword="null"/> /// </summary> public static IEnumerable <string> GetEmailAddresses(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.ContactInformation?.EmailAddresses); }
/// <summary> /// Gets the number of military experiences/posts found on a resume /// </summary> public static int GetNumberOfMilitaryExperience(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.MilitaryExperience?.Count() ?? 0); }
/// <summary> /// Returns the candidate name or <see langword="null"/> /// </summary> public static PersonName GetCandidateName(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.ContactInformation?.CandidateName); }
/// <summary> /// Gets the last-modified date of the resume, if you provided one. Otherwise <see cref="DateTime.MinValue"/> /// </summary> public static DateTime GetDocumentLastModified(this ParseResumeResponseExtensions exts) { return(exts.Response.Value.ResumeData?.ResumeMetadata?.DocumentLastModified ?? DateTime.MinValue); }
/// <summary> /// Returns the specific type of web address if it exists or <see langword="null"/> /// </summary> /// <param name="type"> /// One of: /// <br/><see cref="WebAddressType.PersonalWebsite"/> /// <br/><see cref="WebAddressType.LinkedIn"/> /// <br/><see cref="WebAddressType.Twitter"/> /// <br/><see cref="WebAddressType.GitHub"/> /// <br/><see cref="WebAddressType.Facebook"/> /// <br/><see cref="WebAddressType.Skype"/> /// <br/><see cref="WebAddressType.WhatsApp"/> /// <br/><see cref="WebAddressType.StackOverflow"/> /// <br/><see cref="WebAddressType.Instagram"/> /// <br/><see cref="WebAddressType.Reddit"/> /// <br/><see cref="WebAddressType.Signal"/> /// <br/><see cref="WebAddressType.Quora"/> /// <br/><see cref="WebAddressType.ICQ"/> /// <br/><see cref="WebAddressType.WeChat"/> /// <br/><see cref="WebAddressType.QQ"/> /// <br/><see cref="WebAddressType.Telegraph"/> /// <br/><see cref="WebAddressType.Telegram"/> /// <br/><see cref="WebAddressType.MeWe"/> /// <br/><see cref="WebAddressType.Parler"/> /// <br/><see cref="WebAddressType.Gab"/> /// <br/><see cref="WebAddressType.Unknown"/> /// </param> /// <param name="exts"></param> public static string GetWebAddress(this ParseResumeResponseExtensions exts, WebAddressType type) { return(exts.Response.Value.ResumeData?.ContactInformation?.WebAddresses?.FirstOrDefault(a => a.Type == type.Value)?.Address); }
/// <summary> /// Gets a list of licenses found (if any) or <see langword="null"/> /// </summary> /// <param name="exts"></param> /// <param name="onlyMatchedFromList"> /// <see langword="true"/> to only return licenses that matched to Sovren's internal list of known licenses. /// <br/><see langword="false"/> to return all licenses, no matter how they were found /// </param> public static IEnumerable <string> GetLicenses(this ParseResumeResponseExtensions exts, bool onlyMatchedFromList = false) { return(exts.Response.Value.ResumeData?.Licenses?.Where(c => !onlyMatchedFromList || c.MatchedFromList).Select(c => c.Name)); }