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,
|
location: offer.location,
|
||||||
monthYearReceived: offer.monthYearReceived,
|
monthYearReceived: offer.monthYearReceived,
|
||||||
negotiationStrategy: offer.negotiationStrategy,
|
negotiationStrategy: offer.negotiationStrategy,
|
||||||
previousCompanies: [],
|
previousCompanies: [], // TODO: Fill this up
|
||||||
profileName,
|
profileName,
|
||||||
specialization:
|
specialization:
|
||||||
offer.jobType === JobType.FULLTIME
|
offer.jobType === JobType.FULLTIME
|
||||||
@ -74,10 +74,18 @@ const analysisOfferDtoMapper = (
|
|||||||
offer.offersFullTime.totalCompensation.value;
|
offer.offersFullTime.totalCompensation.value;
|
||||||
analysisOfferDto.income.currency =
|
analysisOfferDto.income.currency =
|
||||||
offer.offersFullTime.totalCompensation.currency;
|
offer.offersFullTime.totalCompensation.currency;
|
||||||
|
analysisOfferDto.income.baseValue =
|
||||||
|
offer.offersFullTime.totalCompensation.baseValue;
|
||||||
|
analysisOfferDto.income.baseCurrency =
|
||||||
|
offer.offersFullTime.totalCompensation.baseCurrency;
|
||||||
} else if (offer.offersIntern?.monthlySalary) {
|
} else if (offer.offersIntern?.monthlySalary) {
|
||||||
analysisOfferDto.income.value = offer.offersIntern.monthlySalary.value;
|
analysisOfferDto.income.value = offer.offersIntern.monthlySalary.value;
|
||||||
analysisOfferDto.income.currency =
|
analysisOfferDto.income.currency =
|
||||||
offer.offersIntern.monthlySalary.currency;
|
offer.offersIntern.monthlySalary.currency;
|
||||||
|
analysisOfferDto.income.baseValue =
|
||||||
|
offer.offersIntern.monthlySalary.baseValue;
|
||||||
|
analysisOfferDto.income.baseCurrency =
|
||||||
|
offer.offersIntern.monthlySalary.baseCurrency;
|
||||||
} else {
|
} else {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'NOT_FOUND',
|
code: 'NOT_FOUND',
|
||||||
|
@ -8,7 +8,7 @@ function GenerateAnalysis() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{JSON.stringify(
|
{JSON.stringify(
|
||||||
analysisMutation.mutate({ profileId: 'cl9h23fb1002ftxysli5iziu2' }),
|
analysisMutation.mutate({ profileId: 'cl9j50xzk008vutfqg6mta2ey' }),
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -5,7 +5,7 @@ import { trpc } from '~/utils/trpc';
|
|||||||
function GetAnalysis() {
|
function GetAnalysis() {
|
||||||
const analysis = trpc.useQuery([
|
const analysis = trpc.useQuery([
|
||||||
'offers.analysis.get',
|
'offers.analysis.get',
|
||||||
{ profileId: 'cl9h23fb1002ftxysli5iziu2' },
|
{ profileId: 'cl9j50xzk008vutfqg6mta2ey' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return <div>{JSON.stringify(analysis.data)}</div>;
|
return <div>{JSON.stringify(analysis.data)}</div>;
|
||||||
|
@ -6,6 +6,7 @@ function Test() {
|
|||||||
const data = trpc.useQuery([
|
const data = trpc.useQuery([
|
||||||
'offers.list',
|
'offers.list',
|
||||||
{
|
{
|
||||||
|
currency: 'SGD',
|
||||||
limit: 100,
|
limit: 100,
|
||||||
location: 'Singapore, Singapore',
|
location: 'Singapore, Singapore',
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
@ -187,14 +187,14 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
{
|
{
|
||||||
offersFullTime: {
|
offersFullTime: {
|
||||||
totalCompensation: {
|
totalCompensation: {
|
||||||
value: 'desc',
|
baseValue: 'desc',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
offersIntern: {
|
offersIntern: {
|
||||||
monthlySalary: {
|
monthlySalary: {
|
||||||
value: 'desc',
|
baseValue: 'desc',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -216,11 +216,11 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
// TODO: Shift yoe out of background to make it mandatory
|
// TODO: Shift yoe out of background to make it mandatory
|
||||||
if (
|
if (
|
||||||
!overallHighestOffer.profile.background ||
|
!overallHighestOffer.profile.background ||
|
||||||
overallHighestOffer.profile.background.totalYoe === undefined
|
overallHighestOffer.profile.background.totalYoe == null
|
||||||
) {
|
) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
code: 'BAD_REQUEST',
|
code: 'NOT_FOUND',
|
||||||
message: 'Cannot analyse without YOE',
|
message: 'YOE not found',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,14 +257,14 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
{
|
{
|
||||||
offersFullTime: {
|
offersFullTime: {
|
||||||
totalCompensation: {
|
totalCompensation: {
|
||||||
value: 'desc',
|
baseValue: 'desc',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
offersIntern: {
|
offersIntern: {
|
||||||
monthlySalary: {
|
monthlySalary: {
|
||||||
value: 'desc',
|
baseValue: 'desc',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -279,12 +279,10 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
{
|
{
|
||||||
offersFullTime: {
|
offersFullTime: {
|
||||||
level: overallHighestOffer.offersFullTime?.level,
|
level: overallHighestOffer.offersFullTime?.level,
|
||||||
specialization:
|
title: overallHighestOffer.offersFullTime?.title,
|
||||||
overallHighestOffer.offersFullTime?.specialization,
|
|
||||||
},
|
},
|
||||||
offersIntern: {
|
offersIntern: {
|
||||||
specialization:
|
title: overallHighestOffer.offersIntern?.title,
|
||||||
overallHighestOffer.offersIntern?.specialization,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -317,7 +315,7 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
similarOffers,
|
similarOffers,
|
||||||
);
|
);
|
||||||
const overallPercentile =
|
const overallPercentile =
|
||||||
similarOffers.length === 0 ? 0 : overallIndex / similarOffers.length;
|
similarOffers.length === 0 ? 1.0 : overallIndex / similarOffers.length;
|
||||||
|
|
||||||
const companyIndex = searchOfferPercentile(
|
const companyIndex = searchOfferPercentile(
|
||||||
overallHighestOffer,
|
overallHighestOffer,
|
||||||
@ -325,10 +323,11 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
);
|
);
|
||||||
const companyPercentile =
|
const companyPercentile =
|
||||||
similarCompanyOffers.length === 0
|
similarCompanyOffers.length === 0
|
||||||
? 0
|
? 1.0
|
||||||
: companyIndex / similarCompanyOffers.length;
|
: 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(
|
similarOffers = similarOffers.filter(
|
||||||
(offer) => offer.id !== overallHighestOffer.id,
|
(offer) => offer.id !== overallHighestOffer.id,
|
||||||
);
|
);
|
||||||
@ -337,10 +336,9 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
);
|
);
|
||||||
|
|
||||||
const noOfSimilarOffers = similarOffers.length;
|
const noOfSimilarOffers = similarOffers.length;
|
||||||
const similarOffers90PercentileIndex =
|
const similarOffers90PercentileIndex = Math.ceil(noOfSimilarOffers * 0.1);
|
||||||
Math.floor(noOfSimilarOffers * 0.9) - 1;
|
|
||||||
const topPercentileOffers =
|
const topPercentileOffers =
|
||||||
noOfSimilarOffers > 1
|
noOfSimilarOffers > 2
|
||||||
? similarOffers.slice(
|
? similarOffers.slice(
|
||||||
similarOffers90PercentileIndex,
|
similarOffers90PercentileIndex,
|
||||||
similarOffers90PercentileIndex + 2,
|
similarOffers90PercentileIndex + 2,
|
||||||
@ -348,10 +346,11 @@ export const offersAnalysisRouter = createRouter()
|
|||||||
: similarOffers;
|
: similarOffers;
|
||||||
|
|
||||||
const noOfSimilarCompanyOffers = similarCompanyOffers.length;
|
const noOfSimilarCompanyOffers = similarCompanyOffers.length;
|
||||||
const similarCompanyOffers90PercentileIndex =
|
const similarCompanyOffers90PercentileIndex = Math.ceil(
|
||||||
Math.floor(noOfSimilarCompanyOffers * 0.9) - 1;
|
noOfSimilarCompanyOffers * 0.1,
|
||||||
|
);
|
||||||
const topPercentileCompanyOffers =
|
const topPercentileCompanyOffers =
|
||||||
noOfSimilarCompanyOffers > 1
|
noOfSimilarCompanyOffers > 2
|
||||||
? similarCompanyOffers.slice(
|
? similarCompanyOffers.slice(
|
||||||
similarCompanyOffers90PercentileIndex,
|
similarCompanyOffers90PercentileIndex,
|
||||||
similarCompanyOffers90PercentileIndex + 2,
|
similarCompanyOffers90PercentileIndex + 2,
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
dashboardOfferDtoMapper,
|
dashboardOfferDtoMapper,
|
||||||
getOffersResponseMapper,
|
getOffersResponseMapper,
|
||||||
} from '~/mappers/offers-mappers';
|
} 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 { Currency } from '~/utils/offers/currency/CurrencyEnum';
|
||||||
import { createValidationRegex } from '~/utils/offers/zodRegex';
|
import { createValidationRegex } from '~/utils/offers/zodRegex';
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ export const offersRouter = createRouter().query('list', {
|
|||||||
? {
|
? {
|
||||||
offersIntern: {
|
offersIntern: {
|
||||||
monthlySalary: {
|
monthlySalary: {
|
||||||
value: order,
|
baseValue: order,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -141,7 +141,7 @@ export const offersRouter = createRouter().query('list', {
|
|||||||
{
|
{
|
||||||
offersIntern: {
|
offersIntern: {
|
||||||
monthlySalary: {
|
monthlySalary: {
|
||||||
value: {
|
baseValue: {
|
||||||
gte: input.salaryMin ?? undefined,
|
gte: input.salaryMin ?? undefined,
|
||||||
lte: input.salaryMax ?? undefined,
|
lte: input.salaryMax ?? undefined,
|
||||||
},
|
},
|
||||||
@ -210,7 +210,7 @@ export const offersRouter = createRouter().query('list', {
|
|||||||
? {
|
? {
|
||||||
offersFullTime: {
|
offersFullTime: {
|
||||||
totalCompensation: {
|
totalCompensation: {
|
||||||
value: order,
|
baseValue: order,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -250,7 +250,7 @@ export const offersRouter = createRouter().query('list', {
|
|||||||
{
|
{
|
||||||
offersFullTime: {
|
offersFullTime: {
|
||||||
totalCompensation: {
|
totalCompensation: {
|
||||||
value: {
|
baseValue: {
|
||||||
gte: input.salaryMin ?? undefined,
|
gte: input.salaryMin ?? undefined,
|
||||||
lte: input.salaryMax ?? undefined,
|
lte: input.salaryMax ?? undefined,
|
||||||
},
|
},
|
||||||
@ -288,36 +288,42 @@ export const offersRouter = createRouter().query('list', {
|
|||||||
if (currency != null && currency in Currency) {
|
if (currency != null && currency in Currency) {
|
||||||
data = await Promise.all(
|
data = await Promise.all(
|
||||||
data.map(async (offer) => {
|
data.map(async (offer) => {
|
||||||
if (offer.offersFullTime?.totalCompensation) {
|
if (offer.offersFullTime?.totalCompensation != null) {
|
||||||
offer.offersFullTime.totalCompensation.value = await convert(
|
offer.offersFullTime.totalCompensation.value =
|
||||||
offer.offersFullTime.totalCompensation.value,
|
await convertWithDate(
|
||||||
offer.offersFullTime.totalCompensation.currency,
|
offer.offersFullTime.totalCompensation.value,
|
||||||
currency,
|
offer.offersFullTime.totalCompensation.currency,
|
||||||
);
|
currency,
|
||||||
|
offer.offersFullTime.totalCompensation.updatedAt,
|
||||||
|
);
|
||||||
offer.offersFullTime.totalCompensation.currency = currency;
|
offer.offersFullTime.totalCompensation.currency = currency;
|
||||||
offer.offersFullTime.baseSalary.value = await convert(
|
offer.offersFullTime.baseSalary.value = await convertWithDate(
|
||||||
offer.offersFullTime.totalCompensation.value,
|
offer.offersFullTime.baseSalary.value,
|
||||||
offer.offersFullTime.totalCompensation.currency,
|
offer.offersFullTime.baseSalary.currency,
|
||||||
currency,
|
currency,
|
||||||
|
offer.offersFullTime.baseSalary.updatedAt,
|
||||||
);
|
);
|
||||||
offer.offersFullTime.baseSalary.currency = currency;
|
offer.offersFullTime.baseSalary.currency = currency;
|
||||||
offer.offersFullTime.stocks.value = await convert(
|
offer.offersFullTime.stocks.value = await convertWithDate(
|
||||||
offer.offersFullTime.totalCompensation.value,
|
offer.offersFullTime.stocks.value,
|
||||||
offer.offersFullTime.totalCompensation.currency,
|
offer.offersFullTime.stocks.currency,
|
||||||
currency,
|
currency,
|
||||||
|
offer.offersFullTime.stocks.updatedAt,
|
||||||
);
|
);
|
||||||
offer.offersFullTime.stocks.currency = currency;
|
offer.offersFullTime.stocks.currency = currency;
|
||||||
offer.offersFullTime.bonus.value = await convert(
|
offer.offersFullTime.bonus.value = await convertWithDate(
|
||||||
offer.offersFullTime.totalCompensation.value,
|
offer.offersFullTime.bonus.value,
|
||||||
offer.offersFullTime.totalCompensation.currency,
|
offer.offersFullTime.bonus.currency,
|
||||||
currency,
|
currency,
|
||||||
|
offer.offersFullTime.bonus.updatedAt,
|
||||||
);
|
);
|
||||||
offer.offersFullTime.bonus.currency = currency;
|
offer.offersFullTime.bonus.currency = currency;
|
||||||
} else if (offer.offersIntern?.monthlySalary) {
|
} else if (offer.offersIntern?.monthlySalary != null) {
|
||||||
offer.offersIntern.monthlySalary.value = await convert(
|
offer.offersIntern.monthlySalary.value = await convertWithDate(
|
||||||
offer.offersIntern.monthlySalary.value,
|
offer.offersIntern.monthlySalary.value,
|
||||||
offer.offersIntern.monthlySalary.currency,
|
offer.offersIntern.monthlySalary.currency,
|
||||||
currency,
|
currency,
|
||||||
|
offer.offersIntern.monthlySalary.updatedAt,
|
||||||
);
|
);
|
||||||
offer.offersIntern.monthlySalary.currency = currency;
|
offer.offersIntern.monthlySalary.currency = currency;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// API from https://github.com/fawazahmed0/currency-api#readme
|
// API from https://github.com/fawazahmed0/currency-api#readme
|
||||||
|
|
||||||
export const convert = async (
|
export const convert = async (
|
||||||
value: number,
|
value: number,
|
||||||
fromCurrency: string,
|
fromCurrency: string,
|
||||||
@ -16,3 +17,33 @@ export const convert = async (
|
|||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => value * data[toCurrency]);
|
.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