[offers][feat] Add get offers analysis API

pull/380/head
BryannYeap 2 years ago
parent 56632892ce
commit e99e580d5e

@ -2,7 +2,7 @@ import React from 'react';
import { trpc } from '~/utils/trpc'; import { trpc } from '~/utils/trpc';
function profileAnalysis() { function GenerateAnalysis() {
const analysis = trpc.useQuery([ const analysis = trpc.useQuery([
'offers.analysis.generate', 'offers.analysis.generate',
{ profileId: 'cl98yxuei002htx1s8lrmwzmy' }, { profileId: 'cl98yxuei002htx1s8lrmwzmy' },
@ -11,4 +11,4 @@ function profileAnalysis() {
return <div>{JSON.stringify(analysis.data)}</div>; return <div>{JSON.stringify(analysis.data)}</div>;
} }
export default profileAnalysis; export default GenerateAnalysis;

@ -0,0 +1,14 @@
import React from 'react';
import { trpc } from '~/utils/trpc';
function GetAnalysis() {
const analysis = trpc.useQuery([
'offers.analysis.get',
{ profileId: 'cl98yxuei002htx1s8lrmwzmy' },
]);
return <div>{JSON.stringify(analysis.data)}</div>;
}
export default GetAnalysis;

@ -121,12 +121,7 @@ const specificAnalysisDtoMapper = (
const highestOfferDtoMapper = ( const highestOfferDtoMapper = (
offer: OffersOffer & { offer: OffersOffer & {
OffersFullTime: OffersFullTime:
| (OffersFullTime & { | (OffersFullTime & { totalCompensation: OffersCurrency })
baseSalary: OffersCurrency;
bonus: OffersCurrency;
stocks: OffersCurrency;
totalCompensation: OffersCurrency;
})
| null; | null;
OffersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null; OffersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
company: Company; company: Company;
@ -146,279 +141,344 @@ const highestOfferDtoMapper = (
}; };
}; };
export const offersAnalysisRouter = createRouter().query('generate', { const profileAnalysisDtoMapper = (
input: z.object({ analysisId: string,
profileId: z.string(), profileId: string,
}), overallHighestOffer: OffersOffer & {
async resolve({ ctx, input }) { OffersFullTime:
await ctx.prisma.offersAnalysis.deleteMany({ | (OffersFullTime & { totalCompensation: OffersCurrency })
where: { | null;
profileId: input.profileId, OffersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
}, company: Company;
}); profile: OffersProfile & { background: OffersBackground | null };
},
noOfSimilarOffers: number,
overallPercentile: number,
topPercentileOffers: Array<any>,
noOfSimilarCompanyOffers: number,
companyPercentile: number,
topPercentileCompanyOffers: Array<any>,
) => {
return {
companyAnalysis: specificAnalysisDtoMapper(
noOfSimilarCompanyOffers,
companyPercentile,
topPercentileCompanyOffers,
),
id: analysisId,
overallAnalysis: specificAnalysisDtoMapper(
noOfSimilarOffers,
overallPercentile,
topPercentileOffers,
),
overallHighestOffer: highestOfferDtoMapper(overallHighestOffer),
profileId,
};
};
const offers = await ctx.prisma.offersOffer.findMany({ export const offersAnalysisRouter = createRouter()
include: { .query('generate', {
OffersFullTime: { input: z.object({
include: { profileId: z.string(),
baseSalary: true, }),
bonus: true, async resolve({ ctx, input }) {
stocks: true, await ctx.prisma.offersAnalysis.deleteMany({
totalCompensation: true, where: {
}, profileId: input.profileId,
}, },
OffersIntern: { });
include: {
monthlySalary: true, const offers = await ctx.prisma.offersOffer.findMany({
include: {
OffersFullTime: {
include: {
baseSalary: true,
bonus: true,
stocks: true,
totalCompensation: true,
},
}, },
}, OffersIntern: {
company: true, include: {
profile: { monthlySalary: true,
include: { },
background: true,
}, },
}, company: true,
}, profile: {
orderBy: [ include: {
{ background: true,
OffersFullTime: {
totalCompensation: {
value: 'desc',
}, },
}, },
}, },
{ orderBy: [
OffersIntern: { {
monthlySalary: { OffersFullTime: {
value: 'desc', totalCompensation: {
value: 'desc',
},
},
},
{
OffersIntern: {
monthlySalary: {
value: 'desc',
},
}, },
}, },
],
where: {
profileId: input.profileId,
}, },
],
where: {
profileId: input.profileId,
},
});
if (!offers || offers.length === 0) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'No offers found on this profile',
}); });
}
const overallHighestOffer = offers[0]; if (!offers || offers.length === 0) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'No offers found on this profile',
});
}
// TODO: Shift yoe to background to make it mandatory const overallHighestOffer = offers[0];
if (
!overallHighestOffer.profile.background ||
!overallHighestOffer.profile.background.totalYoe
) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Cannot analyse without YOE',
});
}
const yoe = overallHighestOffer.profile.background.totalYoe as number; // TODO: Shift yoe to background to make it mandatory
if (
!overallHighestOffer.profile.background ||
!overallHighestOffer.profile.background.totalYoe
) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Cannot analyse without YOE',
});
}
let similarOffers = await ctx.prisma.offersOffer.findMany({ const yoe = overallHighestOffer.profile.background.totalYoe as number;
include: {
OffersFullTime: { let similarOffers = await ctx.prisma.offersOffer.findMany({
include: { include: {
totalCompensation: true, OffersFullTime: {
include: {
totalCompensation: true,
},
}, },
}, OffersIntern: {
OffersIntern: { include: {
include: { monthlySalary: true,
monthlySalary: true, },
}, },
}, company: true,
company: true, profile: {
profile: { include: {
include: { background: {
background: { include: {
include: { experiences: {
experiences: { include: {
include: { company: true,
company: true, },
}, },
}, },
}, },
}, },
}, },
}, },
}, orderBy: [
orderBy: [
{
OffersFullTime: {
totalCompensation: {
value: 'desc',
},
},
},
{
OffersIntern: {
monthlySalary: {
value: 'desc',
},
},
},
],
where: {
AND: [
{ {
location: overallHighestOffer.location, OffersFullTime: {
}, totalCompensation: {
{ value: 'desc',
OR: [
{
OffersFullTime: {
level: overallHighestOffer.OffersFullTime?.level,
specialization:
overallHighestOffer.OffersFullTime?.specialization,
},
OffersIntern: {
specialization:
overallHighestOffer.OffersIntern?.specialization,
},
}, },
], },
}, },
{ {
profile: { OffersIntern: {
background: { monthlySalary: {
AND: [ value: 'desc',
{
totalYoe: {
gte: Math.max(yoe - 1, 0),
lte: yoe + 1,
},
},
],
}, },
}, },
}, },
], ],
}, where: {
}); AND: [
{
location: overallHighestOffer.location,
},
{
OR: [
{
OffersFullTime: {
level: overallHighestOffer.OffersFullTime?.level,
specialization:
overallHighestOffer.OffersFullTime?.specialization,
},
OffersIntern: {
specialization:
overallHighestOffer.OffersIntern?.specialization,
},
},
],
},
{
profile: {
background: {
AND: [
{
totalYoe: {
gte: Math.max(yoe - 1, 0),
lte: yoe + 1,
},
},
],
},
},
},
],
},
});
let similarCompanyOffers = similarOffers.filter( let similarCompanyOffers = similarOffers.filter(
(offer: { companyId: string }) => (offer: { companyId: string }) =>
offer.companyId === overallHighestOffer.companyId, offer.companyId === overallHighestOffer.companyId,
); );
// CALCULATE PERCENTILES // CALCULATE PERCENTILES
const overallIndex = binarySearchOfferPercentile( const overallIndex = binarySearchOfferPercentile(
overallHighestOffer, overallHighestOffer,
similarOffers, similarOffers,
); );
const overallPercentile = overallIndex / similarOffers.length; const overallPercentile = overallIndex / similarOffers.length;
const companyIndex = binarySearchOfferPercentile( const companyIndex = binarySearchOfferPercentile(
overallHighestOffer, overallHighestOffer,
similarCompanyOffers, similarCompanyOffers,
); );
const companyPercentile = companyIndex / similarCompanyOffers.length; const companyPercentile = companyIndex / similarCompanyOffers.length;
// FIND TOP >=90 PERCENTILE OFFERS // FIND TOP >=90 PERCENTILE OFFERS
similarOffers = similarOffers.filter( similarOffers = similarOffers.filter(
(offer: { id: string }) => offer.id !== overallHighestOffer.id, (offer: { id: string }) => offer.id !== overallHighestOffer.id,
); );
similarCompanyOffers = similarCompanyOffers.filter( similarCompanyOffers = similarCompanyOffers.filter(
(offer: { id: string }) => offer.id !== overallHighestOffer.id, (offer: { id: string }) => offer.id !== overallHighestOffer.id,
); );
const noOfSimilarOffers = similarOffers.length; const noOfSimilarOffers = similarOffers.length;
const similarOffers90PercentileIndex = const similarOffers90PercentileIndex =
Math.floor(noOfSimilarOffers * 0.9) - 1; Math.floor(noOfSimilarOffers * 0.9) - 1;
const topPercentileOffers = const topPercentileOffers =
noOfSimilarOffers > 1 noOfSimilarOffers > 1
? similarOffers.slice( ? similarOffers.slice(
similarOffers90PercentileIndex, similarOffers90PercentileIndex,
similarOffers90PercentileIndex + 2, similarOffers90PercentileIndex + 2,
) )
: similarOffers; : similarOffers;
const noOfSimilarCompanyOffers = similarCompanyOffers.length; const noOfSimilarCompanyOffers = similarCompanyOffers.length;
const similarCompanyOffers90PercentileIndex = const similarCompanyOffers90PercentileIndex =
Math.floor(noOfSimilarCompanyOffers * 0.9) - 1; Math.floor(noOfSimilarCompanyOffers * 0.9) - 1;
const topPercentileCompanyOffers = const topPercentileCompanyOffers =
noOfSimilarCompanyOffers > 1 noOfSimilarCompanyOffers > 1
? similarCompanyOffers.slice( ? similarCompanyOffers.slice(
similarCompanyOffers90PercentileIndex, similarCompanyOffers90PercentileIndex,
similarCompanyOffers90PercentileIndex + 2, similarCompanyOffers90PercentileIndex + 2,
) )
: similarCompanyOffers; : similarCompanyOffers;
const analysis = await ctx.prisma.offersAnalysis.create({ const analysis = await ctx.prisma.offersAnalysis.create({
data: { data: {
companyPercentile, companyPercentile,
noOfSimilarCompanyOffers, noOfSimilarCompanyOffers,
noOfSimilarOffers, noOfSimilarOffers,
overallHighestOffer: { overallHighestOffer: {
connect: { connect: {
id: overallHighestOffer.id, id: overallHighestOffer.id,
},
}, },
}, overallPercentile,
overallPercentile, profile: {
profile: { connect: {
connect: { id: input.profileId,
id: input.profileId, },
},
topCompanyOffers: {
connect: topPercentileCompanyOffers.map((offer) => {
return { id: offer.id };
}),
},
topOverallOffers: {
connect: topPercentileOffers.map((offer) => {
return { id: offer.id };
}),
}, },
}, },
topCompanyOffers: { include: {
connect: topPercentileCompanyOffers.map((offer) => { overallHighestOffer: {
return { id: offer.id }; include: {
}), OffersFullTime: {
}, include: {
topOverallOffers: { totalCompensation: true,
connect: topPercentileOffers.map((offer) => { },
return { id: offer.id };
}),
},
},
include: {
overallHighestOffer: {
include: {
OffersFullTime: {
include: {
totalCompensation: true,
}, },
}, OffersIntern: {
OffersIntern: { include: {
include: { monthlySalary: true,
monthlySalary: true, },
}, },
}, company: true,
company: true, profile: {
profile: { include: {
include: { background: true,
background: true, },
}, },
}, },
}, },
}, topCompanyOffers: {
topCompanyOffers: { include: {
include: { OffersFullTime: {
OffersFullTime: { include: {
include: { totalCompensation: true,
totalCompensation: true, },
}, },
}, OffersIntern: {
OffersIntern: { include: {
include: { monthlySalary: true,
monthlySalary: true, },
},
company: true,
profile: {
include: {
background: {
include: {
experiences: {
include: {
company: true,
},
},
},
},
},
}, },
}, },
company: true, },
profile: { topOverallOffers: {
include: { include: {
background: { OffersFullTime: {
include: { include: {
experiences: { totalCompensation: true,
include: { },
company: true, },
OffersIntern: {
include: {
monthlySalary: true,
},
},
company: true,
profile: {
include: {
background: {
include: {
experiences: {
include: {
company: true,
},
}, },
}, },
}, },
@ -427,26 +487,97 @@ export const offersAnalysisRouter = createRouter().query('generate', {
}, },
}, },
}, },
topOverallOffers: { });
include: {
OffersFullTime: { return profileAnalysisDtoMapper(
include: { analysis.id,
totalCompensation: true, analysis.profileId,
overallHighestOffer,
noOfSimilarOffers,
overallPercentile,
topPercentileOffers,
noOfSimilarCompanyOffers,
companyPercentile,
topPercentileCompanyOffers,
);
},
})
.query('get', {
input: z.object({
profileId: z.string(),
}),
async resolve({ ctx, input }) {
const analysis = await ctx.prisma.offersAnalysis.findFirst({
include: {
overallHighestOffer: {
include: {
OffersFullTime: {
include: {
totalCompensation: true,
},
},
OffersIntern: {
include: {
monthlySalary: true,
},
},
company: true,
profile: {
include: {
background: true,
},
}, },
}, },
OffersIntern: { },
include: { topCompanyOffers: {
monthlySalary: true, include: {
OffersFullTime: {
include: {
totalCompensation: true,
},
},
OffersIntern: {
include: {
monthlySalary: true,
},
},
company: true,
profile: {
include: {
background: {
include: {
experiences: {
include: {
company: true,
},
},
},
},
},
}, },
}, },
company: true, },
profile: { topOverallOffers: {
include: { include: {
background: { OffersFullTime: {
include: { include: {
experiences: { totalCompensation: true,
include: { },
company: true, },
OffersIntern: {
include: {
monthlySalary: true,
},
},
company: true,
profile: {
include: {
background: {
include: {
experiences: {
include: {
company: true,
},
}, },
}, },
}, },
@ -455,23 +586,28 @@ export const offersAnalysisRouter = createRouter().query('generate', {
}, },
}, },
}, },
}, where: {
}); profileId: input.profileId,
},
});
return { if (!analysis) {
companyAnalysis: specificAnalysisDtoMapper( throw new TRPCError({
noOfSimilarCompanyOffers, code: 'NOT_FOUND',
companyPercentile, message: 'No analysis found on this profile',
topPercentileCompanyOffers, });
), }
id: analysis.id,
overallAnalysis: specificAnalysisDtoMapper( return profileAnalysisDtoMapper(
noOfSimilarOffers, analysis.id,
overallPercentile, analysis.profileId,
topPercentileOffers, analysis.overallHighestOffer,
), analysis.noOfSimilarOffers,
overallHighestOffer: highestOfferDtoMapper(overallHighestOffer), analysis.overallPercentile,
profileId: analysis.profileId, analysis.topOverallOffers,
}; analysis.noOfSimilarCompanyOffers,
}, analysis.companyPercentile,
}); analysis.topCompanyOffers,
);
},
});

Loading…
Cancel
Save