[offers][chore] Allow analysis to analyse all companies in the backend

pull/501/head^2
Bryann Yeap Kok Keong 3 years ago
parent 10a049f627
commit dbe207f391

@ -333,9 +333,8 @@ model OffersOffer {
offersFullTime OffersFullTime? @relation(fields: [offersFullTimeId], references: [id], onDelete: Cascade)
offersFullTimeId String? @unique
OffersAnalysis OffersAnalysis? @relation("HighestOverallOffer")
OffersAnalysisTopOverallOffers OffersAnalysis[] @relation("TopOverallOffers")
OffersAnalysisTopCompanyOffers OffersAnalysis[] @relation("TopCompanyOffers")
offersAnalysis OffersAnalysis? @relation("HighestOverallOffer")
offersAnalysisUnit OffersAnalysisUnit[]
}
model OffersIntern {
@ -376,14 +375,21 @@ model OffersAnalysis {
offerId String @unique
// OVERALL
overallPercentile Float
overallAnalysis OffersAnalysisUnit @relation("OverallAnalysis", fields: [overallAnalysisUnitId], references: [id])
overallAnalysisUnitId String
companyAnalysis OffersAnalysisUnit[] @relation("CompanyAnalysis")
}
model OffersAnalysisUnit {
id String @id @default(cuid())
percentile Float
noOfSimilarOffers Int
topOverallOffers OffersOffer[] @relation("TopOverallOffers")
topSimilarOffers OffersOffer[]
// Company
companyPercentile Float
noOfSimilarCompanyOffers Int
topCompanyOffers OffersOffer[] @relation("TopCompanyOffers")
offersAnalysisOverall OffersAnalysis[] @relation("OverallAnalysis")
offersAnalysisCompany OffersAnalysis[] @relation("CompanyAnalysis")
}
// End of Offers project models.

@ -1,6 +1,7 @@
import type {
Company,
OffersAnalysis,
OffersAnalysisUnit,
OffersBackground,
OffersCurrency,
OffersEducation,
@ -18,9 +19,9 @@ import { TRPCError } from '@trpc/server';
import type {
AddToProfileResponse,
Analysis,
AnalysisHighestOffer,
AnalysisOffer,
AnalysisUnit,
Background,
CreateOfferProfileResponse,
DashboardOffer,
@ -35,7 +36,8 @@ import type {
SpecificYoe,
UserProfile,
UserProfileOffer,
Valuation} from '~/types/offers';
Valuation,
} from '~/types/offers';
const analysisOfferDtoMapper = (
offer: OffersOffer & {
@ -110,10 +112,9 @@ const analysisOfferDtoMapper = (
return analysisOfferDto;
};
const analysisDtoMapper = (
noOfOffers: number,
percentile: number,
topPercentileOffers: Array<
const analysisUnitDtoMapper = (
analysisUnit: OffersAnalysisUnit & {
topSimilarOffers: Array<
OffersOffer & {
company: Company;
offersFullTime:
@ -130,12 +131,13 @@ const analysisDtoMapper = (
| null;
};
}
>,
>;
},
) => {
const analysisDto: Analysis = {
noOfOffers,
percentile,
topPercentileOffers: topPercentileOffers.map((offer) =>
const analysisDto: AnalysisUnit = {
noOfOffers: analysisUnit.noOfSimilarOffers,
percentile: analysisUnit.percentile,
topPercentileOffers: analysisUnit.topSimilarOffers.map((offer) =>
analysisOfferDtoMapper(offer),
),
};
@ -165,17 +167,9 @@ const analysisHighestOfferDtoMapper = (
export const profileAnalysisDtoMapper = (
analysis:
| (OffersAnalysis & {
overallHighestOffer: OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern:
| (OffersIntern & { monthlySalary: OffersCurrency })
| null;
profile: OffersProfile & { background: OffersBackground | null };
};
topCompanyOffers: Array<
companyAnalysis: Array<
OffersAnalysisUnit & {
topSimilarOffers: Array<
OffersOffer & {
company: Company;
offersFullTime:
@ -195,7 +189,10 @@ export const profileAnalysisDtoMapper = (
};
}
>;
topOverallOffers: Array<
}
>;
overallAnalysis: OffersAnalysisUnit & {
topSimilarOffers: Array<
OffersOffer & {
company: Company;
offersFullTime:
@ -215,6 +212,17 @@ export const profileAnalysisDtoMapper = (
};
}
>;
};
overallHighestOffer: OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern:
| (OffersIntern & { monthlySalary: OffersCurrency })
| null;
profile: OffersProfile & { background: OffersBackground | null };
};
})
| null,
) => {
@ -223,19 +231,11 @@ export const profileAnalysisDtoMapper = (
}
const profileAnalysisDto: ProfileAnalysis = {
companyAnalysis: [
analysisDtoMapper(
analysis.noOfSimilarCompanyOffers,
analysis.companyPercentile,
analysis.topCompanyOffers,
companyAnalysis: analysis.companyAnalysis.map((analysisUnit) =>
analysisUnitDtoMapper(analysisUnit),
),
],
id: analysis.id,
overallAnalysis: analysisDtoMapper(
analysis.noOfSimilarOffers,
analysis.overallPercentile,
analysis.topOverallOffers,
),
overallAnalysis: analysisUnitDtoMapper(analysis.overallAnalysis),
overallHighestOffer: analysisHighestOfferDtoMapper(
analysis.overallHighestOffer,
),
@ -441,17 +441,9 @@ export const profileDtoMapper = (
profile: OffersProfile & {
analysis:
| (OffersAnalysis & {
overallHighestOffer: OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern:
| (OffersIntern & { monthlySalary: OffersCurrency })
| null;
profile: OffersProfile & { background: OffersBackground | null };
};
topCompanyOffers: Array<
companyAnalysis: Array<
OffersAnalysisUnit & {
topSimilarOffers: Array<
OffersOffer & {
company: Company;
offersFullTime:
@ -471,7 +463,10 @@ export const profileDtoMapper = (
};
}
>;
topOverallOffers: Array<
}
>;
overallAnalysis: OffersAnalysisUnit & {
topSimilarOffers: Array<
OffersOffer & {
company: Company;
offersFullTime:
@ -491,6 +486,17 @@ export const profileDtoMapper = (
};
}
>;
};
overallHighestOffer: OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern:
| (OffersIntern & { monthlySalary: OffersCurrency })
| null;
profile: OffersProfile & { background: OffersBackground | null };
};
})
| null;
background:
@ -530,7 +536,7 @@ export const profileDtoMapper = (
user: User | null;
},
inputToken: string | undefined,
inputUserId: string | null | undefined
inputUserId: string | null | undefined,
) => {
const profileDto: Profile = {
analysis: profileAnalysisDtoMapper(profile.analysis),
@ -547,7 +553,7 @@ export const profileDtoMapper = (
profileDto.editToken = profile.editToken ?? null;
profileDto.isEditable = true;
const users = profile.user
const users = profile.user;
// TODO: BRYANN UNCOMMENT THIS ONCE U CHANGE THE SCHEMA
// for (let i = 0; i < users.length; i++) {
@ -558,7 +564,7 @@ export const profileDtoMapper = (
// TODO: REMOVE THIS ONCE U CHANGE THE SCHEMA
if (users?.id === inputUserId) {
profileDto.isSaved = true
profileDto.isSaved = true;
}
}
@ -645,38 +651,53 @@ export const getOffersResponseMapper = (
return getOffersResponse;
};
export const getUserProfileResponseMapper = (res: User & {
OffersProfile: Array<OffersProfile & {
offers: Array<OffersOffer & {
export const getUserProfileResponseMapper = (
res:
| (User & {
OffersProfile: Array<
OffersProfile & {
offers: Array<
OffersOffer & {
company: Company;
offersFullTime: (OffersFullTime & { totalCompensation: OffersCurrency }) | null;
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
}>;
}>;
} | null): Array<UserProfile> => {
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern:
| (OffersIntern & { monthlySalary: OffersCurrency })
| null;
}
>;
}
>;
})
| null,
): Array<UserProfile> => {
if (res) {
return res.OffersProfile.map((profile) => {
return {
createdAt: profile.createdAt,
id: profile.id,
offers: profile.offers.map((offer) => {
return userProfileOfferDtoMapper(offer)
return userProfileOfferDtoMapper(offer);
}),
profileName: profile.profileName,
token: profile.editToken
}
})
token: profile.editToken,
};
});
}
return []
}
return [];
};
const userProfileOfferDtoMapper = (
offer: OffersOffer & {
company: Company;
offersFullTime: (OffersFullTime & { totalCompensation: OffersCurrency }) | null;
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
}): UserProfileOffer => {
},
): UserProfileOffer => {
const mappedOffer: UserProfileOffer = {
company: offersCompanyDtoMapper(offer.company),
id: offer.id,
@ -695,11 +716,10 @@ const userProfileOfferDtoMapper = (
offer.jobType === JobType.FULLTIME
? offer.offersFullTime?.title ?? ''
: offer.offersIntern?.title ?? '',
}
};
if (offer.offersFullTime?.totalCompensation) {
mappedOffer.income.value =
offer.offersFullTime.totalCompensation.value;
mappedOffer.income.value = offer.offersFullTime.totalCompensation.value;
mappedOffer.income.currency =
offer.offersFullTime.totalCompensation.currency;
mappedOffer.income.id = offer.offersFullTime.totalCompensation.id;
@ -709,11 +729,9 @@ const userProfileOfferDtoMapper = (
offer.offersFullTime.totalCompensation.baseCurrency;
} else if (offer.offersIntern?.monthlySalary) {
mappedOffer.income.value = offer.offersIntern.monthlySalary.value;
mappedOffer.income.currency =
offer.offersIntern.monthlySalary.currency;
mappedOffer.income.currency = offer.offersIntern.monthlySalary.currency;
mappedOffer.income.id = offer.offersIntern.monthlySalary.id;
mappedOffer.income.baseValue =
offer.offersIntern.monthlySalary.baseValue;
mappedOffer.income.baseValue = offer.offersIntern.monthlySalary.baseValue;
mappedOffer.income.baseCurrency =
offer.offersIntern.monthlySalary.baseCurrency;
} else {
@ -723,5 +741,5 @@ const userProfileOfferDtoMapper = (
});
}
return mappedOffer
}
return mappedOffer;
};

@ -14,6 +14,15 @@ import { profileAnalysisDtoMapper } from '~/mappers/offers-mappers';
import { createRouter } from '../context';
type Offer = OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
profile: OffersProfile & { background: OffersBackground | null };
};
const searchOfferPercentile = (
offer: OffersOffer & {
company: Company;
@ -58,7 +67,9 @@ export const offersAnalysisRouter = createRouter()
async resolve({ ctx, input }) {
const analysis = await ctx.prisma.offersAnalysis.findFirst({
include: {
overallHighestOffer: {
companyAnalysis: {
include: {
topSimilarOffers: {
include: {
company: true,
offersFullTime: {
@ -73,12 +84,24 @@ export const offersAnalysisRouter = createRouter()
},
profile: {
include: {
background: true,
background: {
include: {
experiences: {
include: {
company: true,
},
},
},
},
},
},
topCompanyOffers: {
},
},
},
},
overallAnalysis: {
include: {
topSimilarOffers: {
include: {
company: true,
offersFullTime: {
@ -106,7 +129,9 @@ export const offersAnalysisRouter = createRouter()
},
},
},
topOverallOffers: {
},
},
overallHighestOffer: {
include: {
company: true,
offersFullTime: {
@ -121,15 +146,7 @@ export const offersAnalysisRouter = createRouter()
},
profile: {
include: {
background: {
include: {
experiences: {
include: {
company: true,
},
},
},
},
background: true,
},
},
},
@ -310,20 +327,21 @@ export const offersAnalysisRouter = createRouter()
},
});
// COMPANY ANALYSIS
const companyMap = new Map<string, Offer>();
offers.forEach((offer) => {
if (companyMap.get(offer.companyId) == null) {
companyMap.set(offer.companyId, offer);
}
});
const companyAnalysis = Array.from(companyMap.values()).map(
(companyOffer) => {
// TODO: Refactor calculating analysis into a function
let similarCompanyOffers = similarOffers.filter(
(offer) => offer.companyId === overallHighestOffer.companyId,
);
// CALCULATE PERCENTILES
const overallIndex = searchOfferPercentile(
overallHighestOffer,
similarOffers,
);
const overallPercentile =
similarOffers.length <= 1
? 100
: 100 - (100 * overallIndex) / (similarOffers.length - 1);
const companyIndex = searchOfferPercentile(
overallHighestOffer,
similarCompanyOffers,
@ -333,25 +351,11 @@ export const offersAnalysisRouter = createRouter()
? 100
: 100 - (100 * companyIndex) / (similarCompanyOffers.length - 1);
// 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,
);
// Get top offers (excluding user's offer)
similarCompanyOffers = similarCompanyOffers.filter(
(offer) => offer.id !== overallHighestOffer.id,
(offer) => offer.id !== companyOffer.id,
);
const noOfSimilarOffers = similarOffers.length;
const similarOffers90PercentileIndex = Math.ceil(noOfSimilarOffers * 0.1);
const topPercentileOffers =
noOfSimilarOffers > 2
? similarOffers.slice(
similarOffers90PercentileIndex,
similarOffers90PercentileIndex + 2,
)
: similarOffers;
const noOfSimilarCompanyOffers = similarCompanyOffers.length;
const similarCompanyOffers90PercentileIndex = Math.ceil(
noOfSimilarCompanyOffers * 0.1,
@ -364,35 +368,79 @@ export const offersAnalysisRouter = createRouter()
)
: similarCompanyOffers;
return {
noOfSimilarOffers: noOfSimilarCompanyOffers,
percentile: companyPercentile,
topSimilarOffers: topPercentileCompanyOffers,
};
},
);
// OVERALL ANALYSIS
const overallIndex = searchOfferPercentile(
overallHighestOffer,
similarOffers,
);
const overallPercentile =
similarOffers.length <= 1
? 100
: 100 - (100 * overallIndex) / (similarOffers.length - 1);
similarOffers = similarOffers.filter(
(offer) => offer.id !== overallHighestOffer.id,
);
const noOfSimilarOffers = similarOffers.length;
const similarOffers90PercentileIndex = Math.ceil(noOfSimilarOffers * 0.1);
const topPercentileOffers =
noOfSimilarOffers > 2
? similarOffers.slice(
similarOffers90PercentileIndex,
similarOffers90PercentileIndex + 2,
)
: similarOffers;
const analysis = await ctx.prisma.offersAnalysis.create({
data: {
companyPercentile,
noOfSimilarCompanyOffers,
companyAnalysis: {
create: companyAnalysis.map((analysisUnit) => {
return {
noOfSimilarOffers: analysisUnit.noOfSimilarOffers,
percentile: analysisUnit.percentile,
topSimilarOffers: {
connect: analysisUnit.topSimilarOffers.map((offer) => {
return { id: offer.id };
}),
},
};
}),
},
overallAnalysis: {
create: {
noOfSimilarOffers,
percentile: overallPercentile,
topSimilarOffers: {
connect: topPercentileOffers.map((offer) => {
return { id: offer.id };
}),
},
},
},
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 };
}),
},
},
include: {
overallHighestOffer: {
companyAnalysis: {
include: {
topSimilarOffers: {
include: {
company: true,
offersFullTime: {
@ -407,12 +455,24 @@ export const offersAnalysisRouter = createRouter()
},
profile: {
include: {
background: true,
background: {
include: {
experiences: {
include: {
company: true,
},
},
},
},
},
},
},
},
topCompanyOffers: {
},
},
overallAnalysis: {
include: {
topSimilarOffers: {
include: {
company: true,
offersFullTime: {
@ -440,7 +500,9 @@ export const offersAnalysisRouter = createRouter()
},
},
},
topOverallOffers: {
},
},
overallHighestOffer: {
include: {
company: true,
offersFullTime: {
@ -455,15 +517,7 @@ export const offersAnalysisRouter = createRouter()
},
profile: {
include: {
background: {
include: {
experiences: {
include: {
company: true,
},
},
},
},
background: true,
},
},
},

@ -113,7 +113,9 @@ export const offersProfileRouter = createRouter()
include: {
analysis: {
include: {
overallHighestOffer: {
companyAnalysis: {
include: {
topSimilarOffers: {
include: {
company: true,
offersFullTime: {
@ -128,12 +130,24 @@ export const offersProfileRouter = createRouter()
},
profile: {
include: {
background: true,
background: {
include: {
experiences: {
include: {
company: true,
},
},
},
},
topCompanyOffers: {
},
},
},
},
},
},
overallAnalysis: {
include: {
topSimilarOffers: {
include: {
company: true,
offersFullTime: {
@ -161,7 +175,9 @@ export const offersProfileRouter = createRouter()
},
},
},
topOverallOffers: {
},
},
overallHighestOffer: {
include: {
company: true,
offersFullTime: {
@ -176,15 +192,7 @@ export const offersProfileRouter = createRouter()
},
profile: {
include: {
background: {
include: {
experiences: {
include: {
company: true,
},
},
},
},
background: true,
},
},
},
@ -394,7 +402,7 @@ export const offersProfileRouter = createRouter()
message: 'Missing fields in background experiences.',
});
}),
)
),
},
specificYoes: {
create: input.background.specificYoes.map((x) => {

@ -143,14 +143,14 @@ export type OffersDiscussion = {
};
export type ProfileAnalysis = {
companyAnalysis: Array<Analysis>;
companyAnalysis: Array<AnalysisUnit>;
id: string;
overallAnalysis: Analysis;
overallAnalysis: AnalysisUnit;
overallHighestOffer: AnalysisHighestOffer;
profileId: string;
};
export type Analysis = {
export type AnalysisUnit = {
noOfOffers: number;
percentile: number;
topPercentileOffers: Array<AnalysisOffer>;
@ -191,7 +191,7 @@ export type UserProfile = {
offers: Array<UserProfileOffers>;
profileName: string;
token: string;
}
};
export type UserProfileOffer = {
company: OffersCompany;
@ -202,4 +202,4 @@ export type UserProfileOffer = {
location: string;
monthYearReceived: Date;
title: string;
}
};

Loading…
Cancel
Save