mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 20:52:00 +08:00
[offers][chore] Save the generate offer profile analysis into db
This commit is contained in:
@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "OffersAnalysis" ALTER COLUMN "overallPercentile" SET DATA TYPE DOUBLE PRECISION,
|
||||
ALTER COLUMN "companyPercentile" SET DATA TYPE DOUBLE PRECISION;
|
@ -365,12 +365,12 @@ model OffersAnalysis {
|
||||
offerId String @unique
|
||||
|
||||
// OVERALL
|
||||
overallPercentile Int
|
||||
overallPercentile Float
|
||||
noOfSimilarOffers Int
|
||||
topOverallOffers OffersOffer[] @relation("TopOverallOffers")
|
||||
|
||||
// Company
|
||||
companyPercentile Int
|
||||
companyPercentile Float
|
||||
noOfSimilarCompanyOffers Int
|
||||
topCompanyOffers OffersOffer[] @relation("TopCompanyOffers")
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ function Test() {
|
||||
],
|
||||
experiences: [
|
||||
{
|
||||
companyId: 'cl95u79f000007im531ysjg79',
|
||||
companyId: 'cl98yuqk80007txhgjtjp8fk4',
|
||||
durationInMonths: 24,
|
||||
jobType: 'FULLTIME',
|
||||
level: 'Junior',
|
||||
@ -151,7 +151,7 @@ function Test() {
|
||||
},
|
||||
},
|
||||
// Comments: '',
|
||||
companyId: 'cl95u79f000007im531ysjg79',
|
||||
companyId: 'cl98yuqk80007txhgjtjp8fk4',
|
||||
jobType: 'FULLTIME',
|
||||
location: 'Singapore, Singapore',
|
||||
monthYearReceived: new Date('2022-09-30T07:58:54.000Z'),
|
||||
@ -180,7 +180,7 @@ function Test() {
|
||||
},
|
||||
},
|
||||
comments: undefined,
|
||||
companyId: 'cl95u79f000007im531ysjg79',
|
||||
companyId: 'cl98yuqk80007txhgjtjp8fk4',
|
||||
jobType: 'FULLTIME',
|
||||
location: 'Singapore, Singapore',
|
||||
monthYearReceived: new Date('2022-09-30T07:58:54.000Z'),
|
||||
@ -261,7 +261,7 @@ function Test() {
|
||||
slug: 'meta',
|
||||
updatedAt: new Date('2022-10-12T16:19:05.196Z'),
|
||||
},
|
||||
companyId: 'cl95u79f000007im531ysjg79',
|
||||
companyId: 'cl98yuqk80007txhgjtjp8fk4',
|
||||
durationInMonths: 24,
|
||||
id: 'cl96stky6002iw32gpt6t87s2',
|
||||
jobType: 'FULLTIME',
|
||||
@ -368,7 +368,7 @@ function Test() {
|
||||
slug: 'meta',
|
||||
updatedAt: new Date('2022-10-12T16:19:05.196Z'),
|
||||
},
|
||||
companyId: 'cl95u79f000007im531ysjg79',
|
||||
companyId: 'cl98yuqk80007txhgjtjp8fk4',
|
||||
id: 'cl976t4de00047iygl0zbce11',
|
||||
jobType: 'FULLTIME',
|
||||
location: 'Singapore, Singapore',
|
||||
@ -421,7 +421,7 @@ function Test() {
|
||||
slug: 'meta',
|
||||
updatedAt: new Date('2022-10-12T16:19:05.196Z'),
|
||||
},
|
||||
companyId: 'cl95u79f000007im531ysjg79',
|
||||
companyId: 'cl98yuqk80007txhgjtjp8fk4',
|
||||
id: 'cl96stky80031w32gau9mu1gs',
|
||||
jobType: 'FULLTIME',
|
||||
location: 'Singapore, Singapore',
|
||||
@ -474,7 +474,7 @@ function Test() {
|
||||
slug: 'meta',
|
||||
updatedAt: new Date('2022-10-12T16:19:05.196Z'),
|
||||
},
|
||||
companyId: 'cl95u79f000007im531ysjg79',
|
||||
companyId: 'cl98yuqk80007txhgjtjp8fk4',
|
||||
id: 'cl96stky9003bw32gc3l955vr',
|
||||
jobType: 'FULLTIME',
|
||||
location: 'Singapore, Singapore',
|
||||
@ -527,7 +527,7 @@ function Test() {
|
||||
slug: 'meta',
|
||||
updatedAt: new Date('2022-10-12T16:19:05.196Z'),
|
||||
},
|
||||
companyId: 'cl95u79f000007im531ysjg79',
|
||||
companyId: 'cl98yuqk80007txhgjtjp8fk4',
|
||||
id: 'cl976wf28000t7iyga4noyz7s',
|
||||
jobType: 'FULLTIME',
|
||||
location: 'Singapore, Singapore',
|
||||
@ -580,7 +580,7 @@ function Test() {
|
||||
slug: 'meta',
|
||||
updatedAt: new Date('2022-10-12T16:19:05.196Z'),
|
||||
},
|
||||
companyId: 'cl95u79f000007im531ysjg79',
|
||||
companyId: 'cl98yuqk80007txhgjtjp8fk4',
|
||||
id: 'cl96tbb3o0051w32gjrpaiiit',
|
||||
jobType: 'FULLTIME',
|
||||
location: 'Singapore, Singapore',
|
||||
|
@ -5,7 +5,7 @@ import { trpc } from '~/utils/trpc';
|
||||
function profileAnalysis() {
|
||||
const analysis = trpc.useQuery([
|
||||
'offers.analysis.generate',
|
||||
{ profileId: 'cl96stky5002ew32gx2kale2x' },
|
||||
{ profileId: 'cl98yxuei002htx1s8lrmwzmy' },
|
||||
]);
|
||||
|
||||
return <div>{JSON.stringify(analysis.data)}</div>;
|
||||
|
@ -8,6 +8,7 @@ import type {
|
||||
OffersOffer,
|
||||
OffersProfile,
|
||||
} from '@prisma/client';
|
||||
import { JobType } from '@prisma/client';
|
||||
import { TRPCError } from '@trpc/server';
|
||||
|
||||
import { createRouter } from '../context';
|
||||
@ -31,14 +32,32 @@ const binarySearchOfferPercentile = (
|
||||
let start = 0;
|
||||
let end = similarOffers.length - 1;
|
||||
|
||||
const salary =
|
||||
offer.jobType === JobType.FULLTIME
|
||||
? offer.OffersFullTime?.totalCompensation.value
|
||||
: offer.OffersIntern?.monthlySalary.value;
|
||||
|
||||
if (!salary) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'Cannot analyse without salary',
|
||||
});
|
||||
}
|
||||
|
||||
while (start <= end) {
|
||||
const mid = Math.floor((start + end) / 2);
|
||||
|
||||
if (similarOffers[mid].id === offer.id) {
|
||||
const similarOffer = similarOffers[mid];
|
||||
const similarSalary =
|
||||
similarOffer.jobType === JobType.FULLTIME
|
||||
? similarOffer.OffersFullTime?.totalCompensation.value
|
||||
: similarOffer.OffersIntern?.monthlySalary.value;
|
||||
|
||||
if (similarSalary === salary) {
|
||||
return mid;
|
||||
}
|
||||
|
||||
if (offer.id < similarOffers[mid].id) {
|
||||
if (salary < similarSalary) {
|
||||
end = mid - 1;
|
||||
} else {
|
||||
start = mid + 1;
|
||||
@ -199,30 +218,29 @@ export const offersAnalysisRouter = createRouter().query('generate', {
|
||||
});
|
||||
|
||||
let similarCompanyOffers = similarOffers.filter(
|
||||
(offer) => offer.companyId === overallHighestOffer.companyId,
|
||||
(offer: { companyId: string }) =>
|
||||
offer.companyId === overallHighestOffer.companyId,
|
||||
);
|
||||
|
||||
// CALCULATE PERCENTILES
|
||||
const highestOfferAgainstOverallIndex = binarySearchOfferPercentile(
|
||||
const overallIndex = binarySearchOfferPercentile(
|
||||
overallHighestOffer,
|
||||
similarOffers,
|
||||
);
|
||||
const highestOfferAgainstOverallPercentile =
|
||||
highestOfferAgainstOverallIndex / similarOffers.length;
|
||||
const overallPercentile = overallIndex / similarOffers.length;
|
||||
|
||||
const highestOfferAgainstCompanyIndex = binarySearchOfferPercentile(
|
||||
const companyIndex = binarySearchOfferPercentile(
|
||||
overallHighestOffer,
|
||||
similarCompanyOffers,
|
||||
);
|
||||
const highestOfferAgainstCompanyPercentile =
|
||||
highestOfferAgainstCompanyIndex / similarCompanyOffers.length;
|
||||
const companyPercentile = companyIndex / similarCompanyOffers.length;
|
||||
|
||||
// FIND TOP >=90 PERCENTILE OFFERS
|
||||
similarOffers = similarOffers.filter(
|
||||
(offer) => offer.id !== overallHighestOffer.id,
|
||||
(offer: { id: string }) => offer.id !== overallHighestOffer.id,
|
||||
);
|
||||
similarCompanyOffers = similarCompanyOffers.filter(
|
||||
(offer) => offer.id !== overallHighestOffer.id,
|
||||
(offer: { id: string }) => offer.id !== overallHighestOffer.id,
|
||||
);
|
||||
|
||||
const noOfSimilarOffers = similarOffers.length;
|
||||
@ -247,18 +265,113 @@ export const offersAnalysisRouter = createRouter().query('generate', {
|
||||
)
|
||||
: similarCompanyOffers;
|
||||
|
||||
return {
|
||||
company: {
|
||||
highestOfferAgainstCompanyPercentile,
|
||||
const analysis = await ctx.prisma.offersAnalysis.create({
|
||||
data: {
|
||||
companyPercentile,
|
||||
noOfSimilarCompanyOffers,
|
||||
topPercentileCompanyOffers,
|
||||
},
|
||||
overall: {
|
||||
highestOfferAgainstOverallPercentile,
|
||||
noOfSimilarOffers,
|
||||
topPercentileOffers,
|
||||
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 };
|
||||
}),
|
||||
},
|
||||
},
|
||||
overallHighestOffer,
|
||||
};
|
||||
include: {
|
||||
overallHighestOffer: {
|
||||
include: {
|
||||
OffersFullTime: {
|
||||
include: {
|
||||
totalCompensation: true,
|
||||
},
|
||||
},
|
||||
OffersIntern: {
|
||||
include: {
|
||||
monthlySalary: true,
|
||||
},
|
||||
},
|
||||
company: true,
|
||||
profile: {
|
||||
include: {
|
||||
background: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
topCompanyOffers: {
|
||||
include: {
|
||||
OffersFullTime: {
|
||||
include: {
|
||||
totalCompensation: true,
|
||||
},
|
||||
},
|
||||
OffersIntern: {
|
||||
include: {
|
||||
monthlySalary: 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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return analysis;
|
||||
},
|
||||
});
|
||||
|
Reference in New Issue
Block a user