[offers][fix] Make analysis more accurate by omitting duplicate salary values when calculating percentiles

pull/522/head
Bryann Yeap Kok Keong 2 years ago
parent a7c9f58ef3
commit cc035387d7

@ -5,6 +5,7 @@ import type {
Country, Country,
OffersBackground, OffersBackground,
OffersCurrency, OffersCurrency,
OffersExperience,
OffersFullTime, OffersFullTime,
OffersIntern, OffersIntern,
OffersOffer, OffersOffer,
@ -13,6 +14,7 @@ import type {
PrismaClient, PrismaClient,
State, State,
} from '@prisma/client'; } from '@prisma/client';
import { JobType } from '@prisma/client';
import { TRPCError } from '@trpc/server'; import { TRPCError } from '@trpc/server';
import { analysisInclusion } from './analysisInclusion'; import { analysisInclusion } from './analysisInclusion';
@ -33,6 +35,27 @@ type Offer = OffersOffer & {
profile: OffersProfile & { background: OffersBackground | null }; profile: OffersProfile & { background: OffersBackground | null };
}; };
type SimilarOffer = OffersOffer & {
company: Company;
location: City & { state: State & { country: Country } };
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
profile: OffersProfile & {
background:
| (OffersBackground & {
experiences: Array<
OffersExperience & {
company: Company | null;
location: (City & { state: State & { country: Country } }) | null;
}
>;
})
| null;
};
};
const getSimilarOffers = async ( const getSimilarOffers = async (
prisma: PrismaClient< prisma: PrismaClient<
Prisma.PrismaClientOptions, Prisma.PrismaClientOptions,
@ -161,28 +184,45 @@ const getSimilarOffers = async (
}); });
}; };
const searchOfferPercentile = ( // OFFERS MUST BE ORDERED
offer: Offer, const calculatePercentile = (
similarOffers: Array< orderedOffers: Array<SimilarOffer>,
OffersOffer & { offerToCalculate: Offer,
company: Company;
offersFullTime:
| (OffersFullTime & {
totalCompensation: OffersCurrency;
})
| null;
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
profile: OffersProfile & { background: OffersBackground | null };
}
>,
) => { ) => {
for (let i = 0; i < similarOffers.length; i++) { let offerToCalculateIndex = -1;
if (similarOffers[i].id === offer.id) { let numberOfNoDuplicateOffers = 0;
return i; let lastOfferSalary = -1;
const offerToCalculateSalary = getSalary(offerToCalculate);
for (let i = 0; i < orderedOffers.length; i++) {
const offer = orderedOffers[i];
const salary = getSalary(offer, lastOfferSalary);
if (salary !== lastOfferSalary) {
if (salary === offerToCalculateSalary) {
offerToCalculateIndex = i;
}
numberOfNoDuplicateOffers++;
lastOfferSalary = salary;
} }
} }
return -1; const percentile =
numberOfNoDuplicateOffers <= 1
? 100
: 100 - (100 * offerToCalculateIndex) / (numberOfNoDuplicateOffers - 1);
return percentile;
};
const getSalary = (offer: Offer | SimilarOffer, defaultSalary = 0) => {
return offer.jobType === JobType.FULLTIME &&
offer.offersFullTime?.totalCompensation?.baseValue != null
? offer.offersFullTime.totalCompensation.baseValue
: offer.jobType === JobType.INTERN &&
offer.offersIntern?.monthlySalary?.baseValue != null
? offer.offersIntern.monthlySalary.baseValue
: defaultSalary;
}; };
export const generateAnalysis = async (params: { export const generateAnalysis = async (params: {
@ -264,9 +304,26 @@ export const generateAnalysis = async (params: {
const overallHighestOffer = offers[0]; const overallHighestOffer = offers[0];
const offerIds = offers.map((offer) => offer.id);
// OVERALL ANALYSIS
let similarOffers = await getSimilarOffers(ctx.prisma, overallHighestOffer); let similarOffers = await getSimilarOffers(ctx.prisma, overallHighestOffer);
const overallPercentile = calculatePercentile(
similarOffers,
overallHighestOffer,
);
const offerIds = offers.map((offer) => offer.id); // Get top offers (excluding user's offers)
similarOffers = similarOffers.filter((offer) => !offerIds.includes(offer.id));
const noOfSimilarOffers = similarOffers.length;
const similarOffers90PercentileIndex = Math.ceil(noOfSimilarOffers * 0.1);
const topPercentileOffers =
noOfSimilarOffers > 2
? similarOffers.slice(
similarOffers90PercentileIndex,
similarOffers90PercentileIndex + 2,
)
: similarOffers;
// COMPANY ANALYSIS // COMPANY ANALYSIS
const companyMap = new Map<string, Offer>(); const companyMap = new Map<string, Offer>();
@ -284,21 +341,15 @@ export const generateAnalysis = async (params: {
companyOffer, companyOffer,
companyOffer.companyId, companyOffer.companyId,
); );
const companyPercentile = calculatePercentile(
const companyIndex = searchOfferPercentile(
companyOffer,
similarCompanyOffers, similarCompanyOffers,
companyOffer,
); );
const companyPercentile =
similarCompanyOffers.length <= 1
? 100
: 100 - (100 * companyIndex) / (similarCompanyOffers.length - 1);
// Get top offers (excluding user's offers) // Get top offers (excluding user's offers)
similarCompanyOffers = similarCompanyOffers.filter( similarCompanyOffers = similarCompanyOffers.filter(
(offer) => !offerIds.includes(offer.id), (offer) => !offerIds.includes(offer.id),
); );
const noOfSimilarCompanyOffers = similarCompanyOffers.length; const noOfSimilarCompanyOffers = similarCompanyOffers.length;
const similarCompanyOffers90PercentileIndex = Math.ceil( const similarCompanyOffers90PercentileIndex = Math.ceil(
noOfSimilarCompanyOffers * 0.1, noOfSimilarCompanyOffers * 0.1,
@ -320,28 +371,6 @@ export const generateAnalysis = async (params: {
}), }),
); );
// OVERALL ANALYSIS
const overallIndex = searchOfferPercentile(
overallHighestOffer,
similarOffers,
);
const overallPercentile =
similarOffers.length <= 1
? 100
: 100 - (100 * overallIndex) / (similarOffers.length - 1);
similarOffers = similarOffers.filter((offer) => !offerIds.includes(offer.id));
const noOfSimilarOffers = similarOffers.length;
const similarOffers90PercentileIndex = Math.ceil(noOfSimilarOffers * 0.1);
const topPercentileOffers =
noOfSimilarOffers > 2
? similarOffers.slice(
similarOffers90PercentileIndex,
similarOffers90PercentileIndex + 2,
)
: similarOffers;
const analysis = await ctx.prisma.offersAnalysis.create({ const analysis = await ctx.prisma.offersAnalysis.create({
data: { data: {
companyAnalysis: { companyAnalysis: {

Loading…
Cancel
Save