JobAdSearchResults IJobAdSearchService.SearchSimilar(Guid?memberId, Guid jobAdId, JobAdSearchQuery searchQuery) { const string method = "GetSimilarJobs"; try { var reader = GetReader(); var searcher = new Searcher(reader); var docId = searcher.Fetch(jobAdId); // If the job ad cannot be found then return no results. if (docId == -1) { return(new JobAdSearchResults()); } var jobAd = _jobAdsQuery.GetJobAd <JobAd>(jobAdId); if (jobAd == null) { return(new JobAdSearchResults()); } // Look for more like this. var mlt = new MoreLikeThis(reader); mlt.setAnalyzer(_contentAnalyzer); mlt.setFieldNames(new [] { FieldName.Content, FieldName.Title }); var query = mlt.like(docId); //query = new SeniorityIndexHandler().GetQuery(query, new JobAdSearchQuery {SeniorityIndex = jobAd.SeniorityIndex}); // Ensure the initial job is not in the results. var searchFilter = new BooleanFilter(); searchFilter.add(new FilterClause(new SpecialsFilter(SearchFieldName.Id, false, new[] { jobAdId.ToFieldValue() }), BooleanClause.Occur.MUST_NOT)); // Add salary and location restriction. var filter = _indexer.GetFilter( new JobAdSearchQuery { Salary = FudgeSalary(jobAd.Description.Salary), ExcludeNoSalary = true, Location = jobAd.Description.Location, Distance = 50, }, null, null); searchFilter.add(new FilterClause(filter, BooleanClause.Occur.MUST)); return(searcher.Search(query, searchFilter, null, null, searchQuery.Skip, searchQuery.Take ?? reader.maxDoc(), false)); } catch (Exception e) { #region Log EventSource.Raise(Event.Error, method, "Unexpected exception.", e); #endregion throw; } }
JobAdSearchResults IJobAdSearchService.SearchSuggested(Guid?memberId, JobAdSearchQuery searchQuery) { const string method = "GetSuggestedJobs"; try { if (memberId == null) { return(new JobAdSearchResults()); } var reader = GetReader(); var searcher = new Searcher(reader); var member = _membersQuery.GetMember(memberId.Value); var candidate = _candidatesQuery.GetCandidate(memberId.Value); if (member == null || candidate == null || candidate.ResumeId == null) { return(new JobAdSearchResults()); } var resume = _resumesQuery.GetResume(candidate.ResumeId.Value); //Get a MLT query based on the candidate's details var mlt = new MoreLikeThis(reader, CreateSimilarity()); mlt.setAnalyzer(_contentAnalyzer); mlt.setMaxNumTokensParsed(10000); //increase for long resumes mlt.setFieldNames(new[] { FieldName.Content }); mlt.setMinWordLen(3); //exclude UK, BBC and the like mlt.setMaxQueryTerms(20); //mlt.setBoost(true); var candidateMltQuery = GetCandidateMltQuery(mlt, candidate, resume, method); mlt.setFieldNames(new[] { FieldName.Title }); var candidateTitleMltQuery = GetCandidateMltQuery(mlt, candidate, resume, method); #region Log if (EventSource.IsEnabled(Event.Trace)) { EventSource.Raise(Event.Trace, method, "Candidate MLT Query", Event.Arg("Query", candidateMltQuery == null ? "null" : candidateMltQuery.toString())); EventSource.Raise(Event.Trace, method, "Candidate Title MLT Query", Event.Arg("Query", candidateTitleMltQuery == null ? "null" : candidateTitleMltQuery.toString())); } #endregion var mltQueries = new BooleanQuery(); // Up to 3 MLT queries ORed together var titleQueries = new BooleanQuery(); // (Optionally) Desired Job Title and up to 3 job titles ORed together var combinedFilter = new BooleanFilter(); // Filtering including exclusion of past applications and salary, location, etc. var combinedQuery = new BooleanQuery(); // mltQueries and titleQueries ANDed together if (candidateMltQuery != null) { mltQueries.add(candidateMltQuery, BooleanClause.Occur.SHOULD); } if (candidateTitleMltQuery != null) { mltQueries.add(candidateTitleMltQuery, BooleanClause.Occur.SHOULD); } //Get a MLT query based on the candidate's past 3 months of applications var jobAdIds = (from a in _memberApplicationsQuery.GetApplications(member.Id) where a.CreatedTime >= DateTime.Now.AddMonths(-3) select a.PositionId).ToList(); if (jobAdIds.Any()) { mlt.setFieldNames(new[] { FieldName.Content }); var applicationsMltQuery = GetApplicationsMltQuery(mlt, jobAdIds); if (applicationsMltQuery != null) { mltQueries.add(applicationsMltQuery, BooleanClause.Occur.SHOULD); } #region Log if (EventSource.IsEnabled(Event.Trace)) { EventSource.Raise(Event.Trace, method, "Applications MLT Query", Event.Arg("Query", applicationsMltQuery == null ? "null" : applicationsMltQuery.toString())); } #endregion // ensure the jobs that have already been applied for are not in the results var idFilter = new SpecialsFilter(SearchFieldName.Id, false, jobAdIds.Select(id => id.ToFieldValue())); combinedFilter.add(new FilterClause(idFilter, BooleanClause.Occur.MUST_NOT)); #region Log if (EventSource.IsEnabled(Event.Trace)) { EventSource.Raise(Event.Trace, method, "Combined Filter #1", Event.Arg("Query", combinedFilter.toString())); } #endregion } if (mltQueries.getClauses().Any()) { combinedQuery.add(new BooleanClause(mltQueries, BooleanClause.Occur.SHOULD)); } // now add in additional weighting data from the candidate's profile if (!string.IsNullOrEmpty(candidate.DesiredJobTitle)) { //Boost the DesiredJobTitle above the rest of the terms var jobTitleExpression = Expression.Parse(Expression.StripOperatorsAndBrackets(candidate.DesiredJobTitle), ModificationFlags.AllowShingling); // First match on title var boostQuery = _indexer.GetQuery(new JobAdSearchQuery { AdTitle = jobTitleExpression, IncludeSynonyms = true }); boostQuery.setBoost(1.5F); titleQueries.add(boostQuery, BooleanClause.Occur.SHOULD); // Also match general content //boostQuery = _indexer.GetLuceneQuery(new JobAdSearchQuery {Keywords = jobTitleExpression, IncludeSynonyms = true}); //boostQuery.setBoost(1.3F); //titleQueries.add(boostQuery, BooleanClause.Occur.SHOULD); #region Log if (EventSource.IsEnabled(Event.Trace)) { EventSource.Raise(Event.Trace, method, "Combined Query #1", Event.Arg("Query", combinedQuery.toString())); } #endregion } if (resume.Jobs != null) { foreach (var jobTitle in resume.Jobs.Select(j => j.Title).Take(3)) { if (string.IsNullOrEmpty(jobTitle)) { continue; } var jobTitleExpression = Expression.Parse(Expression.StripOperatorsAndBrackets(jobTitle), ModificationFlags.AllowShingling); // First match on title var boostQuery = _indexer.GetQuery(new JobAdSearchQuery { AdTitle = jobTitleExpression, IncludeSynonyms = true }); boostQuery.setBoost(1.2F); titleQueries.add(boostQuery, BooleanClause.Occur.SHOULD); // Also match general content //boostQuery = _indexer.GetLuceneQuery(new JobAdSearchQuery { Keywords = jobTitleExpression, IncludeSynonyms = true }); //boostQuery.setBoost(1.1F); //titleQueries.add(boostQuery, BooleanClause.Occur.SHOULD); // Also match general content //boostQuery = _indexer.GetLuceneQuery(new JobAdSearchQuery { Keywords = jobTitleExpression, IncludeSynonyms = true }); //boostQuery.setBoost(1.1F); //combinedQuery.add(boostQuery, BooleanClause.Occur.SHOULD); } } // now combine the queries if (mltQueries.getClauses().Any()) { combinedQuery.add(new BooleanClause(mltQueries, BooleanClause.Occur.SHOULD)); } if (titleQueries.getClauses().Any()) { combinedQuery.add(new BooleanClause(titleQueries, BooleanClause.Occur.MUST)); } #region Log if (EventSource.IsEnabled(Event.Trace)) { EventSource.Raise(Event.Trace, method, "Combined Query #2", Event.Arg("Query", combinedQuery.toString())); } #endregion searchQuery.Salary = candidate.DesiredSalary == null ? null : candidate.DesiredSalary.Clone(); searchQuery.ExcludeNoSalary = false; searchQuery.Location = member.Address.Location; searchQuery.Distance = 50; searchQuery.JobTypes = candidate.DesiredJobTypes; searchQuery.Relocations = candidate.RelocationPreference != RelocationPreference.No && candidate.RelocationLocations != null ? candidate.RelocationLocations = candidate.RelocationLocations.ToArray() : null; //Add salary, job type, location restriction & (optionally) last run time var filter = _indexer.GetFilter(searchQuery, null, null); // combine salary, etc. filter with the excluded job ads combinedFilter.add(new FilterClause(filter, BooleanClause.Occur.MUST)); // exclude blocked jobs var excludedIds = _jobAdActivityFiltersQuery.GetExcludeJobAdIds(member, searchQuery); if (excludedIds != null && excludedIds.Count > 0) { combinedFilter.add(new FilterClause(new SpecialsFilter(SearchFieldName.Id, true, excludedIds.Select(id => id.ToFieldValue())), BooleanClause.Occur.MUST)); } #region Log if (EventSource.IsEnabled(Event.Trace)) { EventSource.Raise(Event.Trace, method, "Combined Filter #2", Event.Arg("Query", combinedFilter.toString())); } #endregion var searchResults = searcher.Search(combinedQuery, combinedFilter, null, null, searchQuery.Skip, searchQuery.Take ?? reader.maxDoc(), false); #region Log if (EventSource.IsEnabled(Event.Trace)) { EventSource.Raise(Event.Trace, method, "MLT Results", Event.Arg("Results Count", searchResults == null ? 0 : searchResults.JobAdIds.Count)); } #endregion return(searchResults); } catch (Exception e) { #region Log EventSource.Raise(Event.Error, method, "Unexpected exception.", e); #endregion throw; } }