File size: 3,095 Bytes
6f1c297
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ad438b8
 
 
 
 
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
import { query } from '../db/client.js';

interface IssueMeasureRow {
	id: string;
	score_id: string;
	measure_index: number;
	measure: any;
	status: number;
	by_user: boolean;
	annotator: string | null;
	created_at: Date;
	updated_at: Date;
}

interface IssueMeasureResult {
	id: string;
	scoreId: string;
	measureIndex: number;
	measure: any;
	status: number;
	byUser: boolean;
	annotator: string | null;
	lastUpdate: Date;
}

function rowToResult(row: IssueMeasureRow): IssueMeasureResult {
	return {
		id: row.id,
		scoreId: row.score_id,
		measureIndex: row.measure_index,
		measure: row.measure,
		status: row.status,
		byUser: row.by_user,
		annotator: row.annotator,
		lastUpdate: row.updated_at,
	};
}

export async function list(
	scoreId: string,
	{ offset = 0, limit = 200, status }: { offset?: number; limit?: number; status?: number[] } = {}
): Promise<{ count: number; rows: IssueMeasureResult[] }> {
	const params: any[] = [scoreId];
	let whereClause = 'WHERE score_id = $1';

	if (status && status.length > 0) {
		const placeholders = status.map((_, i) => `$${i + 2}`).join(', ');
		whereClause += ` AND status IN (${placeholders})`;
		params.push(...status);
	}

	const countResult = await query(`SELECT COUNT(*)::int as count FROM issue_measures ${whereClause}`, params);

	const paramOffset = params.length;
	const { rows } = await query<IssueMeasureRow>(
		`SELECT * FROM issue_measures ${whereClause}
		ORDER BY measure_index ASC
		LIMIT $${paramOffset + 1} OFFSET $${paramOffset + 2}`,
		[...params, limit, offset]
	);

	return {
		count: countResult.rows[0].count,
		rows: rows.map(rowToResult),
	};
}

export async function upsert(scoreId: string, measureIndex: number, measure: any, status: number, annotator?: string | null): Promise<IssueMeasureResult> {
	// Try to find existing record for this score+measure (any status)
	const { rows: existing } = await query<IssueMeasureRow>(
		'SELECT * FROM issue_measures WHERE score_id = $1 AND measure_index = $2 ORDER BY updated_at DESC LIMIT 1',
		[scoreId, measureIndex]
	);

	let row: IssueMeasureRow;
	// undefined = not provided (preserve existing), null = explicitly cleared
	const annotatorProvided = annotator !== undefined;

	if (existing.length > 0) {
		const { rows } = await query<IssueMeasureRow>(
			`UPDATE issue_measures
			SET measure = $1, status = $2, annotator = ${annotatorProvided ? '$3' : 'COALESCE($3, annotator)'}, updated_at = NOW()
			WHERE id = $4
			RETURNING *`,
			[JSON.stringify(measure), status, annotator ?? null, existing[0].id]
		);
		row = rows[0];
	} else {
		const { rows } = await query<IssueMeasureRow>(
			`INSERT INTO issue_measures (score_id, measure_index, measure, status, by_user, annotator)
			VALUES ($1, $2, $3, $4, false, $5)
			RETURNING *`,
			[scoreId, measureIndex, JSON.stringify(measure), status, annotator ?? null]
		);
		row = rows[0];
	}

	return rowToResult(row);
}

export async function deleteByScore(scoreId: string): Promise<number> {
	const result = await query('DELETE FROM issue_measures WHERE score_id = $1', [scoreId]);
	return result.rowCount ?? 0;
}