All files / lib/schemas results.js

100% Statements 7/7
100% Branches 2/2
100% Functions 4/4
100% Lines 6/6

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 155              17x                                                                                       17x                               34x             17x         34x                     4x                                                                                                                                
import { type } from 'arktype';
 
// Can't use $lib/ in $lib/schemas files, they're susceptible
// to be imported by non-Vite-managed pre-build scripts (e.g. JSON Schema generation)
import { mapValues } from '../utils.js';
import { MetadataErrors, MetadataRuntimeValue } from './metadata.js';
 
export const MetadataRecord = type({
	'[string]': {
		value: [
			type.or(
				'null',
				MetadataRuntimeValue.boolean,
				MetadataRuntimeValue.integer,
				MetadataRuntimeValue.float,
				MetadataRuntimeValue.string,
				// MetadataRuntimeValue.date
				// Date is not compatible with JSON Schemas, use a datestring instead
				'string.date.iso',
				MetadataRuntimeValue.location,
				MetadataRuntimeValue.boundingbox
			),
			'@',
			'Valeur de la métadonnée'
		],
		'valueLabel?': [
			'string',
			'@',
			"Label de la valeur de la métadonnée. Existe pour les métadonnées de type enum, contient dans ce cas le label associé à la clé de l'option de l'enum choisie"
		],
		confidence: ['number', '@', 'Confiance dans la valeur de la métadonnée, entre 0 et 1'],
		manuallyModified: [
			'boolean',
			'@',
			'La valeur de la métadonnée a été modifiée manuellement'
		],
		confirmed: type('boolean')
			.describe('La valeur de la métadonnée a été confirmée par un·e utilisateurice')
			.default(false),
		alternatives: type({
			'[string]': [
				'number',
				'@',
				'Confiance dans cette valeur alternative de la métadonnée, entre 0 et 1.'
			]
		}).describe(
			"Autres valeurs possibles. Les clés de l'objet sont les autres valeurs possibles pour cette métadonnée (converties en texte via JSON), les valeurs de l'objet sont les confiances associées à ces alternatives."
		)
	}
});
 
export const AnalyzedImage = type({
	id: ['string', '@', "ID de l'image"],
	fileId: ['string | null', '@', "ID du fichier source de l'image"],
	filename: ['string', '@', 'Nom du fichier utilisé pour cette image'],
	contentType: [
		'string',
		'@',
		"Type de contenu de l'image, au format MIME (exemple: image/jpeg)"
	],
	sequence: [
		'number > 0',
		'@',
		"Numéro de séquence de l'image dans l'archive .zip. Unique à l'entièreté de l'export"
	],
	numberInObservation: ['number > 0', '@', "Numéro de l'image dans l'observation"],
	metadata: MetadataRecord,
	metadataErrors: MetadataErrors.default(() => ({})),
	exportedAs: type({
		original: ['string', '@', "Chemin vers l'image originale"],
		cropped: ['string', '@', "Chemin vers l'image recadrée"]
	}).describe("Chemins dans l'archive .zip vers l'image exportée")
});
 
export const AnalyzedObservation = type({
	number: ['number > 0', '@', "Numéro de l'observation dans l'export"],
	label: ['string', '@', "Label de l'observation"],
	images: AnalyzedImage.array(),
	metadata: MetadataRecord,
	metadataErrors: MetadataErrors.default(() => ({})),
	protocolMetadata: MetadataRecord.describe(
		"Métadonnées définies par le protocole. Les clés de l'objet sont les identifiants des métadonnées, sans le préfixe qui identifie leur protocole de provenance"
	)
});
 
/**
 * @param {Record<string, Omit<import('$lib/database.js').MetadataValue, 'value'> & { value: null | import('$lib/schemas/metadata.js').RuntimeValue }>} values
 * @returns {typeof MetadataRecord.infer}
 */
export function toMetadataRecord(values) {
	return mapValues(values, ({ value, ...rest }) => ({
		value: value instanceof Date ? value.toISOString() : value,
		...rest
	}));
}
 
if (import.meta.vitest) {
	const { describe, it, expect } = import.meta.vitest;
	describe('toMetadataRecord', () => {
		it('should convert Date to ISO string', () => {
			const input = {
				meta1: {
					value: new Date('2024-01-01T12:00:00Z'),
					confidence: 0.9,
					manuallyModified: false,
					confirmed: true,
					alternatives: {
						alt1: 0.7
					}
				}
			};
			const output = toMetadataRecord(input);
			expect(output.meta1).toMatchObject({
				...input.meta1,
				value: '2024-01-01T12:00:00.000Z'
			});
		});
		it('should keep other types unchanged', () => {
			const input = {
				meta1: {
					value: 42,
					confidence: 0.8,
					manuallyModified: true,
					confirmed: false,
					alternatives: {
						alt1: 0.7
					}
				},
				meta2: {
					value: 'test',
					confidence: 1,
					manuallyModified: false,
					confirmed: false,
					alternatives: {
						alt1: 0.7
					}
				},
				meta3: {
					value: null,
					confidence: 0,
					manuallyModified: false,
					confirmed: false,
					alternatives: {
						alt1: 0.7
					}
				}
			};
			const output = toMetadataRecord(input);
			expect(output.meta1).toMatchObject(input.meta1);
			expect(output.meta2).toMatchObject(input.meta2);
			expect(output.meta3).toMatchObject(input.meta3);
		});
	});
}