summaryrefslogtreecommitdiff
path: root/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-recommendations-common.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-recommendations-common.js')
-rw-r--r--node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-recommendations-common.js190
1 files changed, 190 insertions, 0 deletions
diff --git a/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-recommendations-common.js b/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-recommendations-common.js
new file mode 100644
index 0000000..c03b729
--- /dev/null
+++ b/node_modules/@jet-app/app-store/tmp/src/common/personalization/on-device-recommendations-common.js
@@ -0,0 +1,190 @@
+import * as validation from "@jet/environment/json/validation";
+import * as serverData from "../../foundation/json-parsing/server-data";
+import * as mediaDataFetching from "../../foundation/media/data-fetching";
+import * as mediaNetwork from "../../foundation/media/network";
+import * as groupingShelfControllerCommon from "../grouping/shelf-controllers/grouping-shelf-controller-common";
+import { Parameters } from "../../foundation/network/url-constants";
+export class PersonalizedData {
+}
+export async function recommendedAppsForUseCase(objectGraph, useCase, displayContext) {
+ const displayContextLogString = displayContext === "shelf" ? "OnDeviceRecommendationsShelfController" : "OnDeviceRecommendationsPageController";
+ return await new Promise((resolve, reject) => {
+ if (!objectGraph.host.isiOS) {
+ const errorMessage = `${displayContextLogString}: On device personalization is only enabled on iOS devices.`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new Error(errorMessage));
+ return;
+ }
+ if (serverData.isNullOrEmpty(objectGraph.user.dsid)) {
+ const errorMessage = `${displayContextLogString}: User is currently not signed in.`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new Error(errorMessage));
+ return;
+ }
+ if (serverData.isNullOrEmpty(useCase)) {
+ const errorMessage = `${displayContextLogString}: Missing valid useCase for ODP: ${useCase}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new Error(errorMessage));
+ return;
+ }
+ objectGraph.onDeviceRecommendationsManager
+ .performRequest({
+ type: "fetchRecommendations",
+ dsId: objectGraph.user.dsid,
+ useCase: useCase,
+ })
+ .then((recoResponse) => {
+ const candidates = serverData.asArrayOrEmpty(recoResponse["candidates"]);
+ const recoMetrics = serverData.asJSONData(recoResponse["metrics"]);
+ if (serverData.isNullOrEmpty(candidates)) {
+ const errorMessage = `${displayContextLogString}: ODP returned no candidate ids for useCase: ${useCase}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new NoODPCandidatesError(errorMessage));
+ return;
+ }
+ if (serverData.isNullOrEmpty(recoMetrics)) {
+ const errorMessage = `${displayContextLogString}: ODP returned no metrics for useCase: ${useCase}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new Error(errorMessage));
+ return;
+ }
+ const candidatesData = [];
+ for (const candidateId of candidates) {
+ if (serverData.isDefinedNonNullNonEmpty(candidateId)) {
+ candidatesData.push({
+ id: candidateId,
+ type: "apps",
+ });
+ }
+ }
+ // MAPI Request
+ const mediaApiRequest = new mediaDataFetching.Request(objectGraph, candidatesData)
+ .withFilter("apps:recommendable", "true")
+ .addingQuery(Parameters.onDevicePersonalizationUseCase, useCase);
+ groupingShelfControllerCommon.prepareGroupingShelfRequest(objectGraph, mediaApiRequest);
+ mediaNetwork
+ .fetchData(objectGraph, mediaApiRequest)
+ .then((recoDataContainer) => {
+ resolve({
+ candidates: candidates,
+ recoMetrics: recoMetrics,
+ dataContainer: recoDataContainer,
+ });
+ })
+ .catch((error) => {
+ const errorMessage = `${displayContextLogString}: Failed to fetch Media API data for candidates: ${candidates}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new Error(errorMessage));
+ });
+ })
+ .catch((error) => {
+ const errorMessage = `${displayContextLogString}: Failed to perform ODP for useCase: ${useCase}, ${error}`;
+ validation.unexpectedType("defaultValue", errorMessage, null);
+ reject(new Error(errorMessage));
+ });
+ });
+}
+/***
+ * Gives the list of app ids in the order in which they appear in the passed response
+ * @param personalizedDataItems The data items to be processed
+ * @return List of ordered app ids
+ */
+export function getOrderedAppIds(personalizedDataItems) {
+ const seenAppIds = new Set();
+ const orderedAppIds = [];
+ personalizedDataItems.forEach((dataItem, index) => {
+ if (!seenAppIds.has(dataItem.appId)) {
+ orderedAppIds.push(dataItem.appId);
+ seenAppIds.add(dataItem.appId);
+ }
+ });
+ return orderedAppIds;
+}
+/***
+ * This function reranks events based on a calculated on-device score
+ * that can factor in recency, frequency, or usage of an app
+ * @param personalizedDataItems List of personalized data items
+ * @param metricsData Metrics data with hyperparameter values to use
+ * @return Personalized data items after reranking using on-device signals
+ */
+export function getUpdatedScoreAfterBoosting(personalizedDataItems, metricsData) {
+ var _a;
+ const weightParam = Number((_a = metricsData["weight_parameter"]) !== null && _a !== void 0 ? _a : 0.0);
+ for (const personalizedDataItem of personalizedDataItems) {
+ const serverScore = personalizedDataItem.score;
+ const onDeviceScore = personalizedDataItem.onDeviceScore;
+ personalizedDataItem.modifiedScore = weightParam * onDeviceScore + (1 - weightParam) * serverScore;
+ }
+ personalizedDataItems.sort((a, b) => {
+ return b.modifiedScore - a.modifiedScore;
+ });
+ return personalizedDataItems;
+}
+/***
+ * This function will diversify the personalized items on the basis of ordering of apps passed.
+ * In case there are pinned items, we will remove them from diversification bucket and keep them in their position and add diversified items in order
+ * This can lead to duplicates next to the pinned items. But the pinning and duplicate app events case scenario is rare so we avoid complicated logic here.
+ * Explanation ->
+ * If the original data items list contains appId in this order -> [1(a), 1(b), 2(a), 3(a), 2(b), 2(c), 3(b), 4(a), 1(c), 4(b)]
+ * and the ordering of the apps used for relative ranking is -> [1, 2, 3, 4]
+ * Then the updated ordering of the data items should look like -> [1(a), 2(a), 3(a), 4(a), 1(b), 2(b), 3(b), 4(b), 1(c), 2(c)]
+ * @param personalizedDataItems List of personalized data items for diversification
+ * @param orderedAppIds List of ordered app ids used for relative ordering between in-app events
+ * @return The diversified list of data items.
+ */
+export function diversifyDataItems(personalizedDataItems, orderedAppIds) {
+ const pinnedItems = personalizedDataItems.filter((item) => serverData.isDefinedNonNull(item.pinnedPosition));
+ personalizedDataItems = personalizedDataItems.filter((item) => serverData.isNull(item.pinnedPosition));
+ const appIdGroups = new Map();
+ // Insert the elements in a map in reverse order of their appearance in dataItems so that we can later use pop() instead of shift()
+ // 1 -> [1(c), 1(b), 1(a)], 2 -> [2(c), 2(b), 2(a)], 3 -> [3(b), 3(a)], 4-> [4(b), 4(a)]
+ personalizedDataItems.reverse();
+ personalizedDataItems.forEach((dataItem, index) => {
+ if (dataItem.appId in appIdGroups) {
+ appIdGroups[dataItem.appId].push(dataItem);
+ }
+ else {
+ appIdGroups[dataItem.appId] = [dataItem];
+ }
+ });
+ const diversifiedDataItems = [];
+ // We find the max number of appItems for our appIds, to determine the iteration count
+ const maxAppEventsForAppId = Math.max(...Object.values(appIdGroups).map((a) => a.length));
+ for (let index = 0; index < maxAppEventsForAppId; index++) {
+ const reducedServerSideAppIdsOrdering = [];
+ orderedAppIds.forEach((appId) => {
+ if (appId in appIdGroups && appIdGroups[appId].length > 0) {
+ diversifiedDataItems.push(appIdGroups[appId].pop());
+ if (appIdGroups[appId].length !== 0) {
+ reducedServerSideAppIdsOrdering.push(appId);
+ }
+ }
+ });
+ orderedAppIds = reducedServerSideAppIdsOrdering;
+ }
+ // Merge the pinned items and diversified items
+ const finalizedResponse = new Array(pinnedItems.length + diversifiedDataItems.length);
+ // Sort pinned items by pinned position to be able to handle the pinned items that exceed the final list length
+ // For all the pinned items that have position greater than input list size, we just add them to end of the list
+ pinnedItems.sort((a, b) => a.pinnedPosition - b.pinnedPosition);
+ for (const dataItem of pinnedItems) {
+ if (dataItem.pinnedPosition < finalizedResponse.length) {
+ // Possible edgecase - Sanity check in case filtering reduces the length of items
+ finalizedResponse[dataItem.pinnedPosition] = dataItem;
+ }
+ else {
+ // Extremely rare case scenario: Push these items to diversifiedDataItemsList
+ diversifiedDataItems.push(dataItem);
+ }
+ }
+ diversifiedDataItems.reverse(); // To allow popping for first element fetch
+ for (const [index, finalizedItem] of finalizedResponse.entries()) {
+ if (serverData.isNull(finalizedItem) && diversifiedDataItems.length) {
+ finalizedResponse[index] = diversifiedDataItems.pop();
+ }
+ }
+ return finalizedResponse;
+}
+export class NoODPCandidatesError extends Error {
+}
+//# sourceMappingURL=on-device-recommendations-common.js.map \ No newline at end of file