Mirror of https://github.com/roostorg/coop
github.com/roostorg/coop
1import { type Dependencies } from '../../iocContainer/index.js';
2import { inject } from '../../iocContainer/utils.js';
3import {
4 type ItemSubmission,
5 type NormalizedItemData,
6 type RawItemData,
7} from '../../services/itemProcessingService/index.js';
8import { jsonStringifyUnstable } from '../../utils/encoding.js';
9import { getUtcDateOnlyString } from '../../utils/time.js';
10
11// NB: when an incoming POST /items/scores api request fails, the content submission
12// logged to the data warehouse might not be in a valid, processable shape (in fact, it
13// may be that the content api request failed _because_ the content submission
14// was invalid).
15export type ItemModelScoreLogEntry<HasFailure extends boolean> = {
16 orgId: string;
17 itemSubmission: Pick<
18 ItemSubmission,
19 'submissionId' | 'creator' | 'itemId' | 'itemType' | 'submissionTime'
20 > &
21 (HasFailure extends false
22 ? { data: NormalizedItemData }
23 : { data: NormalizedItemData | RawItemData });
24 failureReason: HasFailure extends true
25 ? string
26 : HasFailure extends false
27 ? undefined
28 : string | undefined;
29 model: HasFailure extends true
30 ? undefined
31 : {
32 id: string;
33 version: number;
34 score?: number;
35 };
36};
37
38class ItemModelScoreLogger {
39 constructor(
40 private readonly analytics: Dependencies['DataWarehouseAnalytics'],
41 ) {}
42
43 async logItemModelScore<HasFailure extends boolean>(
44 data: ItemModelScoreLogEntry<HasFailure>,
45 skipBatch: boolean,
46 ) {
47 const { failureReason, itemSubmission } = data;
48 const { itemType } = itemSubmission;
49 const now = new Date();
50 await this.analytics.bulkWrite(
51 'ITEM_MODEL_SCORES_LOG',
52 [
53 {
54 ds: getUtcDateOnlyString(now),
55 ts: now.valueOf(),
56 item_id: itemSubmission.itemId,
57 item_data: jsonStringifyUnstable(itemSubmission.data),
58 ...(itemSubmission.creator !== undefined
59 ? {
60 item_creator_id: itemSubmission.creator.id,
61 item_creator_type_id: itemSubmission.creator.typeId,
62 }
63 : {}),
64 item_type_kind: itemType.kind,
65 item_type_name: itemType.name,
66 item_type_version: itemType.version,
67 item_type_schema_variant: itemType.schemaVariant,
68 item_type_id: itemType.id,
69 item_type_schema: jsonStringifyUnstable(itemType.schema),
70 item_type_schema_field_roles: itemType.schemaFieldRoles,
71 org_id: data.orgId,
72 submission_id: itemSubmission.submissionId,
73 ...(data.model
74 ? {
75 model_id: data.model.id,
76 model_version: data.model.version,
77 model_score: data.model.score,
78 }
79 : {}),
80 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
81 ...(failureReason != null
82 ? {
83 event: 'REQUEST_FAILED' as const,
84 failure_reason: failureReason,
85 }
86 : { event: 'REQUEST_SUCCEEDED' as const }),
87 },
88 ],
89 { batchTimeout: skipBatch ? 0 : undefined },
90 );
91 }
92}
93
94export default inject(['DataWarehouseAnalytics'], ItemModelScoreLogger);
95export { type ItemModelScoreLogger };