Пример #1
0
        /// <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);
        }
Пример #3
0
        /// <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());
        }
Пример #5
0
        /// <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);
        }
Пример #6
0
        /// <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);
        }
Пример #10
0
 /// <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);
 }
Пример #11
0
 /// <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));
 }
Пример #13
0
 /// <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);
 }
Пример #16
0
        /// <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);
        }
Пример #17
0
 /// <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);
 }
Пример #18
0
        /// <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));
        }
Пример #19
0
 /// <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);
 }
Пример #20
0
 /// <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);
 }
Пример #21
0
 /// <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);
 }
Пример #22
0
 /// <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);
 }
Пример #23
0
 /// <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);
 }
Пример #24
0
 /// <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);
 }
Пример #26
0
 /// <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);
 }
Пример #28
0
 /// <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);
 }
Пример #30
0
 /// <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));
 }