mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2026-04-03 02:48:43 +08:00
[offers][fix] Make analysis more accurate by omitting duplicate salary values when calculating percentiles
This commit is contained in:
@@ -5,6 +5,7 @@ import type {
|
||||
Country,
|
||||
OffersBackground,
|
||||
OffersCurrency,
|
||||
OffersExperience,
|
||||
OffersFullTime,
|
||||
OffersIntern,
|
||||
OffersOffer,
|
||||
@@ -13,6 +14,7 @@ import type {
|
||||
PrismaClient,
|
||||
State,
|
||||
} from '@prisma/client';
|
||||
import { JobType } from '@prisma/client';
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { analysisInclusion } from './analysisInclusion';
|
||||
@@ -33,6 +35,27 @@ type Offer = OffersOffer & {
|
||||
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 (
|
||||
prisma: PrismaClient<
|
||||
Prisma.PrismaClientOptions,
|
||||
@@ -161,28 +184,45 @@ const getSimilarOffers = async (
|
||||
});
|
||||
};
|
||||
|
||||
const searchOfferPercentile = (
|
||||
offer: Offer,
|
||||
similarOffers: Array<
|
||||
OffersOffer & {
|
||||
company: Company;
|
||||
offersFullTime:
|
||||
| (OffersFullTime & {
|
||||
totalCompensation: OffersCurrency;
|
||||
})
|
||||
| null;
|
||||
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
|
||||
profile: OffersProfile & { background: OffersBackground | null };
|
||||
}
|
||||
>,
|
||||
// OFFERS MUST BE ORDERED
|
||||
const calculatePercentile = (
|
||||
orderedOffers: Array<SimilarOffer>,
|
||||
offerToCalculate: Offer,
|
||||
) => {
|
||||
for (let i = 0; i < similarOffers.length; i++) {
|
||||
if (similarOffers[i].id === offer.id) {
|
||||
return i;
|
||||
let offerToCalculateIndex = -1;
|
||||
let numberOfNoDuplicateOffers = 0;
|
||||
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: {
|
||||
@@ -264,10 +304,27 @@ export const generateAnalysis = async (params: {
|
||||
|
||||
const overallHighestOffer = offers[0];
|
||||
|
||||
let similarOffers = await getSimilarOffers(ctx.prisma, overallHighestOffer);
|
||||
|
||||
const offerIds = offers.map((offer) => offer.id);
|
||||
|
||||
// OVERALL ANALYSIS
|
||||
let similarOffers = await getSimilarOffers(ctx.prisma, overallHighestOffer);
|
||||
const overallPercentile = calculatePercentile(
|
||||
similarOffers,
|
||||
overallHighestOffer,
|
||||
);
|
||||
|
||||
// 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
|
||||
const companyMap = new Map<string, Offer>();
|
||||
offers.forEach((offer) => {
|
||||
@@ -284,21 +341,15 @@ export const generateAnalysis = async (params: {
|
||||
companyOffer,
|
||||
companyOffer.companyId,
|
||||
);
|
||||
|
||||
const companyIndex = searchOfferPercentile(
|
||||
companyOffer,
|
||||
const companyPercentile = calculatePercentile(
|
||||
similarCompanyOffers,
|
||||
companyOffer,
|
||||
);
|
||||
const companyPercentile =
|
||||
similarCompanyOffers.length <= 1
|
||||
? 100
|
||||
: 100 - (100 * companyIndex) / (similarCompanyOffers.length - 1);
|
||||
|
||||
// Get top offers (excluding user's offers)
|
||||
similarCompanyOffers = similarCompanyOffers.filter(
|
||||
(offer) => !offerIds.includes(offer.id),
|
||||
);
|
||||
|
||||
const noOfSimilarCompanyOffers = similarCompanyOffers.length;
|
||||
const similarCompanyOffers90PercentileIndex = Math.ceil(
|
||||
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({
|
||||
data: {
|
||||
companyAnalysis: {
|
||||
|
||||
Reference in New Issue
Block a user