|
|
|
@ -14,6 +14,15 @@ import { profileAnalysisDtoMapper } from '~/mappers/offers-mappers';
|
|
|
|
|
|
|
|
|
|
import { createRouter } from '../context';
|
|
|
|
|
|
|
|
|
|
type Offer = OffersOffer & {
|
|
|
|
|
company: Company;
|
|
|
|
|
offersFullTime:
|
|
|
|
|
| (OffersFullTime & { totalCompensation: OffersCurrency })
|
|
|
|
|
| null;
|
|
|
|
|
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
|
|
|
|
|
profile: OffersProfile & { background: OffersBackground | null };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const searchOfferPercentile = (
|
|
|
|
|
offer: OffersOffer & {
|
|
|
|
|
company: Company;
|
|
|
|
@ -58,46 +67,62 @@ export const offersAnalysisRouter = createRouter()
|
|
|
|
|
async resolve({ ctx, input }) {
|
|
|
|
|
const analysis = await ctx.prisma.offersAnalysis.findFirst({
|
|
|
|
|
include: {
|
|
|
|
|
overallHighestOffer: {
|
|
|
|
|
companyAnalysis: {
|
|
|
|
|
include: {
|
|
|
|
|
company: true,
|
|
|
|
|
offersFullTime: {
|
|
|
|
|
include: {
|
|
|
|
|
totalCompensation: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
offersIntern: {
|
|
|
|
|
topSimilarOffers: {
|
|
|
|
|
include: {
|
|
|
|
|
monthlySalary: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
profile: {
|
|
|
|
|
include: {
|
|
|
|
|
background: true,
|
|
|
|
|
company: true,
|
|
|
|
|
offersFullTime: {
|
|
|
|
|
include: {
|
|
|
|
|
totalCompensation: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
offersIntern: {
|
|
|
|
|
include: {
|
|
|
|
|
monthlySalary: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
profile: {
|
|
|
|
|
include: {
|
|
|
|
|
background: {
|
|
|
|
|
include: {
|
|
|
|
|
experiences: {
|
|
|
|
|
include: {
|
|
|
|
|
company: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
topCompanyOffers: {
|
|
|
|
|
overallAnalysis: {
|
|
|
|
|
include: {
|
|
|
|
|
company: true,
|
|
|
|
|
offersFullTime: {
|
|
|
|
|
include: {
|
|
|
|
|
totalCompensation: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
offersIntern: {
|
|
|
|
|
topSimilarOffers: {
|
|
|
|
|
include: {
|
|
|
|
|
monthlySalary: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
profile: {
|
|
|
|
|
include: {
|
|
|
|
|
background: {
|
|
|
|
|
company: true,
|
|
|
|
|
offersFullTime: {
|
|
|
|
|
include: {
|
|
|
|
|
totalCompensation: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
offersIntern: {
|
|
|
|
|
include: {
|
|
|
|
|
experiences: {
|
|
|
|
|
monthlySalary: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
profile: {
|
|
|
|
|
include: {
|
|
|
|
|
background: {
|
|
|
|
|
include: {
|
|
|
|
|
company: true,
|
|
|
|
|
experiences: {
|
|
|
|
|
include: {
|
|
|
|
|
company: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
@ -106,7 +131,7 @@ export const offersAnalysisRouter = createRouter()
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
topOverallOffers: {
|
|
|
|
|
overallHighestOffer: {
|
|
|
|
|
include: {
|
|
|
|
|
company: true,
|
|
|
|
|
offersFullTime: {
|
|
|
|
@ -121,15 +146,7 @@ export const offersAnalysisRouter = createRouter()
|
|
|
|
|
},
|
|
|
|
|
profile: {
|
|
|
|
|
include: {
|
|
|
|
|
background: {
|
|
|
|
|
include: {
|
|
|
|
|
experiences: {
|
|
|
|
|
include: {
|
|
|
|
|
company: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
background: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
@ -310,11 +327,56 @@ export const offersAnalysisRouter = createRouter()
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let similarCompanyOffers = similarOffers.filter(
|
|
|
|
|
(offer) => offer.companyId === overallHighestOffer.companyId,
|
|
|
|
|
// COMPANY ANALYSIS
|
|
|
|
|
const companyMap = new Map<string, Offer>();
|
|
|
|
|
offers.forEach((offer) => {
|
|
|
|
|
if (companyMap.get(offer.companyId) == null) {
|
|
|
|
|
companyMap.set(offer.companyId, offer);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const companyAnalysis = Array.from(companyMap.values()).map(
|
|
|
|
|
(companyOffer) => {
|
|
|
|
|
// TODO: Refactor calculating analysis into a function
|
|
|
|
|
let similarCompanyOffers = similarOffers.filter(
|
|
|
|
|
(offer) => offer.companyId === overallHighestOffer.companyId,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const companyIndex = searchOfferPercentile(
|
|
|
|
|
overallHighestOffer,
|
|
|
|
|
similarCompanyOffers,
|
|
|
|
|
);
|
|
|
|
|
const companyPercentile =
|
|
|
|
|
similarCompanyOffers.length <= 1
|
|
|
|
|
? 100
|
|
|
|
|
: 100 - (100 * companyIndex) / (similarCompanyOffers.length - 1);
|
|
|
|
|
|
|
|
|
|
// Get top offers (excluding user's offer)
|
|
|
|
|
similarCompanyOffers = similarCompanyOffers.filter(
|
|
|
|
|
(offer) => offer.id !== companyOffer.id,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const noOfSimilarCompanyOffers = similarCompanyOffers.length;
|
|
|
|
|
const similarCompanyOffers90PercentileIndex = Math.ceil(
|
|
|
|
|
noOfSimilarCompanyOffers * 0.1,
|
|
|
|
|
);
|
|
|
|
|
const topPercentileCompanyOffers =
|
|
|
|
|
noOfSimilarCompanyOffers > 2
|
|
|
|
|
? similarCompanyOffers.slice(
|
|
|
|
|
similarCompanyOffers90PercentileIndex,
|
|
|
|
|
similarCompanyOffers90PercentileIndex + 2,
|
|
|
|
|
)
|
|
|
|
|
: similarCompanyOffers;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
noOfSimilarOffers: noOfSimilarCompanyOffers,
|
|
|
|
|
percentile: companyPercentile,
|
|
|
|
|
topSimilarOffers: topPercentileCompanyOffers,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// CALCULATE PERCENTILES
|
|
|
|
|
// OVERALL ANALYSIS
|
|
|
|
|
const overallIndex = searchOfferPercentile(
|
|
|
|
|
overallHighestOffer,
|
|
|
|
|
similarOffers,
|
|
|
|
@ -324,23 +386,9 @@ export const offersAnalysisRouter = createRouter()
|
|
|
|
|
? 100
|
|
|
|
|
: 100 - (100 * overallIndex) / (similarOffers.length - 1);
|
|
|
|
|
|
|
|
|
|
const companyIndex = searchOfferPercentile(
|
|
|
|
|
overallHighestOffer,
|
|
|
|
|
similarCompanyOffers,
|
|
|
|
|
);
|
|
|
|
|
const companyPercentile =
|
|
|
|
|
similarCompanyOffers.length <= 1
|
|
|
|
|
? 100
|
|
|
|
|
: 100 - (100 * companyIndex) / (similarCompanyOffers.length - 1);
|
|
|
|
|
|
|
|
|
|
// FIND TOP >=90 PERCENTILE OFFERS, DOESN'T GIVE 100th PERCENTILE
|
|
|
|
|
// e.g. If there only 4 offers, it gives the 2nd and 3rd offer
|
|
|
|
|
similarOffers = similarOffers.filter(
|
|
|
|
|
(offer) => offer.id !== overallHighestOffer.id,
|
|
|
|
|
);
|
|
|
|
|
similarCompanyOffers = similarCompanyOffers.filter(
|
|
|
|
|
(offer) => offer.id !== overallHighestOffer.id,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const noOfSimilarOffers = similarOffers.length;
|
|
|
|
|
const similarOffers90PercentileIndex = Math.ceil(noOfSimilarOffers * 0.1);
|
|
|
|
@ -352,86 +400,100 @@ export const offersAnalysisRouter = createRouter()
|
|
|
|
|
)
|
|
|
|
|
: similarOffers;
|
|
|
|
|
|
|
|
|
|
const noOfSimilarCompanyOffers = similarCompanyOffers.length;
|
|
|
|
|
const similarCompanyOffers90PercentileIndex = Math.ceil(
|
|
|
|
|
noOfSimilarCompanyOffers * 0.1,
|
|
|
|
|
);
|
|
|
|
|
const topPercentileCompanyOffers =
|
|
|
|
|
noOfSimilarCompanyOffers > 2
|
|
|
|
|
? similarCompanyOffers.slice(
|
|
|
|
|
similarCompanyOffers90PercentileIndex,
|
|
|
|
|
similarCompanyOffers90PercentileIndex + 2,
|
|
|
|
|
)
|
|
|
|
|
: similarCompanyOffers;
|
|
|
|
|
|
|
|
|
|
const analysis = await ctx.prisma.offersAnalysis.create({
|
|
|
|
|
data: {
|
|
|
|
|
companyPercentile,
|
|
|
|
|
noOfSimilarCompanyOffers,
|
|
|
|
|
noOfSimilarOffers,
|
|
|
|
|
companyAnalysis: {
|
|
|
|
|
create: companyAnalysis.map((analysisUnit) => {
|
|
|
|
|
return {
|
|
|
|
|
noOfSimilarOffers: analysisUnit.noOfSimilarOffers,
|
|
|
|
|
percentile: analysisUnit.percentile,
|
|
|
|
|
topSimilarOffers: {
|
|
|
|
|
connect: analysisUnit.topSimilarOffers.map((offer) => {
|
|
|
|
|
return { id: offer.id };
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
overallAnalysis: {
|
|
|
|
|
create: {
|
|
|
|
|
noOfSimilarOffers,
|
|
|
|
|
percentile: overallPercentile,
|
|
|
|
|
topSimilarOffers: {
|
|
|
|
|
connect: topPercentileOffers.map((offer) => {
|
|
|
|
|
return { id: offer.id };
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
overallHighestOffer: {
|
|
|
|
|
connect: {
|
|
|
|
|
id: overallHighestOffer.id,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
overallPercentile,
|
|
|
|
|
profile: {
|
|
|
|
|
connect: {
|
|
|
|
|
id: input.profileId,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
topCompanyOffers: {
|
|
|
|
|
connect: topPercentileCompanyOffers.map((offer) => {
|
|
|
|
|
return { id: offer.id };
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
topOverallOffers: {
|
|
|
|
|
connect: topPercentileOffers.map((offer) => {
|
|
|
|
|
return { id: offer.id };
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
include: {
|
|
|
|
|
overallHighestOffer: {
|
|
|
|
|
companyAnalysis: {
|
|
|
|
|
include: {
|
|
|
|
|
company: true,
|
|
|
|
|
offersFullTime: {
|
|
|
|
|
topSimilarOffers: {
|
|
|
|
|
include: {
|
|
|
|
|
totalCompensation: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
offersIntern: {
|
|
|
|
|
include: {
|
|
|
|
|
monthlySalary: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
profile: {
|
|
|
|
|
include: {
|
|
|
|
|
background: true,
|
|
|
|
|
company: true,
|
|
|
|
|
offersFullTime: {
|
|
|
|
|
include: {
|
|
|
|
|
totalCompensation: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
offersIntern: {
|
|
|
|
|
include: {
|
|
|
|
|
monthlySalary: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
profile: {
|
|
|
|
|
include: {
|
|
|
|
|
background: {
|
|
|
|
|
include: {
|
|
|
|
|
experiences: {
|
|
|
|
|
include: {
|
|
|
|
|
company: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
topCompanyOffers: {
|
|
|
|
|
overallAnalysis: {
|
|
|
|
|
include: {
|
|
|
|
|
company: true,
|
|
|
|
|
offersFullTime: {
|
|
|
|
|
topSimilarOffers: {
|
|
|
|
|
include: {
|
|
|
|
|
totalCompensation: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
offersIntern: {
|
|
|
|
|
include: {
|
|
|
|
|
monthlySalary: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
profile: {
|
|
|
|
|
include: {
|
|
|
|
|
background: {
|
|
|
|
|
company: true,
|
|
|
|
|
offersFullTime: {
|
|
|
|
|
include: {
|
|
|
|
|
totalCompensation: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
offersIntern: {
|
|
|
|
|
include: {
|
|
|
|
|
monthlySalary: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
profile: {
|
|
|
|
|
include: {
|
|
|
|
|
experiences: {
|
|
|
|
|
background: {
|
|
|
|
|
include: {
|
|
|
|
|
company: true,
|
|
|
|
|
experiences: {
|
|
|
|
|
include: {
|
|
|
|
|
company: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
@ -440,7 +502,7 @@ export const offersAnalysisRouter = createRouter()
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
topOverallOffers: {
|
|
|
|
|
overallHighestOffer: {
|
|
|
|
|
include: {
|
|
|
|
|
company: true,
|
|
|
|
|
offersFullTime: {
|
|
|
|
@ -455,15 +517,7 @@ export const offersAnalysisRouter = createRouter()
|
|
|
|
|
},
|
|
|
|
|
profile: {
|
|
|
|
|
include: {
|
|
|
|
|
background: {
|
|
|
|
|
include: {
|
|
|
|
|
experiences: {
|
|
|
|
|
include: {
|
|
|
|
|
company: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
background: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|