diff --git a/apps/portal/prisma/migrations/20221014205230_/migration.sql b/apps/portal/prisma/migrations/20221014205230_/migration.sql
new file mode 100644
index 00000000..aec827dc
--- /dev/null
+++ b/apps/portal/prisma/migrations/20221014205230_/migration.sql
@@ -0,0 +1,3 @@
+-- AlterTable
+ALTER TABLE "OffersAnalysis" ALTER COLUMN "overallPercentile" SET DATA TYPE DOUBLE PRECISION,
+ALTER COLUMN "companyPercentile" SET DATA TYPE DOUBLE PRECISION;
diff --git a/apps/portal/prisma/schema.prisma b/apps/portal/prisma/schema.prisma
index e30230bb..268d8d27 100644
--- a/apps/portal/prisma/schema.prisma
+++ b/apps/portal/prisma/schema.prisma
@@ -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")
}
diff --git a/apps/portal/src/pages/offers/test/createProfile.tsx b/apps/portal/src/pages/offers/test/createProfile.tsx
index 44740940..f9140452 100644
--- a/apps/portal/src/pages/offers/test/createProfile.tsx
+++ b/apps/portal/src/pages/offers/test/createProfile.tsx
@@ -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',
diff --git a/apps/portal/src/pages/offers/test/profileAnalysis.tsx b/apps/portal/src/pages/offers/test/profileAnalysis.tsx
index 34f55d26..9420653e 100644
--- a/apps/portal/src/pages/offers/test/profileAnalysis.tsx
+++ b/apps/portal/src/pages/offers/test/profileAnalysis.tsx
@@ -5,7 +5,7 @@ import { trpc } from '~/utils/trpc';
function profileAnalysis() {
const analysis = trpc.useQuery([
'offers.analysis.generate',
- { profileId: 'cl96stky5002ew32gx2kale2x' },
+ { profileId: 'cl98yxuei002htx1s8lrmwzmy' },
]);
return
{JSON.stringify(analysis.data)}
;
diff --git a/apps/portal/src/server/router/offers/offers-analysis-router.ts b/apps/portal/src/server/router/offers/offers-analysis-router.ts
index c620cde5..882e84c3 100644
--- a/apps/portal/src/server/router/offers/offers-analysis-router.ts
+++ b/apps/portal/src/server/router/offers/offers-analysis-router.ts
@@ -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;
},
});