From c12c318a0b85890d55d74c7a262c5a48dcc1c4a7 Mon Sep 17 00:00:00 2001
From: BryannYeap <e0543723@u.nus.edu>
Date: Sat, 15 Oct 2022 05:04:38 +0800
Subject: [PATCH] [offers][chore] Save the generate offer profile analysis into
 db

---
 .../migrations/20221014205230_/migration.sql  |   3 +
 apps/portal/prisma/schema.prisma              |   4 +-
 .../src/pages/offers/test/createProfile.tsx   |  18 +-
 .../src/pages/offers/test/profileAnalysis.tsx |   2 +-
 .../router/offers/offers-analysis-router.ts   | 155 +++++++++++++++---
 5 files changed, 149 insertions(+), 33 deletions(-)
 create mode 100644 apps/portal/prisma/migrations/20221014205230_/migration.sql

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 <div>{JSON.stringify(analysis.data)}</div>;
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;
   },
 });