k-l-lambda commited on
Commit
ad438b8
·
1 Parent(s): 8c7d858

update: export from starry-refactor 2026-02-21 (add example score seed)

Browse files
.gitattributes CHANGED
@@ -38,3 +38,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
38
  *.eot filter=lfs diff=lfs merge=lfs -text
39
  *.otf filter=lfs diff=lfs merge=lfs -text
40
  *.woff2 filter=lfs diff=lfs merge=lfs -text
 
 
38
  *.eot filter=lfs diff=lfs merge=lfs -text
39
  *.otf filter=lfs diff=lfs merge=lfs -text
40
  *.woff2 filter=lfs diff=lfs merge=lfs -text
41
+ *.png filter=lfs diff=lfs merge=lfs -text
Dockerfile CHANGED
@@ -123,6 +123,11 @@ COPY --chown=node backend/python-services/ ./backend/python-services/
123
  # --- Supervisord config ---
124
  COPY --chown=node supervisord.conf ./supervisord.conf
125
 
 
 
 
 
 
126
  # --- Config files ---
127
  COPY --chown=node docker-entrypoint.sh ./docker-entrypoint.sh
128
  COPY --chown=node nginx.conf /etc/nginx/nginx.conf
 
123
  # --- Supervisord config ---
124
  COPY --chown=node supervisord.conf ./supervisord.conf
125
 
126
+ # --- Example score for seeding ---
127
+ COPY --chown=node example-score/ ./example-score/
128
+ COPY --chown=node seed-example.sh ./seed-example.sh
129
+ RUN chmod +x seed-example.sh
130
+
131
  # --- Config files ---
132
  COPY --chown=node docker-entrypoint.sh ./docker-entrypoint.sh
133
  COPY --chown=node nginx.conf /etc/nginx/nginx.conf
backend/omr-service/src/db/client.ts CHANGED
@@ -9,9 +9,9 @@ export const pool = new Pool({
9
  database: config.database.database,
10
  user: config.database.user,
11
  password: config.database.password,
12
- max: 10,
13
  idleTimeoutMillis: 30000,
14
- connectionTimeoutMillis: 2000,
15
  });
16
 
17
  export async function query<T extends pg.QueryResultRow = any>(text: string, params?: any[]): Promise<pg.QueryResult<T>> {
 
9
  database: config.database.database,
10
  user: config.database.user,
11
  password: config.database.password,
12
+ max: 20,
13
  idleTimeoutMillis: 30000,
14
+ connectionTimeoutMillis: 10000,
15
  });
16
 
17
  export async function query<T extends pg.QueryResultRow = any>(text: string, params?: any[]): Promise<pg.QueryResult<T>> {
backend/omr-service/src/lib/dbSolutionStore.ts CHANGED
@@ -7,7 +7,11 @@ export const DbSolutionStore = {
7
  return result ?? null;
8
  },
9
  async set(key: string, val: any) {
10
- await solutionCacheService.set(key, val);
 
 
 
 
11
  },
12
  async batchGet(keys: string[]) {
13
  return solutionCacheService.batchGet(keys);
 
7
  return result ?? null;
8
  },
9
  async set(key: string, val: any) {
10
+ try {
11
+ await solutionCacheService.set(key, val);
12
+ } catch (err) {
13
+ console.error('[DbSolutionStore] set failed (non-fatal):', (err as Error).message);
14
+ }
15
  },
16
  async batchGet(keys: string[]) {
17
  return solutionCacheService.batchGet(keys);
backend/omr-service/src/routes/issueMeasures.ts CHANGED
@@ -51,4 +51,10 @@ export default async function issueMeasuresRoutes(fastify: FastifyInstance) {
51
 
52
  return { code: 0, data: result };
53
  });
 
 
 
 
 
 
54
  }
 
51
 
52
  return { code: 0, data: result };
53
  });
54
+
55
+ // Delete all issue measures for a score
56
+ fastify.delete<{ Params: ScoreParams }>('/scores/:id/issueMeasures', async (request) => {
57
+ const count = await issueMeasureService.deleteByScore(request.params.id);
58
+ return { code: 0, data: { deleted: count } };
59
+ });
60
  }
backend/omr-service/src/services/issueMeasure.service.ts CHANGED
@@ -97,3 +97,8 @@ export async function upsert(scoreId: string, measureIndex: number, measure: any
97
 
98
  return rowToResult(row);
99
  }
 
 
 
 
 
 
97
 
98
  return rowToResult(row);
99
  }
100
+
101
+ export async function deleteByScore(scoreId: string): Promise<number> {
102
+ const result = await query('DELETE FROM issue_measures WHERE score_id = $1', [scoreId]);
103
+ return result.rowCount ?? 0;
104
+ }
backend/omr-service/src/services/musicSet.service.ts CHANGED
@@ -65,7 +65,7 @@ export async function createMusicSet(input: CreateMusicSetInput): Promise<MusicS
65
 
66
  export async function getMusicSet(id: string): Promise<MusicSet | null> {
67
  const { rows } = await query<MusicSet>(
68
- `SELECT ms.*,
69
  (SELECT COALESCE(json_agg(json_build_object('id', t.id, 'name', t.name)), '[]'::json)
70
  FROM music_set_tags mst
71
  JOIN tags t ON mst.tag_id = t.id
@@ -98,8 +98,10 @@ export async function listMusicSets(params: ListParams): Promise<{ rows: MusicSe
98
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
99
 
100
  // Validate sortedBy to prevent SQL injection
 
101
  const allowedSortFields = ['name', 'type', 'updated_at', 'created_at'];
102
- const sortField = allowedSortFields.includes(sortedBy) ? sortedBy : 'updated_at';
 
103
  const sortOrder = sortedType === 'asc' ? 'ASC' : 'DESC';
104
 
105
  // Get count
@@ -109,7 +111,7 @@ export async function listMusicSets(params: ListParams): Promise<{ rows: MusicSe
109
  // Get rows with tags
110
  values.push(limit, offset);
111
  const { rows } = await query<MusicSet>(
112
- `SELECT ms.*,
113
  (SELECT COALESCE(json_agg(json_build_object('id', t.id, 'name', t.name)), '[]'::json)
114
  FROM music_set_tags mst
115
  JOIN tags t ON mst.tag_id = t.id
@@ -160,7 +162,7 @@ export async function updateMusicSet(id: string, input: UpdateMusicSetInput): Pr
160
 
161
  // Return updated music set with tags
162
  const { rows } = await client.query<MusicSet>(
163
- `SELECT ms.*,
164
  (SELECT COALESCE(json_agg(json_build_object('id', t.id, 'name', t.name)), '[]'::json)
165
  FROM music_set_tags mst
166
  JOIN tags t ON mst.tag_id = t.id
 
65
 
66
  export async function getMusicSet(id: string): Promise<MusicSet | null> {
67
  const { rows } = await query<MusicSet>(
68
+ `SELECT ms.*, ms.updated_at AS "lastUpdateAt",
69
  (SELECT COALESCE(json_agg(json_build_object('id', t.id, 'name', t.name)), '[]'::json)
70
  FROM music_set_tags mst
71
  JOIN tags t ON mst.tag_id = t.id
 
98
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
99
 
100
  // Validate sortedBy to prevent SQL injection
101
+ const sortFieldMap: Record<string, string> = { lastUpdateAt: 'updated_at' };
102
  const allowedSortFields = ['name', 'type', 'updated_at', 'created_at'];
103
+ const resolved = sortFieldMap[sortedBy] || sortedBy;
104
+ const sortField = allowedSortFields.includes(resolved) ? resolved : 'updated_at';
105
  const sortOrder = sortedType === 'asc' ? 'ASC' : 'DESC';
106
 
107
  // Get count
 
111
  // Get rows with tags
112
  values.push(limit, offset);
113
  const { rows } = await query<MusicSet>(
114
+ `SELECT ms.*, ms.updated_at AS "lastUpdateAt",
115
  (SELECT COALESCE(json_agg(json_build_object('id', t.id, 'name', t.name)), '[]'::json)
116
  FROM music_set_tags mst
117
  JOIN tags t ON mst.tag_id = t.id
 
162
 
163
  // Return updated music set with tags
164
  const { rows } = await client.query<MusicSet>(
165
+ `SELECT ms.*, ms.updated_at AS "lastUpdateAt",
166
  (SELECT COALESCE(json_agg(json_build_object('id', t.id, 'name', t.name)), '[]'::json)
167
  FROM music_set_tags mst
168
  JOIN tags t ON mst.tag_id = t.id
docker-entrypoint.sh CHANGED
@@ -256,6 +256,11 @@ for i in $(seq 1 30); do
256
  sleep 1
257
  done
258
 
 
 
 
 
 
259
  # ── Start nginx (port 7860, foreground) ──
260
  echo 'Starting nginx on port 7860...'
261
  exec nginx -g 'daemon off;'
 
256
  sleep 1
257
  done
258
 
259
+ # ── Seed example score (background — waits for ML predictors internally) ──
260
+ if [ -f /home/node/app/seed-example.sh ]; then
261
+ /home/node/app/seed-example.sh &
262
+ fi
263
+
264
  # ── Start nginx (port 7860, foreground) ──
265
  echo 'Starting nginx on port 7860...'
266
  exec nginx -g 'daemon off;'
example-score/page-00.png ADDED

Git LFS Details

  • SHA256: 09140eb24ae667dc3a123b87d2c487ef1036f2022aa80c758df3729eb25535f0
  • Pointer size: 132 Bytes
  • Size of remote file: 1.37 MB
example-score/page-01.png ADDED

Git LFS Details

  • SHA256: 5d2a9ee6c027e3192d4aef7908c58ba44ca3f63c6140a9aaeb4a907d0c9d2320
  • Pointer size: 132 Bytes
  • Size of remote file: 1.77 MB
example-score/page-02.png ADDED

Git LFS Details

  • SHA256: 2ecb0269ac0ce99d32917f4f94e3f0ead43090ba9f49c18d03eb75f12ca8ca4b
  • Pointer size: 132 Bytes
  • Size of remote file: 1.69 MB
example-score/page-03.png ADDED

Git LFS Details

  • SHA256: bf3922366b7bb44519a705a51fb284d869c18dd18c7a22ef3cfa3a99a0190efe
  • Pointer size: 132 Bytes
  • Size of remote file: 1.66 MB
seed-example.sh ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ #
3
+ # Seed the HF Space with a pre-recognized example score.
4
+ #
5
+ # Runs in background after services start. Waits for ML predictors,
6
+ # then uploads example page images through predict/pages pipeline.
7
+ #
8
+ set -euo pipefail
9
+
10
+ API_BASE="http://127.0.0.1:3080"
11
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12
+ IMAGE_DIR="$SCRIPT_DIR/example-score"
13
+ SCORE_ID="c8d853b1-f18b-5692-a113-39692e17f395"
14
+ SCORE_TITLE="One Summer's Day"
15
+ MAX_WAIT=900 # 15 min for model download + load on CPU
16
+ POLL_INTERVAL=5
17
+
18
+ log() { echo "[seed] $*"; }
19
+
20
+ # ── Step 0: Check if score already exists with data ──────────────────────
21
+ log "Checking if example score already exists..."
22
+ EXISTING=$(curl -sf "$API_BASE/api/scores/$SCORE_ID" 2>/dev/null || echo "")
23
+ if echo "$EXISTING" | python3 -c "
24
+ import json, sys
25
+ try:
26
+ d = json.load(sys.stdin)
27
+ pages = d.get('data', d).get('pages', [])
28
+ if len(pages) > 0:
29
+ sys.exit(0)
30
+ except: pass
31
+ sys.exit(1)
32
+ " 2>/dev/null; then
33
+ log "Example score already has pages, skipping seed."
34
+ exit 0
35
+ fi
36
+
37
+ # ── Step 1: Wait for ML predictors ──────────────────────────────────────
38
+ log "Waiting for ML predictors to be ready (up to ${MAX_WAIT}s)..."
39
+ ELAPSED=0
40
+ while [ $ELAPSED -lt $MAX_WAIT ]; do
41
+ # Try a lightweight predict/layout call to check if the layout predictor is up.
42
+ # We send a tiny 1x1 PNG as a probe — if it responds (even with an error result),
43
+ # the predictor is loaded.
44
+ PROBE_RES=$(curl -sf -X POST "$API_BASE/api/predict/layout" \
45
+ -H "Content-Type: application/json" \
46
+ -d '{"images":["iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="]}' \
47
+ 2>/dev/null || echo "")
48
+ if [ -n "$PROBE_RES" ]; then
49
+ log "Predictors are ready."
50
+ break
51
+ fi
52
+ sleep 10
53
+ ELAPSED=$((ELAPSED + 10))
54
+ if [ $((ELAPSED % 60)) -eq 0 ]; then
55
+ log " Still waiting... (${ELAPSED}s elapsed)"
56
+ fi
57
+ done
58
+
59
+ if [ $ELAPSED -ge $MAX_WAIT ]; then
60
+ log "WARNING: Timed out waiting for predictors. Attempting seed anyway..."
61
+ fi
62
+
63
+ # ── Step 2: Create score shell ───────────────────────────────────────────
64
+ log "Creating score shell ($SCORE_ID)..."
65
+ curl -sf -X PUT "$API_BASE/api/scores/$SCORE_ID/data" \
66
+ -H "Content-Type: application/json" \
67
+ -d '{"pages":[],"patches":[],"tags":[]}' > /dev/null
68
+
69
+ # ── Step 3: Upload images + predict ──────────────────────────────────────
70
+ log "Submitting predict/pages for 4 pages..."
71
+ CURL_ARGS=(-F 'processes=["gauge","semantic","mask","brackets","text"]')
72
+ for i in 0 1 2 3; do
73
+ IMG="$IMAGE_DIR/page-0${i}.png"
74
+ if [ ! -f "$IMG" ]; then
75
+ log "ERROR: Missing image: $IMG"
76
+ exit 1
77
+ fi
78
+ CURL_ARGS+=(-F "page${i}=@${IMG};type=image/png")
79
+ done
80
+
81
+ PREDICT_RES=$(curl -sf -X POST "$API_BASE/api/predict/pages/$SCORE_ID" "${CURL_ARGS[@]}")
82
+ TASK_ID=$(echo "$PREDICT_RES" | python3 -c "import json,sys; print(json.load(sys.stdin)['task_id'])")
83
+ log "Task ID: $TASK_ID"
84
+
85
+ # ── Step 4: Poll task ────────────────────────────────────────────────────
86
+ log "Polling task..."
87
+ ELAPSED=0
88
+ while [ $ELAPSED -lt $MAX_WAIT ]; do
89
+ POLL_RES=$(curl -sf "$API_BASE/api/tasks/$TASK_ID/poll" 2>/dev/null || echo '{"status":"unknown"}')
90
+ STATUS=$(echo "$POLL_RES" | python3 -c "import json,sys; print(json.load(sys.stdin).get('status','unknown'))" 2>/dev/null || echo "unknown")
91
+
92
+ case "$STATUS" in
93
+ completed)
94
+ log "Prediction completed!"
95
+ echo "$POLL_RES" | python3 -c "
96
+ import json, sys
97
+ r = json.load(sys.stdin).get('result', {})
98
+ print(f'[seed] Systems: {r.get(\"systems\",\"?\")}, Staves: {r.get(\"staves\",\"?\")}, StaffLayout: {r.get(\"staffLayout\",\"?\")}')
99
+ " 2>/dev/null || true
100
+ break
101
+ ;;
102
+ failed|error)
103
+ log "ERROR: Prediction task failed!"
104
+ echo "$POLL_RES" | python3 -m json.tool 2>/dev/null || echo "$POLL_RES"
105
+ exit 1
106
+ ;;
107
+ *)
108
+ sleep $POLL_INTERVAL
109
+ ELAPSED=$((ELAPSED + POLL_INTERVAL))
110
+ ;;
111
+ esac
112
+ done
113
+
114
+ if [ $ELAPSED -ge $MAX_WAIT ]; then
115
+ log "ERROR: Task polling timed out after ${MAX_WAIT}s"
116
+ exit 1
117
+ fi
118
+
119
+ # ── Step 5: Set title ────────────────────────────────────────────────────
120
+ log "Setting score title: $SCORE_TITLE"
121
+ curl -sf -X PUT "$API_BASE/api/scores/$SCORE_ID" \
122
+ -H "Content-Type: application/json" \
123
+ -d "{\"title\":\"$SCORE_TITLE\"}" > /dev/null || log "WARNING: Failed to set title"
124
+
125
+ # ── Step 6: Regulation (optional) ────────────────────────────────────────
126
+ log "Running regulation..."
127
+ REG_RES=$(curl -sf -X POST "$API_BASE/api/scores/$SCORE_ID/regulate" --max-time 600 2>/dev/null || echo "")
128
+ if [ -n "$REG_RES" ]; then
129
+ echo "$REG_RES" | python3 -c "
130
+ import json, sys
131
+ r = json.load(sys.stdin)
132
+ if r.get('code') == 0:
133
+ s = r['data']['stat']
134
+ qs = s.get('qualityScore', 0)
135
+ m = s.get('measures', {})
136
+ print(f'[seed] Quality: {qs*100:.1f}%')
137
+ print(f'[seed] Measures: solved={m.get(\"solved\",0)} issue={m.get(\"issue\",0)} fatal={m.get(\"fatal\",0)}')
138
+ else:
139
+ print(f'[seed] Regulation error: {r.get(\"message\",\"unknown\")}')
140
+ " 2>/dev/null || true
141
+ else
142
+ log "WARNING: Regulation failed or timed out (non-critical)."
143
+ fi
144
+
145
+ log "=== Seed complete ==="
146
+ log "View at: /playground/$SCORE_ID"