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

pull/471/head
Bryann Yeap Kok Keong 3 years ago committed by Ai Ling
parent 1ea1afc8a2
commit d0b5867dd7

@ -362,9 +362,8 @@ model OffersOffer {
offersFullTime OffersFullTime? @relation(fields: [offersFullTimeId], references: [id], onDelete: Cascade) offersFullTime OffersFullTime? @relation(fields: [offersFullTimeId], references: [id], onDelete: Cascade)
offersFullTimeId String? @unique offersFullTimeId String? @unique
OffersAnalysis OffersAnalysis? @relation("HighestOverallOffer") offersAnalysis OffersAnalysis? @relation("HighestOverallOffer")
OffersAnalysisTopOverallOffers OffersAnalysis[] @relation("TopOverallOffers") offersAnalysisUnit OffersAnalysisUnit[]
OffersAnalysisTopCompanyOffers OffersAnalysis[] @relation("TopCompanyOffers")
} }
model OffersIntern { model OffersIntern {
@ -405,14 +404,21 @@ model OffersAnalysis {
offerId String @unique offerId String @unique
// OVERALL // 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 noOfSimilarOffers Int
topOverallOffers OffersOffer[] @relation("TopOverallOffers") topSimilarOffers OffersOffer[]
// Company offersAnalysisOverall OffersAnalysis[] @relation("OverallAnalysis")
companyPercentile Float offersAnalysisCompany OffersAnalysis[] @relation("CompanyAnalysis")
noOfSimilarCompanyOffers Int
topCompanyOffers OffersOffer[] @relation("TopCompanyOffers")
} }
// End of Offers project models. // End of Offers project models.

@ -1,6 +1,7 @@
import type { import type {
Company, Company,
OffersAnalysis, OffersAnalysis,
OffersAnalysisUnit,
OffersBackground, OffersBackground,
OffersCurrency, OffersCurrency,
OffersEducation, OffersEducation,
@ -18,9 +19,9 @@ import { TRPCError } from '@trpc/server';
import type { import type {
AddToProfileResponse, AddToProfileResponse,
Analysis,
AnalysisHighestOffer, AnalysisHighestOffer,
AnalysisOffer, AnalysisOffer,
AnalysisUnit,
Background, Background,
CreateOfferProfileResponse, CreateOfferProfileResponse,
DashboardOffer, DashboardOffer,
@ -111,32 +112,32 @@ const analysisOfferDtoMapper = (
return analysisOfferDto; return analysisOfferDto;
}; };
const analysisDtoMapper = ( const analysisUnitDtoMapper = (
noOfOffers: number, analysisUnit: OffersAnalysisUnit & {
percentile: number, topSimilarOffers: Array<
topPercentileOffers: Array< OffersOffer & {
OffersOffer & { company: Company;
company: Company; offersFullTime:
offersFullTime: | (OffersFullTime & { totalCompensation: OffersCurrency })
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
profile: OffersProfile & {
background:
| (OffersBackground & {
experiences: Array<
OffersExperience & { company: Company | null }
>;
})
| null; | null;
}; offersIntern: (OffersIntern & { monthlySalary: OffersCurrency }) | null;
} profile: OffersProfile & {
>, background:
| (OffersBackground & {
experiences: Array<
OffersExperience & { company: Company | null }
>;
})
| null;
};
}
>;
},
) => { ) => {
const analysisDto: Analysis = { const analysisDto: AnalysisUnit = {
noOfOffers, noOfOffers: analysisUnit.noOfSimilarOffers,
percentile, percentile: analysisUnit.percentile,
topPercentileOffers: topPercentileOffers.map((offer) => topPercentileOffers: analysisUnit.topSimilarOffers.map((offer) =>
analysisOfferDtoMapper(offer), analysisOfferDtoMapper(offer),
), ),
}; };
@ -166,6 +167,52 @@ const analysisHighestOfferDtoMapper = (
export const profileAnalysisDtoMapper = ( export const profileAnalysisDtoMapper = (
analysis: analysis:
| (OffersAnalysis & { | (OffersAnalysis & {
companyAnalysis: Array<
OffersAnalysisUnit & {
topSimilarOffers: Array<
OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern:
| (OffersIntern & { monthlySalary: OffersCurrency })
| null;
profile: OffersProfile & {
background:
| (OffersBackground & {
experiences: Array<
OffersExperience & { company: Company | null }
>;
})
| null;
};
}
>;
}
>;
overallAnalysis: OffersAnalysisUnit & {
topSimilarOffers: Array<
OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern:
| (OffersIntern & { monthlySalary: OffersCurrency })
| null;
profile: OffersProfile & {
background:
| (OffersBackground & {
experiences: Array<
OffersExperience & { company: Company | null }
>;
})
| null;
};
}
>;
};
overallHighestOffer: OffersOffer & { overallHighestOffer: OffersOffer & {
company: Company; company: Company;
offersFullTime: offersFullTime:
@ -176,46 +223,6 @@ export const profileAnalysisDtoMapper = (
| null; | null;
profile: OffersProfile & { background: OffersBackground | null }; profile: OffersProfile & { background: OffersBackground | null };
}; };
topCompanyOffers: Array<
OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern:
| (OffersIntern & { monthlySalary: OffersCurrency })
| null;
profile: OffersProfile & {
background:
| (OffersBackground & {
experiences: Array<
OffersExperience & { company: Company | null }
>;
})
| null;
};
}
>;
topOverallOffers: Array<
OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern:
| (OffersIntern & { monthlySalary: OffersCurrency })
| null;
profile: OffersProfile & {
background:
| (OffersBackground & {
experiences: Array<
OffersExperience & { company: Company | null }
>;
})
| null;
};
}
>;
}) })
| null, | null,
) => { ) => {
@ -224,19 +231,11 @@ export const profileAnalysisDtoMapper = (
} }
const profileAnalysisDto: ProfileAnalysis = { const profileAnalysisDto: ProfileAnalysis = {
companyAnalysis: [ companyAnalysis: analysis.companyAnalysis.map((analysisUnit) =>
analysisDtoMapper( analysisUnitDtoMapper(analysisUnit),
analysis.noOfSimilarCompanyOffers,
analysis.companyPercentile,
analysis.topCompanyOffers,
),
],
id: analysis.id,
overallAnalysis: analysisDtoMapper(
analysis.noOfSimilarOffers,
analysis.overallPercentile,
analysis.topOverallOffers,
), ),
id: analysis.id,
overallAnalysis: analysisUnitDtoMapper(analysis.overallAnalysis),
overallHighestOffer: analysisHighestOfferDtoMapper( overallHighestOffer: analysisHighestOfferDtoMapper(
analysis.overallHighestOffer, analysis.overallHighestOffer,
), ),
@ -442,6 +441,52 @@ export const profileDtoMapper = (
profile: OffersProfile & { profile: OffersProfile & {
analysis: analysis:
| (OffersAnalysis & { | (OffersAnalysis & {
companyAnalysis: Array<
OffersAnalysisUnit & {
topSimilarOffers: Array<
OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern:
| (OffersIntern & { monthlySalary: OffersCurrency })
| null;
profile: OffersProfile & {
background:
| (OffersBackground & {
experiences: Array<
OffersExperience & { company: Company | null }
>;
})
| null;
};
}
>;
}
>;
overallAnalysis: OffersAnalysisUnit & {
topSimilarOffers: Array<
OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern:
| (OffersIntern & { monthlySalary: OffersCurrency })
| null;
profile: OffersProfile & {
background:
| (OffersBackground & {
experiences: Array<
OffersExperience & { company: Company | null }
>;
})
| null;
};
}
>;
};
overallHighestOffer: OffersOffer & { overallHighestOffer: OffersOffer & {
company: Company; company: Company;
offersFullTime: offersFullTime:
@ -452,46 +497,6 @@ export const profileDtoMapper = (
| null; | null;
profile: OffersProfile & { background: OffersBackground | null }; profile: OffersProfile & { background: OffersBackground | null };
}; };
topCompanyOffers: Array<
OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern:
| (OffersIntern & { monthlySalary: OffersCurrency })
| null;
profile: OffersProfile & {
background:
| (OffersBackground & {
experiences: Array<
OffersExperience & { company: Company | null }
>;
})
| null;
};
}
>;
topOverallOffers: Array<
OffersOffer & {
company: Company;
offersFullTime:
| (OffersFullTime & { totalCompensation: OffersCurrency })
| null;
offersIntern:
| (OffersIntern & { monthlySalary: OffersCurrency })
| null;
profile: OffersProfile & {
background:
| (OffersBackground & {
experiences: Array<
OffersExperience & { company: Company | null }
>;
})
| null;
};
}
>;
}) })
| null; | null;
background: background:

@ -14,6 +14,15 @@ import { profileAnalysisDtoMapper } from '~/mappers/offers-mappers';
import { createRouter } from '../context'; 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 = ( const searchOfferPercentile = (
offer: OffersOffer & { offer: OffersOffer & {
company: Company; company: Company;
@ -58,46 +67,62 @@ export const offersAnalysisRouter = createRouter()
async resolve({ ctx, input }) { async resolve({ ctx, input }) {
const analysis = await ctx.prisma.offersAnalysis.findFirst({ const analysis = await ctx.prisma.offersAnalysis.findFirst({
include: { include: {
overallHighestOffer: { companyAnalysis: {
include: { include: {
company: true, topSimilarOffers: {
offersFullTime: {
include: {
totalCompensation: true,
},
},
offersIntern: {
include: { include: {
monthlySalary: true, company: true,
}, offersFullTime: {
}, include: {
profile: { totalCompensation: true,
include: { },
background: true, },
offersIntern: {
include: {
monthlySalary: true,
},
},
profile: {
include: {
background: {
include: {
experiences: {
include: {
company: true,
},
},
},
},
},
},
}, },
}, },
}, },
}, },
topCompanyOffers: { overallAnalysis: {
include: { include: {
company: true, topSimilarOffers: {
offersFullTime: {
include: {
totalCompensation: true,
},
},
offersIntern: {
include: { include: {
monthlySalary: true, company: true,
}, offersFullTime: {
}, include: {
profile: { totalCompensation: true,
include: { },
background: { },
offersIntern: {
include: { include: {
experiences: { monthlySalary: true,
},
},
profile: {
include: {
background: {
include: { include: {
company: true, experiences: {
include: {
company: true,
},
},
}, },
}, },
}, },
@ -106,7 +131,7 @@ export const offersAnalysisRouter = createRouter()
}, },
}, },
}, },
topOverallOffers: { overallHighestOffer: {
include: { include: {
company: true, company: true,
offersFullTime: { offersFullTime: {
@ -121,15 +146,7 @@ export const offersAnalysisRouter = createRouter()
}, },
profile: { profile: {
include: { include: {
background: { background: true,
include: {
experiences: {
include: {
company: true,
},
},
},
},
}, },
}, },
}, },
@ -310,11 +327,56 @@ export const offersAnalysisRouter = createRouter()
}, },
}); });
let similarCompanyOffers = similarOffers.filter( // COMPANY ANALYSIS
(offer) => offer.companyId === overallHighestOffer.companyId, 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,
);
const companyIndex = searchOfferPercentile(
overallHighestOffer,
similarCompanyOffers,
);
const companyPercentile =
similarCompanyOffers.length <= 1
? 100
: 100 - (100 * companyIndex) / (similarCompanyOffers.length - 1);
// Get top offers (excluding user's offer)
similarCompanyOffers = similarCompanyOffers.filter(
(offer) => offer.id !== companyOffer.id,
);
const noOfSimilarCompanyOffers = similarCompanyOffers.length;
const similarCompanyOffers90PercentileIndex = Math.ceil(
noOfSimilarCompanyOffers * 0.1,
);
const topPercentileCompanyOffers =
noOfSimilarCompanyOffers > 2
? similarCompanyOffers.slice(
similarCompanyOffers90PercentileIndex,
similarCompanyOffers90PercentileIndex + 2,
)
: similarCompanyOffers;
return {
noOfSimilarOffers: noOfSimilarCompanyOffers,
percentile: companyPercentile,
topSimilarOffers: topPercentileCompanyOffers,
};
},
); );
// CALCULATE PERCENTILES // OVERALL ANALYSIS
const overallIndex = searchOfferPercentile( const overallIndex = searchOfferPercentile(
overallHighestOffer, overallHighestOffer,
similarOffers, similarOffers,
@ -324,23 +386,9 @@ export const offersAnalysisRouter = createRouter()
? 100 ? 100
: 100 - (100 * overallIndex) / (similarOffers.length - 1); : 100 - (100 * overallIndex) / (similarOffers.length - 1);
const companyIndex = searchOfferPercentile(
overallHighestOffer,
similarCompanyOffers,
);
const companyPercentile =
similarCompanyOffers.length <= 1
? 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( similarOffers = similarOffers.filter(
(offer) => offer.id !== overallHighestOffer.id, (offer) => offer.id !== overallHighestOffer.id,
); );
similarCompanyOffers = similarCompanyOffers.filter(
(offer) => offer.id !== overallHighestOffer.id,
);
const noOfSimilarOffers = similarOffers.length; const noOfSimilarOffers = similarOffers.length;
const similarOffers90PercentileIndex = Math.ceil(noOfSimilarOffers * 0.1); const similarOffers90PercentileIndex = Math.ceil(noOfSimilarOffers * 0.1);
@ -352,86 +400,100 @@ export const offersAnalysisRouter = createRouter()
) )
: similarOffers; : similarOffers;
const noOfSimilarCompanyOffers = similarCompanyOffers.length;
const similarCompanyOffers90PercentileIndex = Math.ceil(
noOfSimilarCompanyOffers * 0.1,
);
const topPercentileCompanyOffers =
noOfSimilarCompanyOffers > 2
? similarCompanyOffers.slice(
similarCompanyOffers90PercentileIndex,
similarCompanyOffers90PercentileIndex + 2,
)
: similarCompanyOffers;
const analysis = await ctx.prisma.offersAnalysis.create({ const analysis = await ctx.prisma.offersAnalysis.create({
data: { data: {
companyPercentile, companyAnalysis: {
noOfSimilarCompanyOffers, create: companyAnalysis.map((analysisUnit) => {
noOfSimilarOffers, 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: { overallHighestOffer: {
connect: { connect: {
id: overallHighestOffer.id, id: overallHighestOffer.id,
}, },
}, },
overallPercentile,
profile: { profile: {
connect: { connect: {
id: input.profileId, id: input.profileId,
}, },
}, },
topCompanyOffers: {
connect: topPercentileCompanyOffers.map((offer) => {
return { id: offer.id };
}),
},
topOverallOffers: {
connect: topPercentileOffers.map((offer) => {
return { id: offer.id };
}),
},
}, },
include: { include: {
overallHighestOffer: { companyAnalysis: {
include: { include: {
company: true, topSimilarOffers: {
offersFullTime: {
include: { include: {
totalCompensation: true, company: true,
}, offersFullTime: {
}, include: {
offersIntern: { totalCompensation: true,
include: { },
monthlySalary: true, },
}, offersIntern: {
}, include: {
profile: { monthlySalary: true,
include: { },
background: true, },
profile: {
include: {
background: {
include: {
experiences: {
include: {
company: true,
},
},
},
},
},
},
}, },
}, },
}, },
}, },
topCompanyOffers: { overallAnalysis: {
include: { include: {
company: true, topSimilarOffers: {
offersFullTime: {
include: { include: {
totalCompensation: true, company: true,
}, offersFullTime: {
}, include: {
offersIntern: { totalCompensation: true,
include: { },
monthlySalary: true, },
}, offersIntern: {
}, include: {
profile: { monthlySalary: true,
include: { },
background: { },
profile: {
include: { include: {
experiences: { background: {
include: { include: {
company: true, experiences: {
include: {
company: true,
},
},
}, },
}, },
}, },
@ -440,7 +502,7 @@ export const offersAnalysisRouter = createRouter()
}, },
}, },
}, },
topOverallOffers: { overallHighestOffer: {
include: { include: {
company: true, company: true,
offersFullTime: { offersFullTime: {
@ -455,15 +517,7 @@ export const offersAnalysisRouter = createRouter()
}, },
profile: { profile: {
include: { include: {
background: { background: true,
include: {
experiences: {
include: {
company: true,
},
},
},
},
}, },
}, },
}, },

@ -128,46 +128,62 @@ export const offersProfileRouter = createRouter()
include: { include: {
analysis: { analysis: {
include: { include: {
overallHighestOffer: { companyAnalysis: {
include: { include: {
company: true, topSimilarOffers: {
offersFullTime: {
include: {
totalCompensation: true,
},
},
offersIntern: {
include: { include: {
monthlySalary: true, company: true,
}, offersFullTime: {
}, include: {
profile: { totalCompensation: true,
include: { },
background: true, },
offersIntern: {
include: {
monthlySalary: true,
},
},
profile: {
include: {
background: {
include: {
experiences: {
include: {
company: true,
},
},
},
},
},
},
}, },
}, },
}, },
}, },
topCompanyOffers: { overallAnalysis: {
include: { include: {
company: true, topSimilarOffers: {
offersFullTime: {
include: {
totalCompensation: true,
},
},
offersIntern: {
include: { include: {
monthlySalary: true, company: true,
}, offersFullTime: {
},
profile: {
include: {
background: {
include: { include: {
experiences: { totalCompensation: true,
},
},
offersIntern: {
include: {
monthlySalary: true,
},
},
profile: {
include: {
background: {
include: { include: {
company: true, experiences: {
include: {
company: true,
},
},
}, },
}, },
}, },
@ -176,7 +192,7 @@ export const offersProfileRouter = createRouter()
}, },
}, },
}, },
topOverallOffers: { overallHighestOffer: {
include: { include: {
company: true, company: true,
offersFullTime: { offersFullTime: {
@ -191,15 +207,7 @@ export const offersProfileRouter = createRouter()
}, },
profile: { profile: {
include: { include: {
background: { background: true,
include: {
experiences: {
include: {
company: true,
},
},
},
},
}, },
}, },
}, },
@ -409,7 +417,7 @@ export const offersProfileRouter = createRouter()
message: 'Missing fields in background experiences.', message: 'Missing fields in background experiences.',
}); });
}), }),
) ),
}, },
specificYoes: { specificYoes: {
create: input.background.specificYoes.map((x) => { create: input.background.specificYoes.map((x) => {

@ -143,14 +143,14 @@ export type OffersDiscussion = {
}; };
export type ProfileAnalysis = { export type ProfileAnalysis = {
companyAnalysis: Array<Analysis>; companyAnalysis: Array<AnalysisUnit>;
id: string; id: string;
overallAnalysis: Analysis; overallAnalysis: AnalysisUnit;
overallHighestOffer: AnalysisHighestOffer; overallHighestOffer: AnalysisHighestOffer;
profileId: string; profileId: string;
}; };
export type Analysis = { export type AnalysisUnit = {
noOfOffers: number; noOfOffers: number;
percentile: number; percentile: number;
topPercentileOffers: Array<AnalysisOffer>; topPercentileOffers: Array<AnalysisOffer>;

Loading…
Cancel
Save