diff --git a/apps/portal/src/mappers/offers-mappers.ts b/apps/portal/src/mappers/offers-mappers.ts index ea0f624c..f8b0839f 100644 --- a/apps/portal/src/mappers/offers-mappers.ts +++ b/apps/portal/src/mappers/offers-mappers.ts @@ -56,7 +56,7 @@ const analysisOfferDtoMapper = ( location: offer.location, monthYearReceived: offer.monthYearReceived, negotiationStrategy: offer.negotiationStrategy, - previousCompanies: [], + previousCompanies: [], // TODO: Fill this up profileName, specialization: offer.jobType === JobType.FULLTIME @@ -74,10 +74,18 @@ const analysisOfferDtoMapper = ( offer.offersFullTime.totalCompensation.value; analysisOfferDto.income.currency = offer.offersFullTime.totalCompensation.currency; + analysisOfferDto.income.baseValue = + offer.offersFullTime.totalCompensation.baseValue; + analysisOfferDto.income.baseCurrency = + offer.offersFullTime.totalCompensation.baseCurrency; } else if (offer.offersIntern?.monthlySalary) { analysisOfferDto.income.value = offer.offersIntern.monthlySalary.value; analysisOfferDto.income.currency = offer.offersIntern.monthlySalary.currency; + analysisOfferDto.income.baseValue = + offer.offersIntern.monthlySalary.baseValue; + analysisOfferDto.income.baseCurrency = + offer.offersIntern.monthlySalary.baseCurrency; } else { throw new TRPCError({ code: 'NOT_FOUND', diff --git a/apps/portal/src/pages/offers/test/generateAnalysis.tsx b/apps/portal/src/pages/offers/test/generateAnalysis.tsx index dc1fc18c..029ab5fe 100644 --- a/apps/portal/src/pages/offers/test/generateAnalysis.tsx +++ b/apps/portal/src/pages/offers/test/generateAnalysis.tsx @@ -8,7 +8,7 @@ function GenerateAnalysis() { return (
{JSON.stringify( - analysisMutation.mutate({ profileId: 'cl9h23fb1002ftxysli5iziu2' }), + analysisMutation.mutate({ profileId: 'cl9j50xzk008vutfqg6mta2ey' }), )}
); diff --git a/apps/portal/src/pages/offers/test/getAnalysis.tsx b/apps/portal/src/pages/offers/test/getAnalysis.tsx index 4f29ddf7..477ee183 100644 --- a/apps/portal/src/pages/offers/test/getAnalysis.tsx +++ b/apps/portal/src/pages/offers/test/getAnalysis.tsx @@ -5,7 +5,7 @@ import { trpc } from '~/utils/trpc'; function GetAnalysis() { const analysis = trpc.useQuery([ 'offers.analysis.get', - { profileId: 'cl9h23fb1002ftxysli5iziu2' }, + { profileId: 'cl9j50xzk008vutfqg6mta2ey' }, ]); return
{JSON.stringify(analysis.data)}
; diff --git a/apps/portal/src/pages/offers/test/listOffers.tsx b/apps/portal/src/pages/offers/test/listOffers.tsx index 747f5102..b59f50c6 100644 --- a/apps/portal/src/pages/offers/test/listOffers.tsx +++ b/apps/portal/src/pages/offers/test/listOffers.tsx @@ -6,6 +6,7 @@ function Test() { const data = trpc.useQuery([ 'offers.list', { + currency: 'SGD', limit: 100, location: 'Singapore, Singapore', offset: 0, 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 37b0d83b..35bb9924 100644 --- a/apps/portal/src/server/router/offers/offers-analysis-router.ts +++ b/apps/portal/src/server/router/offers/offers-analysis-router.ts @@ -187,14 +187,14 @@ export const offersAnalysisRouter = createRouter() { offersFullTime: { totalCompensation: { - value: 'desc', + baseValue: 'desc', }, }, }, { offersIntern: { monthlySalary: { - value: 'desc', + baseValue: 'desc', }, }, }, @@ -216,11 +216,11 @@ export const offersAnalysisRouter = createRouter() // TODO: Shift yoe out of background to make it mandatory if ( !overallHighestOffer.profile.background || - overallHighestOffer.profile.background.totalYoe === undefined + overallHighestOffer.profile.background.totalYoe == null ) { throw new TRPCError({ - code: 'BAD_REQUEST', - message: 'Cannot analyse without YOE', + code: 'NOT_FOUND', + message: 'YOE not found', }); } @@ -257,14 +257,14 @@ export const offersAnalysisRouter = createRouter() { offersFullTime: { totalCompensation: { - value: 'desc', + baseValue: 'desc', }, }, }, { offersIntern: { monthlySalary: { - value: 'desc', + baseValue: 'desc', }, }, }, @@ -279,12 +279,10 @@ export const offersAnalysisRouter = createRouter() { offersFullTime: { level: overallHighestOffer.offersFullTime?.level, - specialization: - overallHighestOffer.offersFullTime?.specialization, + title: overallHighestOffer.offersFullTime?.title, }, offersIntern: { - specialization: - overallHighestOffer.offersIntern?.specialization, + title: overallHighestOffer.offersIntern?.title, }, }, ], @@ -317,7 +315,7 @@ export const offersAnalysisRouter = createRouter() similarOffers, ); const overallPercentile = - similarOffers.length === 0 ? 0 : overallIndex / similarOffers.length; + similarOffers.length === 0 ? 1.0 : overallIndex / similarOffers.length; const companyIndex = searchOfferPercentile( overallHighestOffer, @@ -325,10 +323,11 @@ export const offersAnalysisRouter = createRouter() ); const companyPercentile = similarCompanyOffers.length === 0 - ? 0 + ? 1.0 : companyIndex / similarCompanyOffers.length; - // FIND TOP >=90 PERCENTILE OFFERS + // FIND TOP >=90 PERCENTILE OFFERS, DOESN'T GIVE 100th PERCENTILE + // e.g. If there only 4 offers, it gives the 2nd and 3rd offer similarOffers = similarOffers.filter( (offer) => offer.id !== overallHighestOffer.id, ); @@ -337,10 +336,9 @@ export const offersAnalysisRouter = createRouter() ); const noOfSimilarOffers = similarOffers.length; - const similarOffers90PercentileIndex = - Math.floor(noOfSimilarOffers * 0.9) - 1; + const similarOffers90PercentileIndex = Math.ceil(noOfSimilarOffers * 0.1); const topPercentileOffers = - noOfSimilarOffers > 1 + noOfSimilarOffers > 2 ? similarOffers.slice( similarOffers90PercentileIndex, similarOffers90PercentileIndex + 2, @@ -348,10 +346,11 @@ export const offersAnalysisRouter = createRouter() : similarOffers; const noOfSimilarCompanyOffers = similarCompanyOffers.length; - const similarCompanyOffers90PercentileIndex = - Math.floor(noOfSimilarCompanyOffers * 0.9) - 1; + const similarCompanyOffers90PercentileIndex = Math.ceil( + noOfSimilarCompanyOffers * 0.1, + ); const topPercentileCompanyOffers = - noOfSimilarCompanyOffers > 1 + noOfSimilarCompanyOffers > 2 ? similarCompanyOffers.slice( similarCompanyOffers90PercentileIndex, similarCompanyOffers90PercentileIndex + 2, diff --git a/apps/portal/src/server/router/offers/offers.ts b/apps/portal/src/server/router/offers/offers.ts index a68b19dd..284063e9 100644 --- a/apps/portal/src/server/router/offers/offers.ts +++ b/apps/portal/src/server/router/offers/offers.ts @@ -5,7 +5,7 @@ import { dashboardOfferDtoMapper, getOffersResponseMapper, } from '~/mappers/offers-mappers'; -import { convert } from '~/utils/offers/currency/currency-exchange'; +import { convertWithDate } from '~/utils/offers/currency/currency-exchange'; import { Currency } from '~/utils/offers/currency/CurrencyEnum'; import { createValidationRegex } from '~/utils/offers/zodRegex'; @@ -106,7 +106,7 @@ export const offersRouter = createRouter().query('list', { ? { offersIntern: { monthlySalary: { - value: order, + baseValue: order, }, }, } @@ -141,7 +141,7 @@ export const offersRouter = createRouter().query('list', { { offersIntern: { monthlySalary: { - value: { + baseValue: { gte: input.salaryMin ?? undefined, lte: input.salaryMax ?? undefined, }, @@ -210,7 +210,7 @@ export const offersRouter = createRouter().query('list', { ? { offersFullTime: { totalCompensation: { - value: order, + baseValue: order, }, }, } @@ -250,7 +250,7 @@ export const offersRouter = createRouter().query('list', { { offersFullTime: { totalCompensation: { - value: { + baseValue: { gte: input.salaryMin ?? undefined, lte: input.salaryMax ?? undefined, }, @@ -288,36 +288,42 @@ export const offersRouter = createRouter().query('list', { if (currency != null && currency in Currency) { data = await Promise.all( data.map(async (offer) => { - if (offer.offersFullTime?.totalCompensation) { - offer.offersFullTime.totalCompensation.value = await convert( - offer.offersFullTime.totalCompensation.value, - offer.offersFullTime.totalCompensation.currency, - currency, - ); + if (offer.offersFullTime?.totalCompensation != null) { + offer.offersFullTime.totalCompensation.value = + await convertWithDate( + offer.offersFullTime.totalCompensation.value, + offer.offersFullTime.totalCompensation.currency, + currency, + offer.offersFullTime.totalCompensation.updatedAt, + ); offer.offersFullTime.totalCompensation.currency = currency; - offer.offersFullTime.baseSalary.value = await convert( - offer.offersFullTime.totalCompensation.value, - offer.offersFullTime.totalCompensation.currency, + offer.offersFullTime.baseSalary.value = await convertWithDate( + offer.offersFullTime.baseSalary.value, + offer.offersFullTime.baseSalary.currency, currency, + offer.offersFullTime.baseSalary.updatedAt, ); offer.offersFullTime.baseSalary.currency = currency; - offer.offersFullTime.stocks.value = await convert( - offer.offersFullTime.totalCompensation.value, - offer.offersFullTime.totalCompensation.currency, + offer.offersFullTime.stocks.value = await convertWithDate( + offer.offersFullTime.stocks.value, + offer.offersFullTime.stocks.currency, currency, + offer.offersFullTime.stocks.updatedAt, ); offer.offersFullTime.stocks.currency = currency; - offer.offersFullTime.bonus.value = await convert( - offer.offersFullTime.totalCompensation.value, - offer.offersFullTime.totalCompensation.currency, + offer.offersFullTime.bonus.value = await convertWithDate( + offer.offersFullTime.bonus.value, + offer.offersFullTime.bonus.currency, currency, + offer.offersFullTime.bonus.updatedAt, ); offer.offersFullTime.bonus.currency = currency; - } else if (offer.offersIntern?.monthlySalary) { - offer.offersIntern.monthlySalary.value = await convert( + } else if (offer.offersIntern?.monthlySalary != null) { + offer.offersIntern.monthlySalary.value = await convertWithDate( offer.offersIntern.monthlySalary.value, offer.offersIntern.monthlySalary.currency, currency, + offer.offersIntern.monthlySalary.updatedAt, ); offer.offersIntern.monthlySalary.currency = currency; } else { diff --git a/apps/portal/src/utils/offers/currency/currency-exchange.ts b/apps/portal/src/utils/offers/currency/currency-exchange.ts index 4c94209a..0f642100 100644 --- a/apps/portal/src/utils/offers/currency/currency-exchange.ts +++ b/apps/portal/src/utils/offers/currency/currency-exchange.ts @@ -1,4 +1,5 @@ // API from https://github.com/fawazahmed0/currency-api#readme + export const convert = async ( value: number, fromCurrency: string, @@ -16,3 +17,33 @@ export const convert = async ( .then((res) => res.json()) .then((data) => value * data[toCurrency]); }; +// https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@{apiVersion}/{date}/{endpoint} + +export const convertWithDate = async ( + value: number, + fromCurrency: string, + toCurrency: string, + date: Date, +) => { + if (new Date().toDateString === date.toDateString) { + return await convert(value, fromCurrency, toCurrency); + } + + fromCurrency = fromCurrency.trim().toLowerCase(); + toCurrency = toCurrency.trim().toLowerCase(); + + // Format date to YYYY-MM-DD + const formattedDate = date.toJSON().substring(0, 10); + + const url = [ + 'https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1', + formattedDate, + 'currencies', + fromCurrency, + toCurrency, + ].join('/'); + + return await fetch(url + '.json') + .then((res) => res.json()) + .then((data) => value * data[toCurrency]); +};