All files / lib beamup.svelte.js

0% Statements 0/28
0% Branches 0/29
0% Functions 0/18
0% Lines 0/25

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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154                                                                                                                                                                                                                                                                                                                   
/**
 * @import { BeamupSettings } from './schemas/protocols.js'
 * @import { Protocol, Metadata, MetadataValue } from './database.js'
 * @import { DatabaseHandle } from './idb.svelte.js'
 */
 
import * as beamup from '@cigale/beamup';
 
import { generateId } from './database.js';
import { errorMessage } from './i18n.js';
import { serializeMetadataValue } from './metadata.js';
import { getSetting } from './settings.svelte.js';
import { entries, groupBy, nonnull, pick, propOrNothing, range } from './utils.js';
 
/**
 * Stores a correction made to a protocol's metadata value.
 * @param {DatabaseHandle} db - The database handle.
 * @param {Pick<Protocol, 'id' | 'version'> & { beamup: typeof BeamupSettings.infer }} protocol - The protocol of the metadata.
 * @param {string} subject - The ID of the subject (image or observation) the metadata is associated with.
 * @param {Metadata} metadata - The metadata of the value.
 * @param {MetadataValue} beforeValue - The value before the correction.
 * @param {MetadataValue} afterValue - The value after the correction.
 */
export async function storeCorrection(db, protocol, subject, metadata, beforeValue, afterValue) {
	const preferences = await getSetting('beamupPreferences', db);
	if (!preferences[protocol.id]?.enable) return;
 
	const image = await db.get('Image', subject).catch(() => undefined);
	const observation = image
		? undefined
		: await db.get('Observation', subject).catch(() => undefined);
 
	const file = image?.fileId ? await db.get('ImageFile', image.fileId) : undefined;
 
	const hash =
		image?.sha1 ??
		(await Promise.all(
			(observation?.images ?? []).map(async (imageId) =>
				db
					.get('Image', imageId)
					.catch(() => undefined)
					.then((img) => img?.sha1)
			)
		).then((hashes) => hashes.filter(nonnull).join(' ')));
 
	await db.add(
		'BeamupCorrection',
		$state.snapshot({
			id: generateId('BeamupCorrection'),
			client: { version: import.meta.env.buildCommit || 'unversioned' },
			protocol: pick(protocol, 'id', 'version', 'beamup'),
			metadata: pick(metadata, 'id', 'type'),
			email: preferences[protocol.id]?.email ?? null,
			subject: {
				[image ? 'image' : 'observation']: { id: subject },
				contentHash: hash || null
			},
			...propOrNothing(
				'file',
				file
					? pick(
							file,
							'id',
							/* TODO 'contentHash', */ 'filename',
							'contentType',
							'dimensions'
						)
					: undefined
			),
			before: { ...beforeValue, value: serializeMetadataValue(beforeValue) },
			after: { ...afterValue, value: serializeMetadataValue(afterValue) },
			// eslint-disable-next-line svelte/prefer-svelte-reactivity
			occurredAt: new Date().toISOString()
		})
	);
}
 
/**
 * @param {DatabaseHandle} db
 * @param {(ids: string[], error: string|undefined) => void} [onProgress]
 */
export async function syncCorrections(db, onProgress) {
	const correctionsByOrigin = await db
		.getAll('BeamupCorrection')
		.then((corrections) => groupBy(corrections, (c) => c.protocol.beamup.origin));
 
	for (const [origin, corrections] of correctionsByOrigin.entries()) {
		await beamup
			.sendCorrections({
				origin,
				corrections: corrections.map(makeCorrection),
				onProgress(chunk, sent) {
					const start = chunk * beamup.CHUNK_SIZE;
 
					const ids = range(start, sent - start)
						.map((i) => corrections[i]?.id)
						.filter(nonnull);
 
					onProgress?.(ids, undefined);
				}
			})
			.then(async () => {
				for (const { id } of corrections) {
					await db.delete('BeamupCorrection', id);
				}
			})
			.catch((error) => {
				onProgress?.(
					corrections.map((c) => c.id),
					errorMessage(error)
				);
			});
	}
}
 
/**
 *
 * @param {typeof import('$lib/database').Tables.BeamupCorrection.inferIn} correction
 * @returns {import('@cigale/beamup').SendableCorrection}
 */
function makeCorrection({ after, before, protocol, metadata, client, occurredAt, subject, email }) {
	return {
		after: {
			alternatives: entries(after.alternatives).map(([value, confidence]) => ({
				value,
				confidence
			})),
			type: metadata.type,
			value: after.value
		},
		before: {
			alternatives: entries(before.alternatives).map(([value, confidence]) => ({
				value,
				confidence
			})),
			type: metadata.type,
			value: before.value
		},
		client_name: 'Cigale',
		client_version: client.version,
		comment: null,
		done_at: occurredAt,
		metadata: metadata.id,
		protocol_id: protocol.id,
		protocol_version: protocol.version?.toString() ?? 'non versioned',
		subject: subject.image?.id ?? subject.observation?.id ?? '',
		subject_type: /** @type {import('@cigale/beamup').SubjectType} */ (
			subject.image ? 'image' : subject.observation ? 'observation' : 'other'
		),
		subject_content_hash: subject.contentHash,
		user: email
	};
}