From e99e580d5e913dd6678afd9416a4c07e2ce85c1d Mon Sep 17 00:00:00 2001
From: BryannYeap <e0543723@u.nus.edu>
Date: Sat, 15 Oct 2022 08:01:13 +0800
Subject: [PATCH] [offers][feat] Add get offers analysis API

---
 ...ofileAnalysis.tsx => generateAnalysis.tsx} |   4 +-
 .../src/pages/offers/test/getAnalysis.tsx     |  14 +
 .../router/offers/offers-analysis-router.ts   | 666 +++++++++++-------
 3 files changed, 417 insertions(+), 267 deletions(-)
 rename apps/portal/src/pages/offers/test/{profileAnalysis.tsx => generateAnalysis.tsx} (79%)
 create mode 100644 apps/portal/src/pages/offers/test/getAnalysis.tsx

diff --git a/apps/portal/src/pages/offers/test/profileAnalysis.tsx b/apps/portal/src/pages/offers/test/generateAnalysis.tsx
similarity index 79%
rename from apps/portal/src/pages/offers/test/profileAnalysis.tsx
rename to apps/portal/src/pages/offers/test/generateAnalysis.tsx
index 9420653e..fcb969bd 100644
--- a/apps/portal/src/pages/offers/test/profileAnalysis.tsx
+++ b/apps/portal/src/pages/offers/test/generateAnalysis.tsx
@@ -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;
diff --git a/apps/portal/src/pages/offers/test/getAnalysis.tsx b/apps/portal/src/pages/offers/test/getAnalysis.tsx
new file mode 100644
index 00000000..d7ae6797
--- /dev/null
+++ b/apps/portal/src/pages/offers/test/getAnalysis.tsx
@@ -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;
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 e71b3963..7ed9028d 100644
--- a/apps/portal/src/server/router/offers/offers-analysis-router.ts
+++ b/apps/portal/src/server/router/offers/offers-analysis-router.ts
@@ -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,
+      );
+    },
+  });