All files / lib cascades.js

100% Statements 102/102
100% Branches 14/14
100% Functions 1/1
100% Lines 102/102

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 1081x 1x   1x 1x 1x   1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 5x 5x 5x 11x 11x 11x 4x 4x 5x 5x 5x 5x 5x 5x 5x 4x 7x 7x 4x 5x 5x 5x 5x   5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x   5x 5x 2x 2x 2x   2x 2x 2x 2x 2x 2x 5x 5x 5x  
import { metadataOptionId, parseMetadataOptionId } from './schemas/metadata.js';
import { entries, groupBy, nonnull, sum } from './utils.js';
 
/**
 * @import { RuntimeValue } from './metadata.js'
 */
 
/**
 * Get all metadata the given MetadataValue cascades into
 * Confidence scores are the sum of confidences of all metadata options that cascade into a metadata
 *
 * For example, if we have the following cascades:
 *
 * - species:40 -> genus:1
 * - species:41 -> genus:1
 * - species:42 -> genus:2
 * - species:44 -> genus:3
 *
 * And make the following call
 *
 * ```js
 * computeCascades({
 *  metadataId: "species",
 *  confidence: 0.4,
 *  value: 40,
 *  alternatives: { "41": 0.3, "42": 0.2, "44": 0.1 }
 * })
 * ```
 *
 * We'll get the following cascades, ready for another round of `storeMetadataValue` calls:
 *
 * ```js
 * // Only one object in the array
 * // since we only have cascades for a single other metadata
 * [
 *  {
 *    metadataId: "genus",
 *    value: "1",
 *    confidence: 0.7, // 0.4 + 0.3, from species:40 and species:41
 *    alternatives: {
 *      "2": 0.2, // from species:42
 *      "3": 0.1  // from species:44
 *    }
 *  }
 * ]
 * ```
 *
 * @param {object} param0
 * @param {import('./idb.svelte.js').DatabaseHandle} param0.db
 * @param {string} param0.metadataId
 * @param {number} param0.confidence
 * @param {RuntimeValue} param0.value
 * @param { Array<{ value: RuntimeValue, confidence: number }> } param0.alternatives
 */
export async function computeCascades({ db, metadataId, confidence, value, alternatives }) {
	return await Promise.all(
		// List of { value, confidence }, that includes the main value as well as the alternatives
		[{ value, confidence }, ...alternatives].map(async ({ confidence, value }) => {
			// Get the cascades for the corresponding metadata option
			const option = await db.get('MetadataOption', metadataOptionId(metadataId, value));
			if (!option?.cascade) return undefined;
			const { cascade } = option;
			return { cascade, confidence };
		})
	).then((options) => {
		// Combine all cascades that lead to the same metadata option, and sum their confidences
		const groupedByOption = groupBy(
			// Get a list of { option id, confidence } for every cascaded value,
			// the confidence coming from the value that triggers it
			options.filter(nonnull).flatMap(({ cascade, confidence }) => {
				return entries(cascade).map(([metadataId, value]) => ({
					optionId: metadataOptionId(metadataId, value),
					confidence
				}));
			}),
			(c) => c.optionId,
			(c) => c.confidence
		);
 
		// Combine all options of a same metadataId into alternatives.
		// The confidence of every option is the sum of confidences of all
		// cascades that lead to that option
		const groupedByMetadata = groupBy(
			groupedByOption.entries(),
			([optionId]) => parseMetadataOptionId(optionId).metadataId,
			([optionId, confidences]) =>
				/** @type {const} */ ({
					value: parseMetadataOptionId(optionId).key,
					confidence: sum(confidences)
				})
		);
 
		// Return a list of data ready for storeMetadataValue() for every cascaded metadata
		return [...groupedByMetadata.entries()].map(([metadataId, options]) => {
			const [{ value, confidence }, ...alternatives] = options.toSorted(
				({ confidence: a }, { confidence: b }) => b - a
			);
 
			return {
				metadataId,
				value,
				confidence,
				alternatives
			};
		});
	});
}