Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
沈秋雨
/
import_hk_songs
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Commit
527d6149
...
527d6149e175118d3b213143f82dcc65efa45682
authored
2026-07-21 14:45:59 +0800
by
沈秋雨
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
feat(review): 支持同词不同曲和同曲不同词的合并类型审核
1 parent
2ad02073
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
40 additions
and
13 deletions
l2_review_dashboard.html
serve_l2_dashboard.py
l2_review_dashboard.html
View file @
527d614
...
...
@@ -991,6 +991,7 @@
row
.
review_version
=
String
(
record
.
review_version
??
row
.
review_version
??
0
);
row
.
review_claimed_by
=
record
.
review_claimed_by
??
''
;
row
.
review_claim_expires_at
=
record
.
review_claim_expires_at
??
''
;
row
.
merge_diff_type
=
record
.
merge_diff_type
??
''
;
row
.
staging_status
=
record
.
staging_status
??
row
.
staging_status
;
row
.
claimed_by_me
=
Boolean
(
record
.
claimed_by_me
)
||
row
.
review_claimed_by
===
state
.
reviewer
;
}
...
...
@@ -1183,7 +1184,7 @@
</div>`
;
}
async
function
updateStagingReview
(
query
,
decision
,
note
,
candidateId
=
''
)
{
async
function
updateStagingReview
(
query
,
decision
,
note
,
candidateId
=
''
,
mergeDiffType
=
''
)
{
return
reviewPostJSON
(
'/api/staging-review'
,
{
staging_id
:
String
(
query
.
staging_id
),
expected_version
:
Number
(
query
.
review_version
||
0
),
...
...
@@ -1191,13 +1192,16 @@
reviewer
:
state
.
reviewer
,
decision
,
note
,
candidate_id
:
candidateId
candidate_id
:
candidateId
,
merge_diff_type
:
mergeDiffType
});
}
const
_DB_REVIEW_LABELS
=
{
approved_import
:
'确认不重复(待入库)'
,
rejected_duplicate
:
'确认重复(待合并作者)'
,
same_lyric_diff_melody
:
'同词不同曲(待合并)'
,
diff_lyric_same_melody
:
'同曲不同词(待合并)'
,
unsure
:
'待确认'
,
deleted
:
'已删除'
,
};
...
...
@@ -1288,6 +1292,10 @@
const
candidateComposer
=
candidate
?.
candidate_composer
;
const
reason
=
candidate
?.
candidate_reason
;
const
selectedReview
=
candidate
?
getReview
(
candidate
)
:
{};
// 从 DB 状态恢复 merge_diff_type 对应的按钮高亮
if
(
!
selectedReview
.
final_decision
&&
query
.
merge_diff_type
)
{
selectedReview
.
final_decision
=
query
.
merge_diff_type
;
}
const
conflict
=
candidate
?
isConflict
(
candidate
)
:
false
;
const
l1
=
candidate
?
l1Match
(
candidate
)
:
false
;
const
queryAudioUrl
=
query
.
audio_url
||
''
;
...
...
@@ -1351,9 +1359,11 @@
<span style="flex:1">
<b>
${
esc
(
query
.
review_status
===
'approved_import'
&&
query
.
staging_status
===
'imported'
?
'确认不重复(已入库)'
:
query
.
review_status
===
'rejected_duplicate'
&&
query
.
staging_status
===
'skipped'
?
'确认重复(作者已合并)'
:
(
_DB_REVIEW_LABELS
[
query
.
review_status
]
||
query
.
review_status
))}
</b>
:
query
.
merge_diff_type
?
`
${
_DB_REVIEW_LABELS
[
query
.
merge_diff_type
]
||
query
.
merge_diff_type
}
`
:
query
.
review_status
===
'rejected_duplicate'
&&
query
.
staging_status
===
'skipped'
?
'确认重复(作者已合并)'
:
(
_DB_REVIEW_LABELS
[
query
.
review_status
]
||
query
.
review_status
))}
</b>
${
query
.
reviewed_by
?
` · 审核人
${
esc
(
query
.
reviewed_by
)}
`
:
''
}
${
query
.
reviewed_at
?
` ·
${
esc
(
query
.
reviewed_at
)}
`
:
''
}
${
query
.
review_note
?
` · 备注:
${
esc
(
query
.
review_note
)}
`
:
''
}
...
...
@@ -1362,9 +1372,9 @@
</div>
`
:
needsManualReview
?
`
<div class="review-bar">
${[
'duplicate'
,
'not_duplicate'
,
'unsure'
].
map
(
value
=>
`
${[
'duplicate'
,
'
same_lyric_diff_melody'
,
'diff_lyric_same_melody'
,
'
not_duplicate'
,
'unsure'
].
map
(
value
=>
`
<button class="review-choice
${
selectedReview
.
final_decision
===
value
?
'active'
:
''
}
" data-review="
${
value
}
">
${
value
===
'duplicate'
?
'确认重复'
:
value
===
'not_duplicate'
?
'确认不重复'
:
'待确认'
}
${
{
duplicate
:
'确认重复'
,
same_lyric_diff_melody
:
'同词不同曲'
,
diff_lyric_same_melody
:
'同曲不同词'
,
not_duplicate
:
'确认不重复'
,
unsure
:
'待确认'
}
[value]
}
</button>`
).
join
(
''
)}
<input id="reviewNote" value="
${
esc
(
selectedReview
.
note
||
''
)}
" placeholder="人工备注">
<button id="deleteReviewBtn" class="secondary" style="color:#c0392b;border-color:#c0392b">删除</button>
...
...
@@ -1402,7 +1412,17 @@
renderDetail
();
});
}
const
_REVIEW_DECISION_MAP
=
{
duplicate
:
'rejected_duplicate'
,
not_duplicate
:
'approved_import'
,
unsure
:
'unsure'
};
const
_REVIEW_DECISION_MAP
=
{
duplicate
:
'rejected_duplicate'
,
same_lyric_diff_melody
:
'rejected_duplicate'
,
diff_lyric_same_melody
:
'rejected_duplicate'
,
not_duplicate
:
'approved_import'
,
unsure
:
'unsure'
};
const
_MERGE_DIFF_TYPE_MAP
=
{
same_lyric_diff_melody
:
'same_lyric_diff_melody'
,
diff_lyric_same_melody
:
'diff_lyric_same_melody'
};
for
(
const
btn
of
els
.
detail
.
querySelectorAll
(
'.review-choice'
))
{
btn
.
addEventListener
(
'click'
,
async
()
=>
{
if
(
!
candidate
)
return
;
...
...
@@ -1416,8 +1436,11 @@
renderDetail
();
return
;
}
const
mergeDiffType
=
_MERGE_DIFF_TYPE_MAP
[
localDecision
]
||
''
;
const
payload
=
await
updateStagingReview
(
query
,
dbDecision
,
note
,
localDecision
===
'duplicate'
?
(
candidate
?.
candidate_id
||
''
)
:
''
query
,
dbDecision
,
note
,
(
localDecision
===
'duplicate'
||
mergeDiffType
)
?
(
candidate
?.
candidate_id
||
''
)
:
''
,
mergeDiffType
);
for
(
const
row
of
state
.
queryRows
)
applyReviewSnapshot
(
row
,
payload
.
record
);
// 刷新统计和加载列表并行
...
...
serve_l2_dashboard.py
View file @
527d614
...
...
@@ -324,6 +324,7 @@ def _staging_dashboard_row(row: dict[str, object]) -> dict[str, str]:
"review_claimed_by"
:
str
(
row
.
get
(
"review_claimed_by"
)
or
""
),
"review_claim_expires_at"
:
str
(
row
.
get
(
"review_claim_expires_at"
)
or
""
),
"staging_status"
:
str
(
row
.
get
(
"staging_status"
)
or
""
),
"merge_diff_type"
:
str
(
row
.
get
(
"merge_diff_type"
)
or
""
),
"l1_metadata_match"
:
"1"
if
row
.
get
(
"l1_matched_id"
)
else
"0"
,
"l1_l2_conflict"
:
"0"
,
"audio_url"
:
str
(
row
.
get
(
"audio_url"
)
or
""
),
...
...
@@ -582,7 +583,7 @@ def _review_snapshot(cursor, staging_id: int) -> dict[str, object]:
f
"""
SELECT staging_id, source_song_id, dedup_action, biz_review_status, biz_review_note,
reviewed_by, reviewed_at, review_version, review_claimed_by,
review_claim_expires_at, staging_status
review_claim_expires_at, staging_status
, merge_diff_type
FROM {TARGET_TABLE_NAME_TMP}
WHERE staging_id =
%
s
"""
,
...
...
@@ -684,6 +685,7 @@ def _update_staging_review(
expected_version
:
int
,
client_token
:
str
,
candidate_id
:
str
=
""
,
merge_diff_type
:
str
=
""
,
)
->
dict
[
str
,
object
]:
"""Persist one claimed review using optimistic concurrency control."""
if
decision
==
"pending"
:
...
...
@@ -699,7 +701,8 @@ def _update_staging_review(
elif
decision
==
"rejected_duplicate"
:
status_fields
=
"""
biz_review_status =
%
s, reviewed_by =
%
s, reviewed_at = NOW(),
matched_song_id =
%
s
matched_song_id =
%
s,
merge_diff_type = NULLIF(
%
s, '')
"""
else
:
status_fields
=
"biz_review_status =
%
s, reviewed_by =
%
s, reviewed_at = NOW()"
...
...
@@ -713,7 +716,7 @@ def _update_staging_review(
candidate_id
=
candidate_id
.
strip
()
if
not
candidate_id
:
raise
ValueError
(
"确认重复前请先选择一条召回候选"
)
params
=
[
decision
,
reviewer
,
candidate_id
,
note
,
staging_id
,
expected_version
,
client_token
,
reviewer
]
params
=
[
decision
,
reviewer
,
candidate_id
,
merge_diff_type
,
note
,
staging_id
,
expected_version
,
client_token
,
reviewer
]
else
:
params
=
[
decision
,
reviewer
,
note
,
staging_id
,
expected_version
,
client_token
,
reviewer
]
with
_target_conn
()
as
conn
,
conn
.
cursor
()
as
cursor
:
...
...
@@ -1447,6 +1450,7 @@ class Handler(BaseHTTPRequestHandler):
decision
=
str
(
payload
.
get
(
"decision"
,
""
))
.
strip
()
note
=
str
(
payload
.
get
(
"note"
,
""
))
.
strip
()
candidate_id
=
str
(
payload
.
get
(
"candidate_id"
,
""
))
.
strip
()
merge_diff_type
=
str
(
payload
.
get
(
"merge_diff_type"
,
""
))
.
strip
()
client_token
=
str
(
payload
.
get
(
"client_token"
)
or
""
)
.
strip
()
expected_version
=
int
(
payload
.
get
(
"expected_version"
,
-
1
))
if
not
staging_id
:
...
...
@@ -1456,7 +1460,7 @@ class Handler(BaseHTTPRequestHandler):
if
decision
not
in
(
"approved_import"
,
"rejected_duplicate"
,
"unsure"
,
"pending"
,
"deleted"
):
raise
ValueError
(
f
"invalid decision: {decision}"
)
result
=
_update_staging_review
(
staging_id
,
decision
,
note
,
reviewer
,
expected_version
,
client_token
,
candidate_id
staging_id
,
decision
,
note
,
reviewer
,
expected_version
,
client_token
,
candidate_id
,
merge_diff_type
)
_json_response
(
self
,
{
"record"
:
result
})
except
ReviewConflictError
as
exc
:
...
...
Please
register
or
sign in
to post a comment