Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
964006ec6d | ||
|
|
0c82da7a1d | ||
|
|
93a4f808bf | ||
|
|
7a58f37d9f | ||
|
|
b727284ef3 | ||
|
|
396848e898 | ||
|
|
55926b8dc4 | ||
|
|
ee6a1b88c5 | ||
|
|
1a7519becd | ||
|
|
a534b1b70b | ||
|
|
8fdd21f8a2 | ||
|
|
c561330bd0 | ||
|
|
64c62eb394 | ||
|
|
0d3cd69a0f | ||
|
|
81975b72e4 | ||
|
|
1b21471845 | ||
|
|
15f207ffc6 | ||
|
|
93aa06703b | ||
|
|
8fdd25f569 | ||
|
|
00b5c208ec | ||
|
|
c81bfcf51d | ||
|
|
942a74c86c | ||
|
|
1f95f0231d | ||
|
|
ae0e9d0a08 | ||
|
|
6a91bc73eb | ||
|
|
e15c988bf4 | ||
|
|
f36a30411d | ||
|
|
5d5e4fac50 | ||
|
|
e9027472f4 | ||
|
|
e0eb343ce0 | ||
|
|
8d223177cd | ||
|
|
f9ab79f0c2 | ||
|
|
b68393ab01 | ||
|
|
12ace6f45a | ||
|
|
9a42b463a3 |
@@ -6,18 +6,18 @@
|
||||
Messages API: model `claude-sonnet-4-6`, `temperature=0`,
|
||||
thinking disabled, Batch API where possible.
|
||||
- Prompt definition files live in
|
||||
`prompts/<track>-<step>-<task>.md` (e.g. 01-01-tag.md; no
|
||||
version suffix -- versions live in git history) and are
|
||||
passed verbatim as the system prompt.
|
||||
- Per-song LLM judgments (coding, screening) run the same
|
||||
definition file twice, then a separate arbitration step
|
||||
settles only the script-computed disagreements
|
||||
("2 runs + 1 arbitration"). Free-generation steps run
|
||||
twice and both outputs are pooled, unarbitrated.
|
||||
Vocabulary-building steps (convergence) run once as a
|
||||
recorded pass. If an arbitration or validation outcome is
|
||||
unexpected, revise the definition file and repeat that
|
||||
cycle; never patch results by hand.
|
||||
`prompts/<track>-<step>-<substep>-<task>.md` (e.g.
|
||||
01-01-01-tag.md; the substep number fixes the execution
|
||||
order; no version suffix -- versions live in git history)
|
||||
and are passed verbatim as the system prompt.
|
||||
- LLM steps whose outputs are item-by-item comparable
|
||||
(convergence, coding) run the same definition file twice,
|
||||
then a separate arbitration step settles only the
|
||||
script-computed disagreements ("2 runs + 1 arbitration").
|
||||
Free-generation steps run twice and both outputs are pooled,
|
||||
unarbitrated. If arbitration output is unexpected, revise
|
||||
the definition file and repeat that cycle; never patch
|
||||
results by hand.
|
||||
- Each run of a step is archived self-contained under the
|
||||
destination directory given explicitly on the `run-llm`
|
||||
command line (by convention `runs/<definition-file>/run<N>/`):
|
||||
|
||||
+50
-65
@@ -99,8 +99,8 @@
|
||||
|
||||
## 2026-08-02
|
||||
|
||||
- **`import-lyrics` 不設為子命令,pilot 歌詞改以 `excludes/`
|
||||
私人腳本匯入**。理由:pilot 捕捉檔不隨論文發布,子命令
|
||||
- **`import-lyrics` 不設為子命令,pilot 歌詞改以私人腳本
|
||||
匯入**。理由:pilot 捕捉檔不隨論文發布,子命令
|
||||
形式會在發布的 CLI 裡留下讀者無法執行的死命令——要交待的
|
||||
是「沿用 pilot 捕捉」的事實(記於 lyrics-provenance.csv 與
|
||||
論文方法節),不是工具本身;工具移出專案,發布管線即
|
||||
@@ -178,8 +178,8 @@
|
||||
來源公開可稽核,快照乾淨、overrides 縮小;論文方法節揭露
|
||||
「捕捉前經研究者查證補完」。性別以公開自我認同為準,
|
||||
推測不確定且無佐證者列疑慮清單;查證屬資料策展而非
|
||||
分析,以 Claude Code 輔助、不走分析 API;QS 批次存
|
||||
`excludes/`(私人工作檔,不隨論文發布)。
|
||||
分析,以 Claude Code 輔助、不走分析 API;QS 批次以
|
||||
私人工作檔留存,不隨論文發布。
|
||||
|
||||
## 2026-08-04
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
- **Markdown 檔名一律以 dash 連接**(decision-log.md、
|
||||
research-plan.md、project-structure.md 等;與 data/ 層
|
||||
CSV 檔名慣例一致),定義檔命名慣例同步改為
|
||||
`prompts/<task>-v<N>.md`(如 screen-v1.md),`excludes/`
|
||||
`prompts/<task>-v<N>.md`(如 screen-v1.md),版本庫外的
|
||||
私人工作檔一併改名;`conference_abstract.md` 改名並搬入
|
||||
`paper/`——它是本次年會實際送出的摘要,與全文同屬投稿
|
||||
血脈,不是 `docs/` 的內部工作文件。全 repo 指涉同步更新。
|
||||
@@ -323,6 +323,51 @@
|
||||
式化、消滅隱性推導」原則的貫徹。比對子命令驗證兩 run 的定義檔與
|
||||
輸入 SHA 一致、缺 run 即失敗。目標目錄已存在即拒絕執行,重跑須明
|
||||
示 `--replace`。
|
||||
- **收斂仲裁演算法定案(逐對裁決)**:不做組對組匹配
|
||||
(兩組是否「同一組」無原則性答案),比較化約為詞對的
|
||||
共組關係——共識塊(交集細分)→ 分歧塊對枚舉 → 仲裁
|
||||
逐對二元裁決(輸入匿名,不含執行別、共識與歌詞)→
|
||||
union-find 遞移重組(固定邊集的連通元件唯一,與順序
|
||||
無關,決定性成立)。仲裁自身亦 2+1:獨立執行兩次,
|
||||
逐對比對,兩次相同即定案,相異者送 `-arb-arb` 終局票
|
||||
(單次,依協定終局)——每個分歧對等同三票多數決,
|
||||
仲裁鏈至此終止。邊界判斷之裁決為單次記錄性決定,不
|
||||
宣稱可再生(可重現性依計畫定義=程序透明+可稽核)。
|
||||
遞移導出的合併(共識「分開」的塊經第三塊橋接併組)為
|
||||
等價關係語意的邏輯結論,照單接受並逐筆記錄供稽核。
|
||||
一致率低於門檻(暫訂 50%,首輪實跑後校準)即不仲裁
|
||||
——視為定義檔約束不足,修訂後重跑該循環。cap 仲裁後
|
||||
組數略超 50 可接受,如實記錄。演算法明文於
|
||||
`methodology.md`,供讀者依循重現。
|
||||
- **收斂執行不命名,組名全權歸命名鏈(命名 2+1)**:
|
||||
merge、cap 的執行輸出改為無名分組(陣列之陣列)——
|
||||
既然命名鏈存在,收斂執行自取的名字毫無用處,徒增
|
||||
兩次執行間「同組不同名」的假分歧。最終分組定案後,
|
||||
`-name` 命名步驟獨立執行兩次對全部組自由命名——逐組
|
||||
比對,同名即定案(單次執行內名字唯一,故定名間必無
|
||||
撞名);異名送 `-name-arb` 擇一仲裁(匿名呈現兩候選,
|
||||
輸出限於候選並避讓已定名,機械可驗證)。理由:組名
|
||||
承重——merge 名為 cap 的輸入詞、cap 名為 code 的編碼
|
||||
類目——凡承重的語意判斷皆受 2+1。cap 的輸入即 merge
|
||||
定案後的組名清單,原始關鍵字不下傳——如同證據不進
|
||||
收斂,上游細節不污染下游步驟。
|
||||
- **定義檔輸出形狀統一與去署名**:LLM 輸出統一為單層
|
||||
dict/array——tag、code:關鍵字→引述;merge、cap:
|
||||
無名分組(陣列之陣列);code-arb:保留關鍵字→仲裁者自己的引述
|
||||
(剔除即不列,剔除集合由程式以鍵差推得);screen 系:
|
||||
引述陣列,非空即「有」(present 布林刪除,判斷與依據
|
||||
合一)。仲裁輸入一律不含執行別(assigned_by、
|
||||
run1/run2 刪除)——標示何方主張會誘使仲裁者揣測
|
||||
「哪次較可信」,而非就文本裁決。收斂組名採自由命名
|
||||
(不限取自成員詞),取命名貼合度;可追溯性由軌跡歸檔
|
||||
承擔。
|
||||
- **定義檔改三層編號**:
|
||||
`prompts/<軌>-<步>-<次步>-<task>.md`——次步為步內執行
|
||||
順序,明定讀者依循的先後(如 01-02-01-merge →
|
||||
01-02-02-merge-arb → 01-02-03-merge-arb-arb →
|
||||
01-02-04-merge-name → 01-02-05-merge-name-arb)。後綴
|
||||
`-arb` 仲裁、`-arb-arb` 終局票、`-name` 命名、
|
||||
`-name-arb` 命名擇一仲裁。
|
||||
- **screen 標籤詞改為 `women-power`(來歷考據定案)**:
|
||||
考據先導研究的 local agent 存檔:其第一步指令含數十個
|
||||
範例 thematic keywords,其中即有 women-power——為當時
|
||||
@@ -336,63 +381,3 @@
|
||||
考」敘述同步改為考據結果。附帶認清:先導第一步並非零
|
||||
語意提示——此即正式研究「只定格式、不定語意」設計所
|
||||
矯正者。
|
||||
- **定義檔輸出形狀統一與去署名**:LLM 輸出統一為單層
|
||||
dict/array——tag、code:關鍵字→引述;merge、cap:
|
||||
組名→成員;code-arb:保留關鍵字→仲裁者自己的引述
|
||||
(剔除即不列,剔除集合由程式以鍵差推得);screen 系:
|
||||
引述陣列,非空即「有」(present 布林刪除,判斷與依據
|
||||
合一)。仲裁輸入一律不含執行別——標示何方主張會誘使
|
||||
仲裁者揣測「哪次較可信」,而非就文本裁決。收斂組名採
|
||||
自由命名(不限取自成員詞),取命名貼合度;可追溯性由
|
||||
軌跡歸檔承擔。
|
||||
- **定義檔改三層編號**:
|
||||
`prompts/<軌>-<步>-<次步>-<task>.md`——次步為步內執行
|
||||
順序,明定讀者依循的先後(如 01-04-01-code →
|
||||
01-04-02-code-arb);仲裁檔後綴 `-arb`。
|
||||
- **收斂演算法:檢視而棄用的方案**:詞彙表建構(merge、
|
||||
cap)的重複執行與仲裁,歷經四個方案後全數棄用——
|
||||
①整條管線獨立跑兩遍、於終點仲裁兩份最終詞彙表:兩套
|
||||
分類系統互不可比,仲裁淪為第三次建構,一致率無從
|
||||
定義。②組對組匹配(以相似度門檻判定兩組是否「同一
|
||||
組」):無原則性答案,門檻任意。③tag 步驟仲裁:自由
|
||||
詞彙兩次輸出不共享比對單位,無物可裁(tag 改為兩次
|
||||
進池,沿用至今)。④逐對仲裁鏈:交集細分出共識塊、
|
||||
分歧塊對三票多數決、union-find 遞移重組、仲裁後命名
|
||||
2+1——機械上可行且逐項可驗(共 15 份定義檔,全版
|
||||
保存於分支 `tag-algo-13` 備考),但其變異縮減未經證實
|
||||
(逐對多數決降低對層變異,遞移閉包卻放大結構層變異,
|
||||
淨效果不明),複雜度成本則屬確定,且縮減的是儀器變異
|
||||
——對量測無關緊要的量(見次條)。
|
||||
- **詞彙表建構改為單次記錄性程序(演算法簡化)**:
|
||||
merge、cap 各單次執行、執行內自行命名(具名分組
|
||||
輸出),定義檔減為 7 份。2+1 原則改寫:語料層逐首
|
||||
判斷(code、screen)一律 2+1;自由生成(tag)兩次
|
||||
進池;詞彙表建構單次、全程歸檔。理由:(1) 影響量測的
|
||||
是編碼層——詞彙表屬揭露的儀器選擇,凍結後下游同尺量
|
||||
到底,其抽樣變異不污染量測;(2) 對齊領域慣行——
|
||||
codebook 建構本為單次詮釋程序,信度檢驗施於編碼應用
|
||||
層;(3) 複雜仲裁機械無以自證其益(見前條)。驗證改為
|
||||
確定性格式檢查(完整分割、組名唯一、cap ≤ 50),違規
|
||||
依協定修訂定義檔重跑。
|
||||
|
||||
## 2026-08-05
|
||||
|
||||
- **歌詞捕捉加浮水印/誤碼正規化**:全語料普查(程式窮舉
|
||||
非 ASCII)發現歌詞站浮水印與 cp1252 誤碼——同形字(西里爾
|
||||
е 160 處/78 檔、希臘 ό 6 處)、異體空格(U+2005 211 處、
|
||||
U+205F 80 處、U+200A 6 處)、零寬字元(U+200B 3 處)、C1
|
||||
誤碼(U+0091/92/93/94/97 共 58 處;先前手修之 U+0085 同
|
||||
類);受影響 81/883 檔全部來自 pilot 匯入,新抓取檔乾淨。
|
||||
正規化規則四條(C1 依 cp1252 本義還原、同形字還原、異體
|
||||
空格→ASCII 空格、零寬刪除)實作於 fetch-lyrics 的
|
||||
normalize_lyrics(),寫入時套用;既有語料以同一函式一次性
|
||||
套用。倒放行等歌曲本體內容不動——策展界線:清傳輸雜質,
|
||||
不改內容。
|
||||
- **Song 775 拒答機制查明與裁定為無法分析**:tag 首跑僅
|
||||
此一首遭模型拒答。以隔離子代理做消融測試(四變體)
|
||||
證實觸發源是歌詞中的倒放拼寫行(觸發混淆文字防護),
|
||||
與歌詞中的種族蔑稱無關、與零寬字元亦無關(移除
|
||||
U+200B 而保留倒放行仍拒答)。裁定:倒放行是歌曲的
|
||||
忠實內容,保留不動——語料忠實優先於可分析性;該首
|
||||
視為無法分析,缺漏歸因於 Anthropic 平台防護,非本
|
||||
研究語料的問題。
|
||||
|
||||
+114
-40
@@ -6,33 +6,105 @@
|
||||
|
||||
## 自然編碼管線總覽
|
||||
|
||||
四步驟:自由標註(tag,兩次進池)→ 自然收斂(merge,
|
||||
單次)→ 強制收斂(cap,單次)→ 全量編碼(code,2+1),
|
||||
另設「女性力量」單目標篩選(screen,2+1)作黃金標準取樣
|
||||
的補漏網。歌詞只出現在 tag、code、screen 與其仲裁步驟;
|
||||
merge、cap 不接觸歌詞。設計原則見 `research-plan.md`;
|
||||
本檔記載可重現的演算法細節。
|
||||
四步驟:自由標註(tag)→ 自然收斂(merge)→ 強制收斂
|
||||
(cap)→ 全量編碼(code),另設「女性力量」單目標篩選
|
||||
(screen)作黃金標準取樣的補漏網。歌詞只出現在 tag、code、
|
||||
screen 與各仲裁步驟;merge、cap 及其仲裁、命名皆不接觸
|
||||
歌詞。設計原則見 `research-plan.md`;本檔記載可重現的
|
||||
演算法細節。
|
||||
|
||||
## 詞彙表建構(merge、cap)——單次記錄性程序
|
||||
## 收斂步驟(merge、cap)的 2+1 比對與仲裁
|
||||
|
||||
merge 與 cap 各以單次執行完成,執行內自行為各組命名,
|
||||
輸出具名分組(組名 → 成員詞)。不做重複執行與仲裁。
|
||||
兩次獨立執行對同一批輸入詞各產生一個分組。比對不做
|
||||
「組對組」的匹配——兩個組是否為「同一組的變體」無原則性
|
||||
答案——而是把比較化約為「詞對的共組關係」:
|
||||
|
||||
- **定位**:詞彙表是研究的儀器,不是量測。詞彙表凍結
|
||||
入庫後,下游全部量測(編碼 vs 黃金標準)以同一把尺
|
||||
進行,內部一致;建構過程的抽樣變異屬「揭露的儀器
|
||||
選擇」,不污染量測。此與質性研究慣行一致——codebook
|
||||
建構本為單次的詮釋程序,信度檢驗施於編碼應用層。
|
||||
- **取捨紀錄**:曾設計逐對仲裁鏈(共識塊、分歧塊對三票
|
||||
多數、命名 2+1;完整版本保存於分支 `tag-algo-13`)。
|
||||
棄用理由:逐對多數決雖降低單對變異,但遞移閉包會放大
|
||||
結構層變異,淨縮減未經證實;其複雜度成本卻是確定的;
|
||||
且縮減的是儀器變異——對論文主張無關緊要的量。
|
||||
- **確定性驗證**(違規即依協定修訂定義檔重跑):輸出須
|
||||
為輸入詞集的完整分割(缺詞、多詞、重複即失敗);組名
|
||||
唯一且符合格式;cap 組數 ≤ 50。
|
||||
- **收斂軌跡**:原始關鍵字 → merge 組名 → cap 組名,
|
||||
兩份執行輸出本身即完整記錄。
|
||||
1. **共識塊(交集細分)**:兩次執行都放在同組的詞歸為
|
||||
同一塊。即以「(第一次的組, 第二次的組)」二元組為鍵
|
||||
分桶,一桶一塊。此步為純集合運算。
|
||||
2. **分歧塊對枚舉**:每個塊完整落在各次執行的恰一組內,
|
||||
故「兩塊在某次執行中是否同組」定義良好。逐塊對檢查:
|
||||
兩次執行答案相同者為共識(同組或分開,直接定案);
|
||||
不同者列入分歧清單。
|
||||
3. **逐對仲裁(自身 2+1)**:分歧塊對送 LLM 仲裁
|
||||
(`01-02-02-merge-arb.md`、`01-03-02-cap-arb.md`)。
|
||||
仲裁者只看兩塊的內容詞,逐對二元裁決「是否同一
|
||||
主題」;輸入不含執行別(避免「猜哪一次較可信」的
|
||||
偏誤),不含共識部份,不含歌詞。仲裁自身獨立執行
|
||||
兩次,逐對比對:兩次裁決相同即定案;相異的塊對送
|
||||
終局票(`01-02-03-merge-arb-arb.md`、
|
||||
`01-03-03-cap-arb-arb.md`,單次執行,依協定為終局)
|
||||
——每個分歧塊對等同三票多數決,仲裁鏈至此終止。
|
||||
送入仲裁的塊對即模型自身不穩定的邊界判斷,其裁決為
|
||||
單次記錄性決定,不宣稱可再生;可重現性依計畫定義為
|
||||
「程序透明+可稽核」,裁決與其輸入全程歸檔。
|
||||
4. **確定性重組**:以每個「同組」裁決為一條邊,最終
|
||||
分組=圖的連通元件(union-find)。固定邊集的連通
|
||||
元件唯一,與處理順序無關,故重組決定性成立。
|
||||
遞移性後果照單全收:兩個共識「分開」的塊可能經第三
|
||||
塊橋接而併入同組——此為等價關係語意的邏輯結論,
|
||||
比對程式將此類「遞移導出的合併」逐筆記錄於歸檔,
|
||||
供稽核。仲裁的結果空間因此大於「兩次執行擇一」:
|
||||
可能比兩次都粗(多對皆裁同組),也可能比兩次都細
|
||||
(多對皆裁分開)。
|
||||
5. **一致率與保險絲**:比對程式計算塊對層級的一致率並
|
||||
記入歸檔。低於門檻(暫訂 50%,首輪實跑後校準)即
|
||||
不進行仲裁——兩次分組面目全非說明定義檔約束不足,
|
||||
依協定修訂定義檔並重跑該循環。
|
||||
6. **cap 的上限容忍**:仲裁後組數可能略超 50(多對裁
|
||||
「分開」時)。略微超過可接受,如實記錄,不強行
|
||||
壓縮。
|
||||
7. **組名定案(收斂不命名+命名 2+1)**:merge、cap 的
|
||||
執行輸出不含組名(無名分組,陣列之陣列)——命名鏈
|
||||
既然存在,收斂執行自取的名字只會製造兩次執行間
|
||||
「同組不同名」的假分歧。組名承重——merge 的組名是
|
||||
cap 的輸入詞,cap 的組名是 code 對歌詞編碼的類目——
|
||||
故與其他語意判斷同受 2+1。程序:(1) 最終分組定案後,
|
||||
命名步驟(`01-02-04-merge-name.md`、
|
||||
`01-03-04-cap-name.md`)獨立執行兩次,輸入為不透明
|
||||
組 ID 對成員詞,對全部組自由命名,僅受格式約束
|
||||
(小寫連字號、單次執行內不重複);(2) 逐組比對:兩次
|
||||
同名即定案(單次執行內名字唯一,故定名間必無撞名);
|
||||
(3) 異名的組送擇一仲裁(`01-02-05-merge-name-arb.md`、
|
||||
`01-03-05-cap-name-arb.md`)——匿名呈現兩候選,逐組
|
||||
擇一,輸出限於候選並避讓已定名,機械可驗證。cap 的
|
||||
輸入即 merge 定案後的組名清單。
|
||||
|
||||
### 收斂步驟的資料流(比對子命令的輸入輸出契約)
|
||||
|
||||
以 merge 為例(cap 完全同構,檔名換為 01-03 系):
|
||||
|
||||
1. 兩次執行的原始輸出:`runs/01-02-01-merge/run1/output.jsonl`
|
||||
與 `run2/output.jsonl`,各含一個無名分組(陣列之陣列)。
|
||||
2. 比對子命令讀入兩份分組,先驗證兩者為同一輸入詞集的
|
||||
完整分割(缺詞、多詞、重複即失敗),再計算共識塊與
|
||||
分歧塊對,產出仲裁輸入檔——即
|
||||
`01-02-02-merge-arb.md` 所收的 JSON:
|
||||
- `blocks`:塊 ID → 成員詞。**塊 ID 的指派決定性**:
|
||||
全部塊先按「各塊字典序最小的成員詞」排序,依序編為
|
||||
b1、b2、…;塊內成員詞亦按字典序排列。
|
||||
- `pairs`:分歧塊對清單,每對內部按塊 ID 序、清單
|
||||
整體按 (第一元素, 第二元素) 字典序排列。
|
||||
- 僅分歧塊對入列;共識(同組或分開)不送仲裁,由
|
||||
比對子命令直接寫入共識紀錄檔。
|
||||
同時產出:共識紀錄(共識塊、共識同組對、共識分開對)
|
||||
與塊對一致率(含保險絲判定)。
|
||||
3. 仲裁輸入檔以 run-llm 跑兩次
|
||||
(`runs/01-02-02-merge-arb/run1`、`run2`);比對
|
||||
子命令逐對比對兩份裁決,兩票相同即定案,相異的塊對
|
||||
依同一契約組成終局票輸入檔(`blocks` 僅含涉事塊、
|
||||
ID 沿用原編號),跑
|
||||
`runs/01-02-03-merge-arb-arb/run1`。
|
||||
4. 裁決套用子命令彙整三票結果,以 union-find 重組出
|
||||
最終分組,並寫出:最終分組檔(無名,陣列之陣列,
|
||||
組序與組內成員皆字典序)、遞移導出合併的紀錄、
|
||||
收斂軌跡(原始詞 → 最終組)。
|
||||
5. 最終分組轉為不透明組 ID(依組序編 g1、g2、…)進入
|
||||
命名鏈(見第 7 條);命名定案後,組名清單(字典序)
|
||||
即下一步的輸入。
|
||||
6. 以上中間檔全部隨 `runs/` 歸檔;一切排序規則固定,
|
||||
故給定相同的兩份執行輸出與相同的裁決,全流程輸出
|
||||
逐位元組可再生。
|
||||
|
||||
## 編碼步驟(code、screen)的 2+1 比對與仲裁
|
||||
|
||||
@@ -48,9 +120,6 @@ merge 與 cap 各以單次執行完成,執行內自行為各組命名,
|
||||
引述陣列。
|
||||
- 仲裁者的引述可能與原引述不同:仲裁是對歌詞的重新
|
||||
判讀,其引述是該裁決自身的依據,非轉抄。
|
||||
- 送入仲裁的標籤即模型自身不穩定的邊界判斷,其裁決為
|
||||
單次記錄性決定,不宣稱可再生;可重現性依計畫定義為
|
||||
「程序透明+可稽核」,裁決與其輸入全程歸檔。
|
||||
|
||||
## 軌跡對映(診斷用)
|
||||
|
||||
@@ -61,7 +130,7 @@ merge 與 cap 各以單次執行完成,執行內自行為各組命名,
|
||||
## 全管線的交接契約
|
||||
|
||||
每一步的輸出如何變成下一步的輸入,皆為確定性程序,規則
|
||||
明定如下:
|
||||
明定如下(收斂步驟內部的交接見上節):
|
||||
|
||||
- **歌詞輸入檔(tag、code、screen 共用)**:
|
||||
`export-llm-input` 自工作儲存產出,每筆
|
||||
@@ -73,9 +142,9 @@ merge 與 cap 各以單次執行完成,執行內自行為各組命名,
|
||||
進池同時寫出處記錄(關鍵字 →(執行別,歌曲 ID)
|
||||
清單),供軌跡對映回到歌曲;出處記錄不進任何 LLM
|
||||
輸入。
|
||||
- **merge → cap**:merge 輸出的組名以字典序排成 JSON
|
||||
陣列,即 cap 的輸入;原始關鍵字不下傳。
|
||||
- **cap → code 定義檔**:cap 輸出的組名以字典序逐行填入
|
||||
- **merge 定案 → cap**:merge 定案組名以字典序排成
|
||||
JSON 陣列,即 cap 的輸入(見上節第 5 點)。
|
||||
- **cap 定案 → code 定義檔**:定案組名以字典序逐行填入
|
||||
`01-04-01-code.md` 的詞彙表節(逐字),檔案隨 git
|
||||
commit 後方可執行——code 的定義檔因此自我完備,
|
||||
論文附錄可直接引用。
|
||||
@@ -92,6 +161,11 @@ merge 與 cap 各以單次執行完成,執行內自行為各組命名,
|
||||
入仲裁輸入 JSONL(依 ID 升序),`content` 為
|
||||
`{"lyrics": …, "evidence": <肯定方引述>}`。
|
||||
- **screen 定案**:命中集合=兩次皆有 ∪ 仲裁裁定有。
|
||||
- **命名鏈的輸入構成**:`-name` 輸入的組 ID 依最終分組
|
||||
之組序(上節第 5 點)編 g1、g2、…,組內成員字典序;
|
||||
`-name-arb` 輸入中每組的兩個候選名**按字典序排列**
|
||||
——不按執行別,避免順序洩漏何方所取;`taken` 為已
|
||||
定案名的字典序清單。
|
||||
- **女性力量候選集**:於 cap 詞彙表定案後、黃金標準
|
||||
編碼開始前,由研究者指認詞彙表中屬「女性力量」概念
|
||||
域的組(指認及理由記入決策日誌),候選集=code 定案
|
||||
@@ -99,20 +173,20 @@ merge 與 cap 各以單次執行完成,執行內自行為各組命名,
|
||||
- **序列化通則**:所有中間檔為 UTF-8 JSON,鍵序與元素
|
||||
序皆依上列規則明定,無時間戳、無隨機成分;解析一律
|
||||
偵測重複鍵,違規即失敗。JSONL 一律以換行字元(\n)
|
||||
切行——歌詞含 U+0085 等控制字元時,
|
||||
切行——歌詞含 U+2028 等 Unicode 行分隔符,
|
||||
`str.splitlines()` 類的通用切行會截斷 JSON 字串
|
||||
(實測踩中)。給定相同的 LLM 執行輸出,全部交接產物
|
||||
逐位元組可再生。
|
||||
(實測踩中)。給定相同的 LLM 執行輸出,
|
||||
全部交接產物逐位元組可再生。
|
||||
|
||||
## 執行與稽核
|
||||
|
||||
- 每一步驟以 `run-llm <定義檔> <輸入檔> <歸檔目錄>`
|
||||
執行;2+1 步驟的兩次執行=重現命令清單上的兩行命令,
|
||||
各自歸檔(`runs/<定義檔名>/run1`、`run2`),仲裁為
|
||||
獨立步驟、獨立歸檔;單次步驟(merge、cap)一行命令、
|
||||
一份歸檔。
|
||||
- 進池、比對、裁決套用、對映皆為確定性程式(子命令),
|
||||
其輸入輸出檔隨 runs/ 歸檔。
|
||||
執行;獨立執行兩次=重現命令清單上的兩行命令,各自
|
||||
歸檔(`runs/<定義檔名>/run1`、`run2`),仲裁與命名
|
||||
各為獨立步驟、獨立歸檔。
|
||||
- 比對、裁決套用、重組、對映皆為確定性程式(子命令),
|
||||
其輸入輸出檔隨 runs/ 歸檔,JSON 解析一律偵測重複鍵,
|
||||
違規即失敗。
|
||||
- Batch API 的每筆請求自含全部脈絡且互不可見(平台
|
||||
契約),歌與歌之間的獨立性由此成立;兩次執行的獨立
|
||||
性由「兩次呼叫、兩個批次、兩份歸檔」的執行結構自明。
|
||||
|
||||
@@ -25,7 +25,7 @@ pop-fem-audit/
|
||||
│ └── artists.csv # 歌手報表(人讀;進 git)
|
||||
├── prompts/ # LLM 定義檔(逐字作為 system prompt)
|
||||
│ └── <軌>-<步>-<次步>-<task>.md # 01-01-01-tag.md、
|
||||
│ # 01-04-02-code-arb.md…
|
||||
│ # 01-02-02-merge-arb.md…
|
||||
│ # 不帶版本號,版本即 git 歷史
|
||||
├── tools/ # 輔助工具子專案(src-layout)
|
||||
│ ├── pyproject.toml # 發行名 pop-fem-audit-tools;
|
||||
@@ -73,7 +73,7 @@ pop-fem-audit/
|
||||
│ └── methodology.md # 方法細節(全文方法節底稿;
|
||||
│ # 映射分析方法須在看結果前寫定)
|
||||
└── paper/
|
||||
├── conference-abstract.md # pilot 摘要(投稿版)
|
||||
├── abstract.md # 摘要
|
||||
└── full-paper.md # 全文
|
||||
```
|
||||
|
||||
|
||||
+27
-31
@@ -7,20 +7,17 @@
|
||||
|
||||
- **研究敘事**:全文為獨立完整的正式研究(2016–2025 十年語料
|
||||
共 1000 筆榜單紀錄,凍結程序後全量執行),**不與先導研究
|
||||
(paper/conference-abstract.md)做比較**;全文數字一律以正式研究
|
||||
做比較**;全文數字一律以正式研究
|
||||
結果為準。先導研究僅作為 codebook v0 與假說的內部來源,
|
||||
記於決策日誌,不進入論文敘事。
|
||||
- **執行原則**:主會話只做討論;所有分析由 deterministic script
|
||||
執行。LLM 步驟以 Python script 呼叫 Anthropic Messages API
|
||||
(個人 Console 帳號、Batch API 五折),定義檔逐字作為 system
|
||||
prompt。2+1 協定適用於語料層的逐首判斷(編碼、篩選):
|
||||
「同一定義檔獨立執行兩次+一次仲裁」,仲裁只裁程式算出
|
||||
的分歧。自由生成步驟(自由標註)兩次執行全數進池、不
|
||||
仲裁——自由詞彙兩次輸出不共享比對單位,無物可裁。詞彙
|
||||
表建構(收斂)為單次記錄性程序,不重複執行——詞彙表是
|
||||
揭露的儀器選擇,非量測;信度檢驗施於編碼層(詳見
|
||||
`methodology.md`)。仲裁或驗證結果不符預期則修訂定義檔
|
||||
重跑該循環。
|
||||
prompt。2+1 協定依輸出可比性適用:輸出可逐項機械比對的步驟
|
||||
(收斂、編碼)「同一定義檔獨立執行兩次+一次仲裁」,仲裁
|
||||
只裁程式算出的分歧清單;自由生成步驟(首步自由標註)兩次
|
||||
執行全數進池、不仲裁——自由詞彙兩次輸出不共享比對單位,
|
||||
無物可裁。仲裁結果不符預期則修訂定義檔重跑該循環。
|
||||
- **提示詞只定格式、不定語意**:研究對象是通用 LLM 以其
|
||||
網路語料知識背景所做的自然編碼,編碼結果本身是批判對象。
|
||||
LLM 定義檔只規定任務形狀(輸入、數量範圍、輸出格式),
|
||||
@@ -72,8 +69,8 @@
|
||||
候選歌曲的歌手另做深度背景(族裔以公開自我認同為準、音樂
|
||||
場景),script 輔助、人工核定。
|
||||
- **Pilot 歌詞沿用(私人匯入,不進發布管線)**:先導研究
|
||||
捕捉檔(lyrics.json,684 首,2018–2025)以 `excludes/` 的
|
||||
私人腳本匯入歌詞快取——只取識別欄位與歌詞本文,以
|
||||
捕捉檔(lyrics.json,684 首,2018–2025)以私人腳本
|
||||
匯入歌詞快取——只取識別欄位與歌詞本文,以
|
||||
(year, rank) 精確匹配 song_id。匯入工具不屬於專案交付物
|
||||
(讀者拿不到其輸入),讀者的重現路徑純粹是 `fetch-lyrics`;
|
||||
沿用之**事實**記於 `data/captures/lyrics-provenance.csv`(進 git):
|
||||
@@ -98,12 +95,13 @@
|
||||
themes 過早抽象)之繼承,於執行前鎖定,防止事後擇優。
|
||||
2. **自然收斂(merge)**:輸入為池中純去重關鍵字清單——
|
||||
無歌詞、無頻次、無歌曲出處——模型按自身理解合併近似
|
||||
概念,執行內自行為各組命名。單次執行,不仲裁;確定性
|
||||
驗證(完整分割、組名唯一)違規即修訂重跑(詳見
|
||||
`methodology.md`)。
|
||||
概念。2+1:兩次收斂處理同一批輸入詞,程式算出共識塊
|
||||
與分歧塊對,逐對仲裁(仲裁自身亦 2+1,第三票終局)後
|
||||
以連通元件決定性重組;收斂執行不命名,組名由命名步驟
|
||||
2+1 定案(演算法詳見 `methodology.md`)。
|
||||
3. **強制收斂(cap)**:同第 2 步形態,輸入為 merge
|
||||
輸出的組名清單(原始關鍵字不下傳),限制併至 50 個
|
||||
以內。單次執行,不仲裁。頻次不入收斂:頻率的分析角色由第 4 步編碼承擔;
|
||||
定案後的組名清單(原始關鍵字不下傳),限制併至 50 個
|
||||
以內;仲裁後略超 50 可接受,如實記錄。頻次不入收斂:頻率的分析角色由第 4 步編碼承擔;
|
||||
池中頻次含跨執行噪音;頻次會誘使模型以頻率剪枝(喪失
|
||||
稀有主題)並把高頻大主題切細。代價(特異主題佔名額)
|
||||
已知並接受,換取主題多樣性與純語意歸併的可辯護性。
|
||||
@@ -125,16 +123,18 @@
|
||||
提示中唯一的語意種子,屬研究者的儀器選擇,據實揭露。
|
||||
|
||||
定義檔命名 `prompts/<軌>-<步>-<次步>-<task>.md`——軌
|
||||
01=由下而上自然編碼、02=預先決定的 women-power 篩選;
|
||||
步為軌內步驟序,次步為步內執行順序,讀者依編號先後依循:
|
||||
01-01-01-tag.md、01-02-01-merge.md、01-03-01-cap.md、
|
||||
01-04-01-code.md、01-04-02-code-arb.md、
|
||||
02-01-01-screen.md、02-01-02-screen-arb.md;仲裁定義檔
|
||||
同 prefix 加 `-arb`。檔名不帶版本號——版本即 git 歷史,
|
||||
失敗的版本不保留,需要回看的舊版都在 git history;每次
|
||||
執行的定義檔快照隨 `runs/` 自我完備。收斂執行的輸出本身
|
||||
即分組記錄(具名分組 JSON),與全部中間交接檔同隨
|
||||
`runs/` 歸檔(交接契約見 `methodology.md`)。
|
||||
01=由下而上自然編碼、02=預先決定的 women-power
|
||||
篩選;步為軌內步驟序,次步為
|
||||
步內執行順序,讀者依編號先後依循(如 01-02-01-merge.md →
|
||||
01-02-02-merge-arb.md → 01-02-03-merge-arb-arb.md →
|
||||
01-02-04-merge-name.md → 01-02-05-merge-name-arb.md)。
|
||||
後綴 `-arb` 為仲裁、`-arb-arb` 為仲裁之終局票、`-name`
|
||||
為命名、`-name-arb` 為命名擇一仲裁。檔名不帶
|
||||
版本號——版本即 git 歷史,失敗的版本不保留,需要回看的
|
||||
舊版都在 git history;每次執行的定義檔快照隨 `runs/`
|
||||
自我完備。收斂執行的輸出本身即分組記錄(無名分組
|
||||
JSON),與全部中間交接檔同隨 `runs/` 歸檔(交接契約見
|
||||
`methodology.md`)。
|
||||
|
||||
## 階段與時程(全文截稿 2026-08-15)
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
|---|---|---|---|
|
||||
| 0 | 基礎建設:git init、目錄結構、.gitignore、決策日誌、runner script(含 Batch API)、codebook v0 骨架 | script + 討論 | 7/30–7/31 |
|
||||
| 1 | 資料準備:`run_llm` 改走統一設定 → `build-db`(解析榜單成 songs/chart_entries/artists/song_artists)→ `import-lyrics`(pilot 2018–2025)→ `fetch-lyrics`(2016–17 與缺漏,Lyrics.ovh / LRCLIB)→ `fetch-artists`(Wikidata 快照)→ `export-llm-input` | 子命令 | 7/31–8/3 |
|
||||
| 2 | 自然編碼管線:tag ×2 進池 → merge ×1 → cap ×1 → 詞彙表定稿 → code 2+1(全 883 首,附引述);另跑 screen 補漏網 | API + script | 8/4–8/7 |
|
||||
| 2 | 自然編碼管線:tag ×2 進池 → merge 2+1 → cap 2+1 → 詞彙表定稿 → code 2+1(全 883 首,附引述);另跑 screen 補漏網 | API + script | 8/4–8/7 |
|
||||
| 3 | 黃金標準:依 codebook 人工逐首判定 genuine/peripheral/fake,附引用歌詞證據表(LLM 只做摘錄,不給判定建議);先以 10–15 首校準樣本試編並修訂 codebook 後凍結;同批校準樣本實測 Sonnet 4.6 vs Opus 5 一致率 | 人工 + script 輔助 | 8/5–8/9 |
|
||||
| 4 | 受控比較(盲點實驗):條件 A(詞彙層提示)vs 條件 B(框架感知提示),各 2+1,對照黃金標準計算假陽/假陰率 | API | 8/8–8/11 |
|
||||
| 4' | 映射分析:自然編碼結果(第 4 步)與黃金標準交叉表;軌跡對映 vs 直接編碼的扭曲診斷(分析方法先寫入 methodology.md 再看結果) | script | 與 4 並行 |
|
||||
@@ -165,14 +165,10 @@
|
||||
與「貶低其他女性/以男性指標定義權力」。
|
||||
- 流程:v0 → 校準樣本試編 → 修訂 → 凍結 v1 → 全量編碼;
|
||||
中途修訂須記決策日誌並回檢已編歌曲。
|
||||
- Positionality statement 寫入論文(53 歲、長年婦運者、資深
|
||||
工程師、離開學術圈多年、因整理榜單而起)。
|
||||
|
||||
## 其他已定案事項
|
||||
|
||||
- 歌詞受版權保護:完整歌詞不進 git(`data/captures/lyrics/` gitignored),
|
||||
論文與 repo 只留分類所引摘錄。
|
||||
- 工程支援(寫 script、style check、審稿)用公司訂閱的
|
||||
Claude Code;進論文的分析 token 由個人 API 帳號支付。
|
||||
- Fable 5 不用於 pipeline:成本高、thinking 無法關閉且不可稽核、
|
||||
無 temperature 控制,且會混淆「盲點是提示問題」的核心主張。
|
||||
|
||||
@@ -14,10 +14,6 @@ Rules:
|
||||
- For each keyword, quote 1 to 3 verbatim lines of the lyrics
|
||||
that ground it; do not give a keyword you cannot ground.
|
||||
|
||||
Do not wrap the output in a Markdown code fence.
|
||||
The output must be valid JSON; escape any double quote
|
||||
inside a string as \".
|
||||
|
||||
Output a single JSON object mapping each keyword to its list
|
||||
of quotes, and nothing else:
|
||||
|
||||
|
||||
@@ -13,18 +13,11 @@ Rules:
|
||||
- Do not invent keywords that are not in the input.
|
||||
- Merge only what you consider the same theme; a group may be
|
||||
a single keyword.
|
||||
- Name each group with a short lowercase phrase, words joined
|
||||
by hyphens, that best names the theme it gathers. Group
|
||||
names must be unique.
|
||||
|
||||
Do not wrap the output in a Markdown code fence.
|
||||
The output must be valid JSON; escape any double quote
|
||||
inside a string as \".
|
||||
Output a single JSON array of groups — each group an array of
|
||||
its member keywords — and nothing else:
|
||||
|
||||
Output a single JSON object mapping each group name to the
|
||||
list of its member keywords, and nothing else:
|
||||
|
||||
{
|
||||
"first-group-name": ["member-keyword", "another-member"],
|
||||
"second-group-name": ["member-keyword"]
|
||||
}
|
||||
[
|
||||
["keyword", "another-keyword"],
|
||||
["keyword"]
|
||||
]
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
You are arbitrating between two independent consolidations of
|
||||
the same vocabulary of thematic keywords from a song corpus.
|
||||
|
||||
Where the two consolidations agree, the result has been
|
||||
settled by script and is not shown. You rule on the
|
||||
disagreements: pairs of keyword blocks that one consolidation
|
||||
merged into one theme and the other kept apart.
|
||||
|
||||
Input: a JSON object. "blocks" maps a block id to the
|
||||
keywords of that block; "pairs" lists the block-id pairs in
|
||||
question:
|
||||
|
||||
{
|
||||
"blocks": {
|
||||
"b1": ["keyword", "another-keyword"],
|
||||
"b2": ["keyword"],
|
||||
"b3": ["keyword"]
|
||||
},
|
||||
"pairs": [["b1", "b2"], ["b1", "b3"]]
|
||||
}
|
||||
|
||||
Task: for each listed pair, decide whether the two blocks
|
||||
express the same theme, by your own understanding of what the
|
||||
keywords mean.
|
||||
|
||||
Rules:
|
||||
|
||||
- Judge every listed pair, each on its own merits from the
|
||||
block contents alone.
|
||||
- Judge only the listed pairs.
|
||||
|
||||
Output a single JSON array holding the pairs whose two blocks
|
||||
express the same theme, written exactly as given in "pairs";
|
||||
an empty array when none do:
|
||||
|
||||
[["b1", "b2"]]
|
||||
@@ -0,0 +1,34 @@
|
||||
You are casting the deciding judgment on disagreements about
|
||||
a consolidated vocabulary of thematic keywords from a song
|
||||
corpus: for each pair of keyword blocks in question, earlier
|
||||
independent judgments disagreed on whether the two blocks
|
||||
express the same theme.
|
||||
|
||||
Input: a JSON object. "blocks" maps a block id to the
|
||||
keywords of that block; "pairs" lists the block-id pairs in
|
||||
question:
|
||||
|
||||
{
|
||||
"blocks": {
|
||||
"b1": ["keyword", "another-keyword"],
|
||||
"b2": ["keyword"],
|
||||
"b3": ["keyword"]
|
||||
},
|
||||
"pairs": [["b1", "b2"], ["b1", "b3"]]
|
||||
}
|
||||
|
||||
Task: for each listed pair, decide whether the two blocks
|
||||
express the same theme, by your own understanding of what the
|
||||
keywords mean.
|
||||
|
||||
Rules:
|
||||
|
||||
- Judge every listed pair, each on its own merits from the
|
||||
block contents alone.
|
||||
- Judge only the listed pairs.
|
||||
|
||||
Output a single JSON array holding the pairs whose two blocks
|
||||
express the same theme, written exactly as given in "pairs";
|
||||
an empty array when none do:
|
||||
|
||||
[["b1", "b2"]]
|
||||
@@ -0,0 +1,28 @@
|
||||
You are naming the theme groups of a consolidated vocabulary
|
||||
of thematic keywords from a song corpus.
|
||||
|
||||
Input: a JSON object mapping an opaque group id to the member
|
||||
keywords of that group:
|
||||
|
||||
{
|
||||
"g1": ["keyword", "another-keyword"],
|
||||
"g2": ["keyword"]
|
||||
}
|
||||
|
||||
Task: give each group a name that best names the theme its
|
||||
members gather, by your own understanding of what the
|
||||
keywords mean.
|
||||
|
||||
Rules:
|
||||
|
||||
- Each name is a short lowercase phrase with the words joined
|
||||
by hyphens.
|
||||
- Names must be unique across the groups.
|
||||
|
||||
Output a single JSON object mapping each group id to its
|
||||
name, and nothing else:
|
||||
|
||||
{
|
||||
"g1": "group-name",
|
||||
"g2": "another-group-name"
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
You are arbitrating between two independent namings of the
|
||||
theme groups of a consolidated vocabulary of thematic
|
||||
keywords from a song corpus.
|
||||
|
||||
For each group in question, the two namings proposed
|
||||
different names. You choose between them.
|
||||
|
||||
Input: a JSON object. "groups" maps an opaque group id to
|
||||
its member keywords and its two candidate names; "taken"
|
||||
lists names that are already in use:
|
||||
|
||||
{
|
||||
"groups": {
|
||||
"g1": {
|
||||
"members": ["keyword", "another-keyword"],
|
||||
"candidates": ["one-name", "other-name"]
|
||||
}
|
||||
},
|
||||
"taken": ["existing-name"]
|
||||
}
|
||||
|
||||
Task: for each group, choose the candidate that better names
|
||||
the theme its members gather, by your own understanding of
|
||||
what the keywords mean.
|
||||
|
||||
Rules:
|
||||
|
||||
- Choose only from that group's two candidates.
|
||||
- Choices must be unique across the groups and must not
|
||||
reuse any name in "taken".
|
||||
|
||||
Output a single JSON object mapping each group id to the
|
||||
chosen name, and nothing else:
|
||||
|
||||
{
|
||||
"g1": "one-name"
|
||||
}
|
||||
+6
-13
@@ -16,18 +16,11 @@ Rules:
|
||||
- Merge the keywords that are closest in meaning first; keep
|
||||
clearly distinct themes apart as far as the limit allows.
|
||||
A group may be a single keyword.
|
||||
- Name each group with a short lowercase phrase, words joined
|
||||
by hyphens, that best names the theme it gathers. Group
|
||||
names must be unique.
|
||||
|
||||
Do not wrap the output in a Markdown code fence.
|
||||
The output must be valid JSON; escape any double quote
|
||||
inside a string as \".
|
||||
Output a single JSON array of groups — each group an array of
|
||||
its member keywords — and nothing else:
|
||||
|
||||
Output a single JSON object mapping each group name to the
|
||||
list of its member keywords, and nothing else:
|
||||
|
||||
{
|
||||
"first-group-name": ["member-keyword", "another-member"],
|
||||
"second-group-name": ["member-keyword"]
|
||||
}
|
||||
[
|
||||
["keyword", "another-keyword"],
|
||||
["keyword"]
|
||||
]
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
You are arbitrating between two independent consolidations
|
||||
that each reduced the same vocabulary of thematic keywords
|
||||
from a song corpus to a fixed maximum number of themes.
|
||||
|
||||
Where the two consolidations agree, the result has been
|
||||
settled by script and is not shown. You rule on the
|
||||
disagreements: pairs of keyword blocks that one consolidation
|
||||
merged into one theme and the other kept apart.
|
||||
|
||||
Input: a JSON object. "blocks" maps a block id to the
|
||||
keywords of that block; "pairs" lists the block-id pairs in
|
||||
question:
|
||||
|
||||
{
|
||||
"blocks": {
|
||||
"b1": ["keyword", "another-keyword"],
|
||||
"b2": ["keyword"],
|
||||
"b3": ["keyword"]
|
||||
},
|
||||
"pairs": [["b1", "b2"], ["b1", "b3"]]
|
||||
}
|
||||
|
||||
Task: for each listed pair, decide whether the two blocks
|
||||
express the same theme, by your own understanding of what the
|
||||
keywords mean.
|
||||
|
||||
Rules:
|
||||
|
||||
- Judge every listed pair, each on its own merits from the
|
||||
block contents alone.
|
||||
- Judge only the listed pairs.
|
||||
|
||||
Output a single JSON array holding the pairs whose two blocks
|
||||
express the same theme, written exactly as given in "pairs";
|
||||
an empty array when none do:
|
||||
|
||||
[["b1", "b2"]]
|
||||
@@ -0,0 +1,35 @@
|
||||
You are casting the deciding judgment on disagreements about
|
||||
a vocabulary of thematic keywords from a song corpus that was
|
||||
consolidated to a fixed maximum number of themes: for each
|
||||
pair of keyword blocks in question, earlier independent
|
||||
judgments disagreed on whether the two blocks express the
|
||||
same theme.
|
||||
|
||||
Input: a JSON object. "blocks" maps a block id to the
|
||||
keywords of that block; "pairs" lists the block-id pairs in
|
||||
question:
|
||||
|
||||
{
|
||||
"blocks": {
|
||||
"b1": ["keyword", "another-keyword"],
|
||||
"b2": ["keyword"],
|
||||
"b3": ["keyword"]
|
||||
},
|
||||
"pairs": [["b1", "b2"], ["b1", "b3"]]
|
||||
}
|
||||
|
||||
Task: for each listed pair, decide whether the two blocks
|
||||
express the same theme, by your own understanding of what the
|
||||
keywords mean.
|
||||
|
||||
Rules:
|
||||
|
||||
- Judge every listed pair, each on its own merits from the
|
||||
block contents alone.
|
||||
- Judge only the listed pairs.
|
||||
|
||||
Output a single JSON array holding the pairs whose two blocks
|
||||
express the same theme, written exactly as given in "pairs";
|
||||
an empty array when none do:
|
||||
|
||||
[["b1", "b2"]]
|
||||
@@ -0,0 +1,29 @@
|
||||
You are naming the theme groups of a vocabulary of thematic
|
||||
keywords from a song corpus that was consolidated to a fixed
|
||||
maximum number of themes.
|
||||
|
||||
Input: a JSON object mapping an opaque group id to the member
|
||||
keywords of that group:
|
||||
|
||||
{
|
||||
"g1": ["keyword", "another-keyword"],
|
||||
"g2": ["keyword"]
|
||||
}
|
||||
|
||||
Task: give each group a name that best names the theme its
|
||||
members gather, by your own understanding of what the
|
||||
keywords mean.
|
||||
|
||||
Rules:
|
||||
|
||||
- Each name is a short lowercase phrase with the words joined
|
||||
by hyphens.
|
||||
- Names must be unique across the groups.
|
||||
|
||||
Output a single JSON object mapping each group id to its
|
||||
name, and nothing else:
|
||||
|
||||
{
|
||||
"g1": "group-name",
|
||||
"g2": "another-group-name"
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
You are arbitrating between two independent namings of the
|
||||
theme groups of a vocabulary of thematic keywords from a song
|
||||
corpus that was consolidated to a fixed maximum number of
|
||||
themes.
|
||||
|
||||
For each group in question, the two namings proposed
|
||||
different names. You choose between them.
|
||||
|
||||
Input: a JSON object. "groups" maps an opaque group id to
|
||||
its member keywords and its two candidate names; "taken"
|
||||
lists names that are already in use:
|
||||
|
||||
{
|
||||
"groups": {
|
||||
"g1": {
|
||||
"members": ["keyword", "another-keyword"],
|
||||
"candidates": ["one-name", "other-name"]
|
||||
}
|
||||
},
|
||||
"taken": ["existing-name"]
|
||||
}
|
||||
|
||||
Task: for each group, choose the candidate that better names
|
||||
the theme its members gather, by your own understanding of
|
||||
what the keywords mean.
|
||||
|
||||
Rules:
|
||||
|
||||
- Choose only from that group's two candidates.
|
||||
- Choices must be unique across the groups and must not
|
||||
reuse any name in "taken".
|
||||
|
||||
Output a single JSON object mapping each group id to the
|
||||
chosen name, and nothing else:
|
||||
|
||||
{
|
||||
"g1": "one-name"
|
||||
}
|
||||
@@ -21,10 +21,6 @@ Rules:
|
||||
the lyrics that ground it; do not assign a keyword you
|
||||
cannot ground.
|
||||
|
||||
Do not wrap the output in a Markdown code fence.
|
||||
The output must be valid JSON; escape any double quote
|
||||
inside a string as \".
|
||||
|
||||
Output a single JSON object mapping each assigned keyword to
|
||||
its list of quotes (an empty object when no keyword applies),
|
||||
and nothing else:
|
||||
|
||||
@@ -30,10 +30,6 @@ Rules:
|
||||
lines of the lyrics; quote them.
|
||||
- Do not add any keyword that is not listed.
|
||||
|
||||
Do not wrap the output in a Markdown code fence.
|
||||
The output must be valid JSON; escape any double quote
|
||||
inside a string as \".
|
||||
|
||||
Output a single JSON object mapping each kept keyword to your
|
||||
list of quotes, and nothing else. A dropped keyword is simply
|
||||
left out; output an empty object when nothing is kept:
|
||||
|
||||
@@ -12,10 +12,6 @@ Rules:
|
||||
lines of the lyrics that ground the judgment; if you cannot
|
||||
ground it in a quote, the answer is no.
|
||||
|
||||
Do not wrap the output in a Markdown code fence.
|
||||
The output must be valid JSON; escape any double quote
|
||||
inside a string as \".
|
||||
|
||||
Output a single JSON array and nothing else: the quoted
|
||||
lines when the song expresses the theme, or an empty array
|
||||
when it does not:
|
||||
|
||||
@@ -21,10 +21,6 @@ Rules:
|
||||
lines of the lyrics that ground the judgment; if you cannot
|
||||
ground it in a quote, the answer is no.
|
||||
|
||||
Do not wrap the output in a Markdown code fence.
|
||||
The output must be valid JSON; escape any double quote
|
||||
inside a string as \".
|
||||
|
||||
Output a single JSON array and nothing else: the quoted
|
||||
lines when the song expresses the theme, or an empty array
|
||||
when it does not:
|
||||
|
||||
@@ -57,49 +57,6 @@ SLEEP_SECONDS: float = 1.0
|
||||
"""The delay between consecutive HTTP requests, in seconds."""
|
||||
|
||||
|
||||
def _build_normalization() -> dict[int, str | None]:
|
||||
"""Build the lyrics normalization translation table.
|
||||
|
||||
:return: The codepoint-to-replacement mapping, a replacement
|
||||
of None meaning removal.
|
||||
"""
|
||||
table: dict[int, str | None] = {}
|
||||
codepoint: int
|
||||
for codepoint in range(0x80, 0xa0):
|
||||
try:
|
||||
table[codepoint] = bytes([codepoint]).decode("cp1252")
|
||||
except UnicodeDecodeError:
|
||||
table[codepoint] = None
|
||||
table[0x0435] = "e"
|
||||
table[0x03cc] = "ó"
|
||||
for codepoint in (0x2005, 0x205f, 0x200a):
|
||||
table[codepoint] = " "
|
||||
for codepoint in (0x200b, 0x200c, 0x200d, 0xfeff):
|
||||
table[codepoint] = None
|
||||
return table
|
||||
|
||||
|
||||
NORMALIZATION: dict[int, str | None] = _build_normalization()
|
||||
"""The codepoint-to-replacement mapping applied to fetched
|
||||
lyrics: cp1252-mojibake restoration for U+0080-U+009F (with the
|
||||
five byte values undefined in cp1252 removed), homoglyph
|
||||
restoration for the Cyrillic "e" and the Greek "o" with tonos,
|
||||
ASCII-space restoration for exotic space variants, and removal
|
||||
of zero-width characters. A replacement of None removes the
|
||||
codepoint."""
|
||||
|
||||
|
||||
def normalize_lyrics(text: str) -> str:
|
||||
"""Restore or remove watermark and mojibake characters.
|
||||
|
||||
:param text: The lyrics text as fetched from an API.
|
||||
:return: The text with the codepoints in
|
||||
:data:`NORMALIZATION` replaced or removed; every other
|
||||
character is unchanged.
|
||||
"""
|
||||
return text.translate(NORMALIZATION)
|
||||
|
||||
|
||||
def parse_args(argv: list[str] | None) -> argparse.Namespace:
|
||||
"""Parse the command-line arguments.
|
||||
|
||||
@@ -230,9 +187,6 @@ def save_lyrics(lyrics_dir: Path, song_id: int,
|
||||
|
||||
The cache directory is created when missing.
|
||||
|
||||
The lyrics text is normalized with :func:`normalize_lyrics`
|
||||
before being written.
|
||||
|
||||
:param lyrics_dir: The lyrics cache directory.
|
||||
:param song_id: The song ID.
|
||||
:param lyrics: The lyrics text.
|
||||
@@ -241,7 +195,7 @@ def save_lyrics(lyrics_dir: Path, song_id: int,
|
||||
"""
|
||||
lyrics_dir.mkdir(parents=True, exist_ok=True)
|
||||
(lyrics_dir / f"{song_id}.txt").write_text(
|
||||
normalize_lyrics(lyrics), encoding="utf-8")
|
||||
lyrics, encoding="utf-8")
|
||||
|
||||
|
||||
def append_provenance(path: Path, song_id: int,
|
||||
|
||||
@@ -317,79 +317,6 @@ class TestFetchLyrics(unittest.TestCase):
|
||||
self.assertEqual([x[0] for x in rows[1:]],
|
||||
["1", "2", "2"])
|
||||
|
||||
def test_normalize_cp1252_mojibake(self) -> None:
|
||||
"""Test that cp1252 mojibake codepoints are restored."""
|
||||
self.assertEqual(
|
||||
fetch_lyrics.normalize_lyrics("wait
"),
|
||||
"wait…")
|
||||
self.assertEqual(
|
||||
fetch_lyrics.normalize_lyrics(
|
||||
"quote"),
|
||||
"‘quote’")
|
||||
self.assertEqual(
|
||||
fetch_lyrics.normalize_lyrics(
|
||||
"quote"),
|
||||
"“quote”")
|
||||
self.assertEqual(
|
||||
fetch_lyrics.normalize_lyrics("dashline"),
|
||||
"dash—line")
|
||||
|
||||
def test_normalize_undefined_cp1252_removed(self) -> None:
|
||||
"""Test that undefined cp1252 byte values are removed."""
|
||||
text: str = ("abcdef")
|
||||
self.assertEqual(
|
||||
fetch_lyrics.normalize_lyrics(text), "abcdef")
|
||||
|
||||
def test_normalize_homoglyphs(self) -> None:
|
||||
"""Test that watermark homoglyphs are restored."""
|
||||
self.assertEqual(
|
||||
fetch_lyrics.normalize_lyrics("likе that"),
|
||||
"like that")
|
||||
self.assertEqual(
|
||||
fetch_lyrics.normalize_lyrics("lό que soy"),
|
||||
"ló que soy")
|
||||
|
||||
def test_normalize_space_variants(self) -> None:
|
||||
"""Test that exotic space variants become ASCII space."""
|
||||
self.assertEqual(
|
||||
fetch_lyrics.normalize_lyrics(
|
||||
"a b c d"),
|
||||
"a b c d")
|
||||
|
||||
def test_normalize_zero_width_removed(self) -> None:
|
||||
"""Test that zero-width characters are removed."""
|
||||
text: str = (
|
||||
"abcde")
|
||||
self.assertEqual(
|
||||
fetch_lyrics.normalize_lyrics(text), "abcde")
|
||||
|
||||
def test_normalize_ascii_unchanged(self) -> None:
|
||||
"""Test that plain ASCII text passes through unchanged."""
|
||||
text: str = "Hello, it's me\n"
|
||||
self.assertEqual(
|
||||
fetch_lyrics.normalize_lyrics(text), text)
|
||||
|
||||
def test_normalize_legitimate_non_ascii_unchanged(self) -> None:
|
||||
"""Test that legitimate non-ASCII content is unchanged."""
|
||||
text: str = "¿cómo estás? 안녕하세요\n"
|
||||
self.assertEqual(
|
||||
fetch_lyrics.normalize_lyrics(text), text)
|
||||
|
||||
def test_fetched_lyrics_saved_normalized(self) -> None:
|
||||
"""Test that a fetched lyric is normalized before saving."""
|
||||
self.__seed([("Hello", "Adele")])
|
||||
with mock.patch(
|
||||
"urllib.request.urlopen",
|
||||
side_effect=[self.__response(
|
||||
{"lyrics": "wait
likе"
|
||||
" that now\n"})]):
|
||||
status: int = self.__run_fetch()[0]
|
||||
self.assertEqual(status, 0)
|
||||
self.assertEqual(
|
||||
(self.__lyrics / "1.txt")
|
||||
.read_text(encoding="utf-8"),
|
||||
"wait… like that now\n")
|
||||
|
||||
def test_no_store_fails(self) -> None:
|
||||
"""Test that a missing working store fails the run."""
|
||||
urlopen: mock.Mock
|
||||
|
||||
Reference in New Issue
Block a user