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

Loading…
Cancel
Save