mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-28 04:33:42 +08:00
[offers][fix] Fix analysis and offers API to accommodate new fields in OffersCurrency
This commit is contained in:
@ -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',
|
||||
|
@ -8,7 +8,7 @@ function GenerateAnalysis() {
|
||||
return (
|
||||
<div>
|
||||
{JSON.stringify(
|
||||
analysisMutation.mutate({ profileId: 'cl9h23fb1002ftxysli5iziu2' }),
|
||||
analysisMutation.mutate({ profileId: 'cl9j50xzk008vutfqg6mta2ey' }),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
@ -5,7 +5,7 @@ import { trpc } from '~/utils/trpc';
|
||||
function GetAnalysis() {
|
||||
const analysis = trpc.useQuery([
|
||||
'offers.analysis.get',
|
||||
{ profileId: 'cl9h23fb1002ftxysli5iziu2' },
|
||||
{ profileId: 'cl9j50xzk008vutfqg6mta2ey' },
|
||||
]);
|
||||
|
||||
return <div>{JSON.stringify(analysis.data)}</div>;
|
||||
|
@ -6,6 +6,7 @@ function Test() {
|
||||
const data = trpc.useQuery([
|
||||
'offers.list',
|
||||
{
|
||||
currency: 'SGD',
|
||||
limit: 100,
|
||||
location: 'Singapore, Singapore',
|
||||
offset: 0,
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
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 {
|
||||
|
@ -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]);
|
||||
};
|
||||
|
Reference in New Issue
Block a user