9 Commits
Author SHA1 Message Date
imacat 502d8b6b5a Enforce strict validation of configuration settings 2026-08-21 08:12:17 +08:00
imacat 47bef84e7b Rename SQLALCHEMY_DATABASE_URL to SQLALCHEMY_DATABASE_URI to follow SQLAlchemy conventions. 2026-08-21 08:10:21 +08:00
imacatandClaude Fable 5 9b6450fcbb Capture the stderr summary in the request-preview tests
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 23:17:26 +08:00
imacat 309667e82b Add type hints and refactor SQLite foreign key enforcement setup 2026-08-20 23:17:22 +08:00
imacat 195605826f Advance tools to version 1.0.0. 2026-08-19 20:11:29 +08:00
imacat e569b3481c Add change log. 2026-08-19 20:10:38 +08:00
imacat f0547208ed Update README.md. 2026-08-19 20:05:15 +08:00
imacatandClaude Opus 5 eef434acf7 Gather the runs and the results under data/
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 20:01:59 +08:00
imacatandClaude Opus 5 7a625d50fc Trim the docs to their own jobs
The methodology keeps only the method that reaches the paper's
conclusions; the research plan becomes the proposal minus its
externalized method chapter; the standing rules move to a
Chinese conventions.md; the project-structure inventory, a
hand-kept mirror of the tree itself, is deleted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 20:01:54 +08:00
95 changed files with 273 additions and 587 deletions
+10 -2
View File
@@ -1,6 +1,6 @@
# 流行音樂中「女性力量」語彙的挪用與污染——以 Billboard Year-End Hot 1002016-2025)為例的內容分析
這是研究論文《流行音樂中「女性力量」語彙的挪用與污染——以 Billboard Year-End Hot 1002016-2025)為例的內容分析》的專案資料,包括論文本身、文件、紀錄、資料、工具程式、AI提示詞,等等。
這是研究論文《流行音樂中「女性力量」語彙的挪用與污染——以 Billboard Year-End Hot 1002016-2025)為例的內容分析》的專案資料,包括論文本身、文件、紀錄、資料、工具程式、LLM提示詞,等等。
## 論文和摘要
@@ -10,9 +10,17 @@
研究方法、記錄等文件,請參閱 docs/ 資料夾。
## LLM提示
LLM提示請參閱 prompts/ 資料夾。
## 輔助工具程式
輔助工具程式請參閱 tools/ 資料夾。
輔助工具程式請參閱 tools/ 資料夾。
## 研究資料
研究資料請參閱 data/ 資料夾。
## 授權
Can't render this file because it is too large.
+61 -70
View File
@@ -1,78 +1,69 @@
# Project Conventions
# 常設工作規範
The standing working rules of this project. Formerly the
project `CLAUDE.md`; moved here so that Claude Code subagents
do not inherit it into their context (blind-reading agents
must not see it). A main session working on this project
reads this file before touching the pipeline, the data, or
the documents.
本專案的常設工作規範。原專案 `CLAUDE.md`;移置於此,
使 Claude Code subagent 不將其繼承入 context(盲判型
agent 不得見之)。凡於本專案工作的主會話,動手管線、
資料或文件之前,先讀本檔。
## Analysis pipeline
## 分析管線
The pipeline has run to completion; these conventions govern
any rerun or extension.
管線已跑畢;本規範適用於任何重跑或擴充。
- LLM analysis runs via Python scripts calling the Anthropic
Messages API, Batch API where possible. Steps 1 and 3 run
on `claude-sonnet-4-6` with `temperature=0` and thinking
disabled; steps 4 and 5 run on `claude-fable-5`, which
accepts neither parameter -- step 4 absorbs its sampling
variance by the majority vote, step 5 by consolidating the
three readings.
- Prompt definition files live in
`prompts/<step><substep>-<task>.md` (e.g. 1-tag.md,
5a-read.md; substeps are lettered, matching the step
numbering of the paper; no version suffix -- versions live
in git history) and are passed verbatim as the system
prompt. The number names a step of the research
procedure, not the file: the deterministic vocabulary step
(step 2) has no definition file yet holds its own number.
- Itemwise LLM judgments (per-song coding in step 3,
per-keyword group selection in step 4) run the same
definition file three times, independently, over the same
input; a deterministic tally then assigns an item (a
(song, keyword) or (group, keyword) pair) when at least
two of the three runs assign it ("3 runs + majority
vote"). Free-generation steps run
twice and both outputs are pooled. The step-5 qualitative
readings are neither: three independent readings per song,
consolidated per song and synthesized across songs by
their own definition files -- a qualitative protocol, not
a vote (see docs/methodology.md). The vocabulary is
built by a deterministic subcommand (embedding +
clustering), not by an LLM. If a validation outcome 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/<step>/run<N>/`):
prompt snapshot, raw output, and `meta.json` (model ID,
parameters, timestamps, batch ID). The runs of a step are
that many separate invocations of `run-llm`. Replacing
an existing run archive requires an explicit flag;
superseded runs live in git history. Deterministic steps
archive under `runs/<step>/` with no `run<N>` level.
- Token usage and cost of every `run-llm` execution are
recorded in `docs/run-costs.md` in the same commit as the
run archive.
- Scripts read the API key from the `ANTHROPIC_API_KEY`
environment variable (`.env`, gitignored).
- LLM 分析以 Python 腳本呼叫 Anthropic Messages API
執行,能用 Batch API 處即用之。步驟 1 與步驟 3 以
`claude-sonnet-4-6` 執行,`temperature=0`thinking
停用;步驟 4 與步驟 5 以 `claude-fable-5` 執行,該
模型兩個參數皆不受理——步驟 4 的取樣變異由多數決
吸收,步驟 5 由整合三份閱讀吸收。
- 定義檔置於 `prompts/<步><次步>-<task>.md`(如
1-tag.md、5a-read.md;次步以字母標示,與論文正文的
步驟編號一致;不帶版本號——版本即 git 歷史),逐字
作為 system prompt。
- 逐項的 LLM 判斷(步驟 3 的逐首編碼、步驟 4 的逐碼
入群判斷)以同一份定義檔、同一份輸入獨立執行三次;
再由確定性計票將三次執行中至少兩次指派的項目(一個
(歌,關鍵字)或(群,關鍵字)配對)收入定案
(「三次執行+多數決」)。自由生成步驟執行兩次,
兩份輸出進池。步驟 5 的質性閱讀兩者皆非:逐首三次
獨立閱讀,由各自的定義檔逐首整合、跨首統整——質性
協定,不是投票(見 docs/methodology.md)。詞彙表由
確定性子命令建構(嵌入+分群),不經 LLM。驗證結果
不符預期時,修訂定義檔並重複該循環;絕不手改結果。
- 一步的每次執行皆自我完備歸檔於 `run-llm` 命令列上
明示指定的目的目錄下(慣例為
`data/runs/<步驟>/run<N>/`):定義檔快照、原始輸出與
`meta.json`model ID、參數、時間戳、batch ID)。
一步的 N 次執行即 N 次各自的 `run-llm` 呼叫。覆蓋
既有執行歸檔須明示旗標;被取代的執行留在 git 歷史。
確定性步驟歸檔於 `data/runs/<步驟>/`,不分 `run<N>` 層。
- 每次 `run-llm` 執行的 token 用量與費用記入
`docs/run-costs.md`,與執行歸檔同一 commit。
- 腳本自環境變數 `ANTHROPIC_API_KEY` 讀取 API key
`.env`gitignored)。
## Data rules
## 資料規則
- `data/source/` holds the immutable hand-placed raw files;
`data/captures/` is written only by the fetch commands and the
private import script; `data/manual/` is written only by the
user's own hand; `data/derived/` is written only by the
`build-db` subcommand.
- Full lyrics are copyrighted: they stay in `data/captures/lyrics/`
(gitignored) and must never be committed or reproduced in
full anywhere in the repo.
- `data/source/` 存手放後不動的原始檔;
`data/captures/` 只由 fetch 命令與私人匯入腳本
寫入;`data/manual/` 只由研究者親手寫入;
`data/derived/` 只由 `build-db` 子命令寫入;
`data/runs/` 存 LLM 執行的原始歸檔,只由執行程序
寫入;`data/results/` 存論文引用的定案表,只由
計票程序寫入。
- 歌詞全文有版權:一律置於 `data/captures/lyrics/`
gitignored),絕不 commit,亦絕不於 repo 任何處
全文重現。
## Documents
## 文件
- `results/` holds the final tallied tables (what the paper
cites); `runs/` holds raw audit records. The paper cites
`results/` only.
- Any change to a definition file or the plan is recorded in
`docs/decision-log.md` with date and reason.
- `data/results/` 存計票後的定案表(論文所引);
`data/runs/` 存原始稽核紀錄。論文只引 `data/results/`
- **Commit 判準**:凡能由「committed 的輸入+committed
的程式」決定性再生者不 commit;凡不能者一律以文字
格式 commit,格式跟著上文「資料規則」一節所定的
層次走。例外:`data/results/` 定案表與
`data/derived/` 人讀報表雖可再生仍 commit——理由是
引用穩定性、審稿人零門檻、撰稿期數字變動可 diff;
兩者皆與工作儲存同一動作產出,稽核鏈無中間空缺。
- 凡定義檔或研究規劃之更動,皆記入
`docs/decision-log.md`,註明日期與原因。
+101 -202
View File
@@ -1,12 +1,12 @@
# 方法細節
(全文方法節底稿。演算法在執行前寫定;任何修訂記入
`decision-log.md`定義檔全文見 `prompts/`,執行紀錄見
`runs/`。)
`decision-log.md`
## 自然編碼管線總覽
五個步驟:步驟 1 自由標註(兩次執行進池)→ 步驟 2 詞彙表
五個步驟:步驟 1 自由標註(`claude-sonnet-4-6`
temperature=0、thinking 關閉,兩次執行進池)→ 步驟 2 詞彙表
建構(詞向量分群,確定性)→ 步驟 3 全量編碼(三次執行+
多數決)→ 步驟 4 語意編碼群(三次執行+多數決)→ 步驟 5
女性主義問題之質性深讀(三次閱讀+逐首整合+樣態統整)。
@@ -14,13 +14,6 @@
不接觸歌詞,步驟 2 亦不呼叫 LLM。設計原則見
`research-plan.md`;本檔記載可重現的演算法細節。
編號的所指為**研究程序的工序**,不是定義檔:步驟 1、
步驟 3、步驟 4 與步驟 5 有定義檔(`prompts/`;步驟 5 依
子工序有三份),步驟 2 沒有——它是單一確定性計算,由
`cluster-keywords` 一個子命令完成。有無定義檔的區別即
「該步是否為 LLM 判斷」,由 `prompts/` 是否存在同號檔案
直接可見。
## 步驟 2 詞彙表建構——詞向量分群
詞彙表由確定性程序產生,不經 LLM。完整分割(每個關鍵字
@@ -30,8 +23,7 @@
### 進池
兩次標註執行的全部關鍵字取聯集、逐字串精確去重、字典序
排列。失敗與拒答的記錄跳過(其歌曲不貢獻關鍵字);解析
時偵測重複鍵,違規即失敗。
排列。失敗與拒答的記錄跳過(其歌曲不貢獻關鍵字)
### 分群
@@ -39,14 +31,10 @@
(釘定 revision),關鍵字的連字號先還原為空格再編碼,
輸出 768 維向量並 L2 正規化。
- **分群**:階層式聚合分群(Ward linkage),k=100。
向量既已正規化,歐氏距離與餘弦相似度單調對應;三種
linkage 實測比較,Ward 於各個 k 的組內一致性均最高
average 與 complete 皆產生吞噬半數語料的巨大異質
組)。
向量既已正規化,歐氏距離與餘弦相似度單調對應
- **組數的取捨**:k 太小則壓縮比過高,樹上層被迫併入
不相干的詞,組雖大而無主題k=30 最大組 491 詞、
組內一致性 0.41,成員橫跨籃球、海灘、外星人綁架);
k 太大則人工難以通覽。定於 100,理由是實測顯示雜物櫃
不相干的詞,組雖大而無主題k 太大則人工難以通覽。
定於 100,理由是實測顯示雜物櫃
組於此始裂解為有主題的組,且編碼實測未見碼數過多的
副作用——全量三次執行下 101 個碼全數用到;模型另行
造出的碼共 13 筆,佔 44,149 筆標籤指派的 0.03%。
@@ -55,39 +43,23 @@
自己產出過的關鍵字,非任何人事後撰寫。已知限制:
組越大越異質時,medoid 只是折衷詞,可能代表不了組內
內容(實測 `mutual-individuality` 組內一致 0.70 而
編碼從未使用);此類碼於結果中呈現為零使用,據實
報告,不事後改名。
- **取捨紀錄**:曾以 LLM 單發收斂(mergecap 兩步)
實作本步,四種模型六次執行全部無法維持完整分割,
已棄用(詳見 `decision-log.md` 2026-08-05;棄用的
定義檔止於 git 歷史,見 `git log -- prompts/`
- **產物**:五份,前綴分別標示來源與結果。
`source-keywords.txt`(進池後的關鍵字,一行一個)
記錄進來的是什麼;`result-keywords.txt`(組名,一行
一個)與 `groups.csv`(欄位 Group、Keyword,一列一個
成員)記錄算出來的分割;`keywords-to-merge.json`
`{"keywords": [...]}`)是實際交給模型的碼,即組名
加上先驗主題詞——五份中只有這一份含研究者的介入。
`meta.json` 記錄執行本身:進池的兩份執行歸檔與其有效
筆數、嵌入模型與釘定 revision、分群參數與組數、外加
的先驗詞、關鍵字總數,以及產生數字的套件版本。凡命令
列上的選擇與環境事實皆在此,不記時間戳與輸入雜湊
——前者使同環境重跑逐位元組可再生,後者只會重述 git
已保證的事。
編碼從未使用);此類碼於結果中呈現為零使用
- **取捨紀錄**:詞彙表分群曾比較的替代法與棄用理由,見
`decision-log.md` 2026-08-05 條。
- **產物**:分群輸出中,只有實際交給模型的碼表——組名
加上先驗主題詞——含研究者的介入;其餘皆為分群過程本身
的機械紀錄
- **可重現性**:同一輸入、同一釘定模型、同一參數逐次
重現。不同 CPU/BLAS 實作的浮點尾數差異可能使邊界
詞的歸屬翻動,屬已揭露的限制;論文所用碼表逐字
commit,引用單位為該份定案檔案。
詞的歸屬翻動,屬已揭露的限制
### women-power 的注入
定案詞彙表為 100 個分群組名再加上 `women-power` 一詞,
共 101 個碼。`women-power` 是研究者任意決定的先驗主題(即本
論文的主題本身),不由資料產生,屬揭露的儀器介入。該詞
於執行時以 `--extra-keyword` 明示加入,不寫死在程式裏
——研究者的介入因此每次都出現在重現命令上,而非無聲
發生;分群結果的兩份產物不含它,只有交給模型的碼表含
它。
的加入於每次執行皆為明示、可稽核的介入,而非無聲內建於
判斷邏輯;分群結果本身不含它,只有交給模型的碼表含它。
注入而非另設篩選軌的理由:讓研究者的主題詞與模型自己
收斂出的類別(分群已自行長出 `female-empowerment` 等組)
@@ -97,24 +69,17 @@
## 步驟 3 編碼的三次執行與多數決
- **模型**`claude-sonnet-4-6`temperature=0、thinking 關閉。
- **三次執行**:同一份定義檔、同一份輸入檔,獨立執行
三次,三份歸檔並列`runs/3a-code/run1``run2`
`run3`),彼此無先後主從之別。
三次,三份歸檔並列,彼此無先後主從之別。
- **多數決**:一首歌的一個標籤,三次執行中至少兩次標出
即收入定案編碼。三票不平手,裁決規則因此無例外條款,
計票由確定性子命令完成(見交接契約)。
- **第三票取全量**只對前兩次分歧的標籤補問第三票,
計票結果相同;仍採全量執行——全部歌曲、全部關鍵字
——使三票在同一條件下取得。
計票由確定性程序完成(見交接契約)。
- **第三票取全量**第三次執行同為全量——全部歌曲、
全部關鍵字——使三票在同一條件下取得。
- **對邊緣標籤的作用**:兩次執行只分得出「兩次皆標」與
「僅一次標」;三次執行還分得出 3-0 與 2-1,故「定案
編碼中有多少比例僅以一票之差成立」成為可報告的量。
至於判定本身,兩次執行相左的標籤在何種協定下都由第三
個判斷定奪,票數不使不確定性消失:某標籤於單次執行被
標出的傾向若恰為一半,任何票數皆為擲幣。三票之效在
傾向偏離一半處——多數決將判定推向該傾向本身(單次
0.7 者為 0.780.9 者為 0.97),程序重跑的一致性因而
高於單次執行,唯獨恰半處無從改善。
## 步驟 4 語意編碼群
@@ -125,103 +90,79 @@
由 LLM 依編碼名的字面語意判斷。
- **任務**:每筆輸入為一個群名加 101 個編碼的字母序
清單,輸出為入選編碼的單層 JSON 陣列;定義檔
`prompts/4-group.md` 只定格式,不含任何群的語意定義。
- **模型**`claude-fable-5`(步驟 1、3 為
`claude-sonnet-4-6`)。該模型不受理 `temperature`
`thinking` 參數,兩者均不送出;取樣變異由多數決吸收。
只定格式,不含任何群的語意定義。
- **模型**`claude-fable-5`;取樣變異由多數決吸收;
模型裁定的理由與對照實驗見決策日誌。
- **三次執行**:同一份定義檔、同一份輸入檔,獨立執行
三次,歸檔並列`runs/4-group/run1``run2``run3`
三次,歸檔並列。
- **多數決**:一個(群,編碼)配對,三次執行中至少兩次
入選即屬該群;不在 101 碼詞彙表內的輸出項無效,
丟棄印於標準錯誤。計票由確定性子命令 `tally-groups`
完成:`tally-groups <執行歸檔 1> <執行歸檔 2> <執行歸檔
3> <合法碼清單> <輸出 CSV>`,合法碼清單之產法同步驟 3。
定案分群寫入 `results/groups.csv`,欄位 `Group`
`Keyword``Votes`,列序先依群名、再依編碼,一律以
Unicode 碼位比較,換行為 CRLF。
- **工作儲存**`build-db --groups <定案分群 CSV>` 將定案
分群逐欄照存入 `groups` 資料表(群、編碼、票數),供
群層次查詢。
入選即屬該群;不在 101 碼詞彙表內的輸出項無效,
記錄後丟棄。計票由確定性計票程序完成,合法碼
清單之產法同步驟 3,結果為定案分群表。
## 步驟 5 女性主義問題之質性深讀
本步驟之 5a 至 5c 為**質性閱讀,非編碼**:輸出為自由
文字的問題閱讀報告,無可逐項機械比對的單位,故不適用
三票多數決與仲裁;
三次獨立閱讀為分析者三角檢核,逐首整合為整合而非裁決,
跨首統整之產出為草稿,終審與詮釋由研究者為之。論文引用
本步驟之 5a 至 5c 為**質性閱讀非編碼**輸出為自由
文字的問題閱讀報告無可逐項機械比對的單位故不適用
三票多數決與仲裁
三次獨立閱讀為分析者三角檢核逐首整合為整合而非裁決
跨首統整之產出為草稿終審與詮釋由研究者為之。論文引用
本步驟時不作次數宣稱。
- **對象**:定案編碼含 `women-power`
- **對象**定案編碼含 `women-power`
`female-empowerment` 的 145 首歌。
- **5a 逐首閱讀**:每筆輸入為一首歌的完整歌詞逐字全文,
不含歌名與演唱者(盲讀);定義檔
`prompts/5a-read.md`。同一份定義檔、同一份輸入檔,
獨立執行三次,歸檔並列(`runs/5a-read/run1`
`run2``run3`)。
- **5b 逐首整合**:每筆輸入為該首歌的三份閱讀報告
(不含歌詞);以問題機制為單位保守合併,標收斂註記
((3/3)、(2/3)),主清單僅列兩讀以上提出者,單讀發現
以一行存目;定義檔 `prompts/5b-consolidate.md`,
執行一次,歸檔 `runs/5b-consolidate/run1`
- **5c 樣態統整**:單筆輸入為一批整合報告;歸納問題
**樣態**——問題呈現與運作的重複形態,非問題分類,
代表引句僅取自主清單;定義檔
`prompts/5c-synthesize.md`。四種輸入範圍各執行
一次:全 145 首之基底統整(歸檔
`runs/5c-synthesize/run1`),及依 `performer_gender`
(演唱聲音之性別)切分之三個發話脈絡統整——男聲
(male,歸檔 `run2`)、女聲(female,歸檔 `run3`)、
混合(mixed,歸檔 `run4`);基底看橫貫各脈絡之樣態,
分組看各權力脈絡下之樣態。genderfluid 與 non-binary
共 3 首不設群——樣本數不支持歸納——僅入基底統整,
由研究者以個案閱讀。同一輸入不重複執行:自由歸納之
產出無機械合併可言,其變異由 5a 三讀、5b 整合與
草稿地位承接,四份草稿互為對照,由研究者終審裁決
- **5d 樣態標註**:以 (歌, 樣態) 對為可逐項機械比對之
單位,回歸「三次執行+多數決」協定。對象為「有問題」
的歌——三讀中至多一個「無」(多數決精神;恰兩「無」
者其整合報告主清單必為空,與 5b 主清單規則自洽),
計 111 首(男聲 12、女聲 69、混合 29、genderfluid 1)。
樣態表為三份分組統整草稿原文:男聲 13 條(M1–M13)、
女聲 14 條(F1F14)、混合 16 條(X1X16);基底 15 條
- **5a 逐首閱讀**每筆輸入為一首歌的完整歌詞逐字全文
不含歌名與演唱者盲讀)。同一份定義檔、同一份輸入檔,
獨立執行三次,歸檔並列。
- **5b 逐首整合**:每筆輸入為該首歌的三份閱讀報告
(不含歌詞);以問題機制為單位保守合併,標收斂註記
((3/3)、(2/3)),主清單僅列兩讀以上提出者,單讀發現
以一行存目;定義檔,執行一次。
- **5c 樣態統整**:單筆輸入為一批整合報告;歸納問題
**樣態**——問題呈現與運作的重複形態,非問題分類,
代表引句僅取自主清單。四種輸入範圍各執行
一次:全 145 首之基底統整,及依演唱聲音之性別切分
之三個發話脈絡統整——男聲(male)、女聲(female)、
混合(mixed);基底看橫貫各
脈絡之樣態,分組看各權力脈絡下之樣態。genderfluid
與 non-binary 共 3 首不設群——樣本數不支持歸納——
僅入基底統整。同一輸入不重複
執行:自由歸納之產出無機械合併可言,其變異由 5a
三讀、5b 整合與草稿地位承接,四份草稿互為對照,由
研究者終審裁決。
- **5d 樣態標註**:以(歌,樣態)對為可逐項機械比對之
單位,回歸「三次執行+多數決」協定。對象為「有問題」
的歌——三讀中至多一個「無」(多數決精神;恰兩「無」
者其整合報告主清單必為空,與 5b 主清單規則自洽),
計 111 首(男聲 12、女聲 69、混合 29、genderfluid 1
樣態表為三份分組統整草稿原文:男聲 13 條(M1–M13)、
女聲 14 條(F1F14)、混合 16 條(X1–X16);基底 15 條
不入矩陣——全體歸納之一條樣態可能疊合不同方向的
權力關係(男對女、女對男),實為多個樣態共用一名。
檢驗範圍:男聲樣態不檢驗純女聲歌、女聲樣態不檢驗
純男聲歌(發話位置範疇錯置,檢查無意義);混合樣態
檢驗全部(其男女聲部無系統化切分方式,無意義之標註
容忍之);genderfluid 歌三套全查(無自身透鏡,發話
位置無法先驗決定),其歸屬引用維持個案地位。每筆
輸入為一首歌之整合報告(「僅單獨提及」行於組裝時
剝除,標註僅依主清單)與該首適用之樣態表;定義檔
`prompts/5d-annotate.md`,獨立執行三次
(`runs/5d-annotate/run1`~`run3`),(歌, 樣態) 對
權力關係男對女、女對男),實為多個樣態共用一名。
檢驗範圍男聲樣態不檢驗純女聲歌、女聲樣態不檢驗
純男聲歌發話位置範疇錯置檢查無意義);混合樣態
檢驗全部其男女聲部無系統化切分方式無意義之標註
容忍之);genderfluid 歌三套全查無自身透鏡發話
位置無法先驗決定),其歸屬引用維持個案地位。每筆
輸入為一首歌之整合報告「僅單獨提及」行於組裝時
剝除標註僅依主清單與該首適用之樣態表定義檔
獨立執行三次,(歌,樣態)對
得兩票以上者定案。
- **模型**:`claude-fable-5`(與先導深讀同儀器;
`temperature``thinking` 參數不適用,均不送出)
- **輸入組裝**:確定性行內腳本。5a:145 首依歌曲 ID
升序,`content` 為歌詞逐字全文;5b:每筆
`{"reports": [run1 輸出, run2 輸出, run3 輸出]}`;
5c:單筆以 `song-<ID>` 為鍵、整合報告為值之 JSON
物件,鍵集合為該次統整之範圍(基底為全 145 首,
分組依工作庫 `performer_gender` 切分);5d:每筆
`{"report": 主清單, "patterns": [{"id", "name",
"description"}]}`,樣態條目自分組統整草稿機械切出,
代表引句不隨附——引句出自特定歌曲,判該曲時形同
預答。各輸入檔之 SHA-256 記入該步 meta。
- **模型**`claude-fable-5`
- **輸入組裝**:各步輸入檔由確定性程序自上游產物組裝
5d 之樣態條目自分組統整草稿機械切出,代表引句不
隨附——引句出自特定歌曲,判該曲時形同預答。
## 女性力量候選集
候選集為兩類歌曲的合集:定案編碼含 `women-power` 者,
以及定案編碼含研究者指認之女性力量概念域分群組者。
指認於詞彙表定案後、黃金標準編碼開始前完成,指認清單
與理由記入決策日誌。
候選集為定案編碼含 `women-power` `female-empowerment`
(步驟 4 女性力量群的兩個編碼)之歌曲聯集:wp 66 首、
fe 144 首,聯集 145 首。
## 軌跡對映(診斷用)
沿收斂軌跡的機械對映:原始關鍵字 →(兩份標註執行歸檔的
`output.jsonl`)歌曲、原始關鍵字 →(分群)組,純程式查表,
原始輸出)歌曲、原始關鍵字 →(分群)組,純機械查表,
決定性。以其結果與步驟 3 直接編碼的差異率作為「收斂軌跡
扭曲」的診斷量,不作主結果。
@@ -230,78 +171,36 @@
每一步的輸出如何變成下一步的輸入,皆為確定性程序,規則
明定如下:
- **歌詞輸入檔(步驟 1**`export-llm-input` 自工作
儲存產出,每筆 `{"id": "song-<ID>", "content": <歌詞>}`
依歌曲 ID 升序。步驟 3 的輸入由同一子命令、同一工作
儲存產出(見下),兩步的語料同一性由此成立;各步
輸入檔的 SHA-256 記入該步 meta。
- **步驟 1 → 2**`cluster-keywords` 讀兩份執行歸檔的
`output.jsonl`(一律以換行字元 `\n` 切行——歌詞含
U+0085 等控制字元時,`str.splitlines()` 類的通用切行
會截斷 JSON 字串,實測踩中),進池後直接分群,一次
產出上列五份檔案。
- **步驟 2 → 3 輸入檔**`export-llm-input --extras
<定案碼表>` 自工作儲存產出步驟 3 的輸入,每筆
`{"id": "song-<ID>", "content": <字串>}``content` 為
固定鍵序序列化的 `{"lyrics": …, "keywords": [...]}`
依歌曲 ID 升序。碼表以參數傳入而非填進定義檔——定義
- **歌詞輸入檔(步驟 1**由確定性的匯出程序自工作
儲存產出,一筆一首歌,依歌曲 ID 升序。步驟 3 的輸入
由同一匯出程序、同一工作儲存產出(見下),兩步的語料
同一性由此成立
- **步驟 1 → 2**:確定性的分群程序讀兩份執行歸檔的
執行紀錄,進池後直接分群,產出詞彙表與交給
模型的碼表。
- **步驟 2 → 3 輸入檔**:同一匯出程序自工作儲存產出
步驟 3 的輸入,一筆一首歌,兼含歌詞與定案碼表,依
歌曲 ID 升序。碼表以參數傳入而非填進定義檔——定義
檔只規定任務形狀,換詞彙表、換演算法都不必改它。
- **步驟 3 定案**`tally-codings <執行歸檔 1> <執行歸檔
2> <執行歸檔 3> <輸出 CSV> --corrections <更正表>
--valid-keywords <合法碼清單>` 讀三份執行歸檔的
`output.jsonl`,依序套用更正表、驗證所有標籤皆在合法碼
- **步驟 3 定案**確定性的計票程序讀三份執行歸檔的
執行紀錄,依序套用更正表、驗證所有標籤皆在合法碼
清單之內、計票。
- **更正表**`data/manual/coding-corrections.csv`,研究者
逐列校定的人工著作,欄位 `Song ID`、`Run`、`Type`、
`To Be Replaced`、`Correct Term`。`Type` 為 `keyword`
或 `evidence`,分別更正標籤與引述;`Correct Term` 為
替代字串,或 `**REMOVE**` 表示刪去該筆標籤指派(`keyword`
或該句引述(`evidence`)。一筆 `evidence` 更正套用於該
首歌該次執行的所有出現處。兩個文字欄以歌詞慣例「 / 」
表示換行(與載入後的執行紀錄同一表示法,逐字比對、不再
轉換),故一列一行,純文字工具可逐列處理。表中任一列若
在資料中找不到對應者,即中止;校定的判準記於
`decision-log.md`。
- **合法碼清單**:純文字、一行一個碼,自詞彙表產出:
`{ cat runs/2-cluster/result-keywords.txt; echo
women-power; } | sort`。
- **定案表**`results/codings.csv`,欄位 `Song`、
`Artist Credit`、`Keyword`、`Quote`,一列一個標籤。歌名
與演出者名銜逐首查工作儲存取得,故本子命令須在
`build-db` 之後執行。`Quote` 為該標籤在計票中各份執行
所引的歌詞行:各份的引述串接後逐字去重,按 Unicode
碼位排序,以單一 `|` 相接(三份執行彼此無先後主從之
別,引述之序取決於引述本身);引述內的換行於執行紀錄
載入時一次換成歌詞慣例「 / 」,此後更正表、定案表與
工作儲存全鏈路同一表示法,不再還原。「 / 」的無歧義性
是語料事實而非結構保證:全 883 首歌詞經窮舉查核不含
「 / 」;換語料須重查。列序依印出的前三欄依序排:
歌名、演出者名銜、
標籤,一律以 Unicode 碼位比較,換行為 CRLF(同專案
其他 CSV)。
- **序列化通則**:所有中間檔為 UTF-8,欄序、鍵序與元素
序皆依上列規則明定,無時間戳、無隨機成分;JSON 解析
一律偵測重複鍵,違規即失敗。人讀為主的產物採純文字或
CSVCSV 依 RFC 4180,標題列字首大寫),機器交接檔採
JSON。給定相同的 LLM 執行輸出,全部交接產物逐位元組
可再生。
- **更正表**研究者逐列校定的人工著作,逐筆更正標籤
或引述——以替代字串取代,或刪去該筆標籤指派或該句
引述。一筆引述更正套用於該首歌該次執行的所有出現處。
表中任一列若在資料中找不到對應者,即中止;校定的
判準記於 `decision-log.md`
- **合法碼清單**:自詞彙表產出:分群組名加上
`women-power`
- **定案編碼表**:歌名與演出者名銜逐首查工作儲存取得。
每個定案標籤隨附其在計票中各份執行所引的
歌詞行,供逐碼查核(三份執行彼此無先後主從之別)。
## 執行與稽核
- LLM 步驟以 `run-llm <定義檔> <輸入檔> <歸檔目錄>`
執行;一步的 N 次執行=重現命令清單上的 N 行命令,
各自歸檔(`runs/<步驟>/run1`、`run2`,三票制步驟另有
`run3`)。
- 確定性步驟(進池、分群、計票、對映)為子命令,其
輸入輸出檔同隨 `runs/` 歸檔;因無執行變異,歸檔目錄
下不分 `run<N>` 層。
- LLM 步驟以批次執行程序執行,一份定義檔配一份
輸入檔;一步的 N 次執行為 N 次各自獨立的呼叫,各自
歸檔自我完備。
- Batch API 的每筆請求自含全部脈絡且互不可見(平台
契約),歌與歌之間的獨立性由此成立;各次執行的獨立
性由「一次呼叫、一個批次、一份歸檔」的執行結構自明。
- 每次 `run-llm` 執行的 token 用量與費用記入
`run-costs.md`,被取代的執行一併保留供總支出核算。
## 映射分析方法
(依 2026-07-30 決策,於看到結果前寫定;待黃金標準
編碼展開前補入。)
+3 -4
View File
@@ -1,7 +1,7 @@
# LLM 輸出的契約查核
2026-08-06 量測。對象為步驟 3 的三份執行歸檔
`runs/3a-code/run1``run3`,共 883 首歌、44,149 筆標籤
`data/runs/3a-code/run1``run3`,共 883 首歌、44,149 筆標籤
指派、44,146 句引述。歌詞以模型實際看到的那一份為準,即
`tools/instance/llm-input-code.jsonl`。)
@@ -46,9 +46,8 @@ song-750 run3: -use=[] -abuse=["I got a thing for the hard
liquor on ice"]
```
模型寫錯後綴、已輸出的 token 收不回,遂以空陣列收束該鍵
再於正確的鍵補上引述。song-750 的 run1 與 run2 則整筆使用
錯拼的鍵並附上引述,故該首的三票分裂於兩種拼寫之間。
song-750 的 run1 與 run2 則整筆使用錯拼的鍵並附上引述
故該首的三票分裂於兩種拼寫之間。
## 引述的存在
+17 -39
View File
@@ -2,15 +2,14 @@
2026-08-17 認清並記錄。本檔記述正式研究之前的先導研究:
它做了什麼、研究者檢視到哪一層、哪些成果被沿用、哪些被
棄用,以及它如何促成正式研究的設計。散見於
`decision-log.md` 的相關條目在文中逐一指出。)
棄用,以及它如何促成正式研究的設計。
## 一、先導研究是什麼
正式研究之前,研究者曾以 Claude Code 對 Billboard Year-End
Hot 100(20182025、684 首)的歌詞做過一輪探索性分析,檢視
「女性力量」語彙的使用狀況,並附帶分析 pussy 一詞作為女性
代稱的修辭。2026 年 8 月投出的研討會摘要即根據該輪分析撰寫。
代稱的修辭。2026 年 4 月投出的研討會摘要即根據該輪分析撰寫。
## 二、它實際的執行方式
@@ -37,25 +36,22 @@ frame-aware 提示修正實驗,均由當時協作的 Claude Code 設計並
- **歌詞捕捉檔**:先導研究蒐集的 lyrics.json(684 首,
2018–2025)以私人腳本(不入版本庫)匯入歌詞快取,只取
識別欄位與歌詞本文,先導的分析欄位一概不匯入;出處記於
`data/captures/lyrics-provenance.csv`,method 欄標
`pilot-import`(見 `decision-log.md` 2026-07-31、08-02 條)。
`data/captures/lyrics-provenance.csv`
(見 `decision-log.md` 2026-07-31、08-02 條)。
- **假說方向**:女性力量語彙的挪用與污染,成為正式研究的
研究問題。
- **粒度選擇**:正式研究鎖定 thematic keywords 這一粒度,
係繼承先導研究三種粒度的比較結果(keywords 過碎、themes
過早抽象),且於執行前鎖定以防事後擇優
過早抽象)。
- **`women-power` 一詞的來歷**:考據先導研究的 local agent
存檔可知,其第一步指令含數十個範例 thematic keywords,
其中即有 women-power——為當時協作的 Claude Code 依研究者
長期表達的關注主動加入(研究者端播種,非明示指定);先導
的標籤 `women-power-and-empowerment` 則是第三步強制合併
兩個關鍵字的管線人工產物。正式研究的探針因此指向被播種的
本詞 `women-power`,不用合併假影(見 `decision-log.md`
2026-08-04 條)。**附帶認清:先導第一步並非零語意提示,
此即正式研究「提示詞只定格式、不定語意」設計所矯正者。**
兩個關鍵字的管線人工產物(見 `decision-log.md` 2026-08-04
條)。
- **簿記容量的教訓**:先導研究九百餘詞可以在單一回應內完成
分組,正式研究的 5,999 個關鍵字則四種模型六次執行全部未
通過完整分割驗證,遂改用詞向量嵌入+確定性分群(見
分組;此法未沿用,詞彙表改由確定性程序產生(見
`decision-log.md` 2026-08-06 條)。
## 四、被棄用的成果
@@ -63,35 +59,20 @@ frame-aware 提示修正實驗,均由當時協作的 Claude Code 設計並
以下先導研究的產物未進入正式研究,論文亦未引用:
- **genuineperipheralfake 三分類與「44% 假女性力量」**:
構念與分母皆與正式研究不同(正式研究為 145 首女性力量群
歌曲中 111 首有性別問題),兩者不可對讀
- **五種「假女性力量」類型(A–E)**:正式研究改由儀器分三個
發話脈絡各自歸納,得 43 條問題樣態(`results/patterns.csv`)。
- **pussy 一詞的修辭分類**:正式研究範圍收斂至語彙的挪用與
污染,未納入。
構念與分母均未經稽核回溯,未沿用。
- **五種「假女性力量」類型(A–E)**:未沿用
- **pussy 一詞的修辭分類**:未沿用。
- **frame-aware(框架感知)提示修正法**:先導研究以框架判準
寫進提示以提高準確率;正式研究刻意不定義編碼,因為研究
對象正是 LLM 未受引導的自然編碼——把框架寫進提示,即無法
再以其輸出為批判對象。兩者的設計方向相反,故未沿用
- **三個獨立 LLM subagent+人工仲裁的流程**:正式研究改以
Anthropic API 逐首獨立呼叫,原因是實測證實 subagent 會繼承
CLAUDE.md 與環境資訊,context 無法僅憑定義檔重現(見
`decision-log.md` 2026-07-30 條)。
寫進提示以提高準確率;未沿用。
- **三個獨立 LLM subagent+人工仲裁的流程**:未沿用,原因見
`decision-log.md` 2026-07-30 條
## 五、它如何促成正式研究的設計
## 五、它促成了可稽核的設計
先導研究的根本限制不在結論對錯,而在**不可稽核**:沒有定義檔
快照、沒有原始輸出歸檔、沒有參數紀錄,因此任何一個數字都無法
回溯到產生它的那一次執行。正式研究的幾項設計正是針對這一點:
- 所有 LLM 步驟改以 API script 執行,每次執行自我完備歸檔於
`runs/`(定義檔快照、原始輸出、model ID 與參數、批次 ID、
token 用量);
- 可逐項機械比對的判斷採三次執行+多數決,並量測其穩定性
(見 `reliability.md`);
- 定義檔只規定任務形狀,不給主題定義、判準或範例;
- 每一筆編碼必附歌詞引述,供逐筆查核;
- 輸出的契約遵從另行查核(見 `output-validation.md`)。
回溯到產生它的那一次執行。正式研究的可稽核設計,正是針對這一點
而立。
換言之,正式研究對 LLM 輸出所採取的「不信任、須查核」立場,
其第一個案例就是先導研究本身。
@@ -102,6 +83,3 @@ frame-aware 提示修正實驗,均由當時協作的 Claude Code 設計並
的前身),樹中不另存副本——摘要只是產物,不足以記述過程,
故另立本檔。
- 先導研究的歌詞捕捉檔沿用事實:`data/captures/lyrics-provenance.csv`
- 相關決策條目:`decision-log.md` 2026-07-30(不與先導比較、
不用 subagent)、07-31 與 08-02(歌詞沿用與私人匯入腳本)、
08-04(women-power 來歷考據)、08-06(詞彙表改用詞向量分群)。
-139
View File
@@ -1,139 +0,0 @@
# 專案目錄結構
2026-07-30 討論定案;2026-07-31 更新為 tools/ 子專案與
SQLite 工作儲存架構;2026-08-17 依完成後的現況更新)
```
pop-fem-audit/
├── README.md # 專案說明
├── .gitignore # captures/lyrics/、.env、scratch
├── data/ # 依生命週期分層(文字格式)
│ ├── source/ # 源頭:手放後不動
│ │ └── yearend_hot100_2016_2025.csv # 原始榜單
│ ├── captures/ # 外部捕捉:只由 fetch 命令與
│ │ │ # 私人匯入腳本寫入
│ │ ├── artists-wikidata.csv # Wikidata 快照
│ │ ├── lyrics-provenance.csv # 歌詞出處
│ │ └── lyrics/ # 歌詞 .txt 快取
│ │ # gitignored,版權)
│ ├── manual/ # 人工著作:只由研究者手寫
│ │ ├── coding-corrections.csv # 編碼與引述的校對表
│ │ └── performer-gender-corrections.csv # 演唱聲音性別的
│ │ # 手工修正
│ └── derived/ # 衍生:只由 build-db 寫入
│ ├── songs.csv # 歌曲報表(人讀;進 git)
│ └── artists.csv # 歌手報表(人讀;進 git)
├── prompts/ # LLM 定義檔(逐字作為 system prompt
│ └── <步><次步>-<task>.md # 1-tag.md、3a-code.md、
│ # 4-group.md、5a-read.md、
│ # 5b-consolidate.md、
│ # 5c-synthesize.md、
│ # 5d-annotate.md
│ # (次步以字母標示,與論文正文
│ # 的步驟編號一致;步內僅一個
│ # 執行時省略次步)
│ # 不帶版本號,版本即 git 歷史
│ # (編號的所指是工序:確定性
│ # 的步驟 2 無定義檔仍佔一號)
├── tools/ # 輔助工具子專案(src-layout
│ ├── pyproject.toml # 發行名 pop-fem-audit-tools
│ │ # pip install -e tools/ 安裝
│ ├── README.rst LICENSE MANIFEST.in .env.example .gitignore
│ ├── docs/ # Sphinx API 文件
│ ├── instance/ # SQLite 工作儲存(generated、
│ │ # gitignored;含歌詞全文)
│ ├── src/pop_fem_audit_tools/
│ │ ├── __main__.py # 套件 CLI 進入點(分派子命令)
│ │ ├── commands/ # CLI 子命令模組(登記於 __init__)
│ │ │ ├── build_db.py # build the SQLite working store
│ │ │ │ # from the inputs
│ │ │ ├── export_llm_input.py # export the LLM input JSONL
│ │ │ │ # (lyrics only) from the
│ │ │ │ # working store
│ │ │ ├── fetch_artists.py # fetch artist metadata from
│ │ │ │ # Wikidata into the snapshot CSV
│ │ │ ├── fetch_lyrics.py # fetch missing lyrics from the
│ │ │ │ # public APIs into the lyrics dir
│ │ │ ├── cluster_keywords.py # pool the tagging runs'
│ │ │ │ # keywords and cluster them
│ │ │ │ # into the codes (step 2)
│ │ │ ├── tally_codings.py # settle step 3 by majority
│ │ │ ├── tally_groups.py # settle step 4 by majority
│ │ │ ├── tally_annotations.py # settle step 5d by majority
│ │ │ └── run_llm.py # API 執行器:一份定義檔+一份輸入
│ │ │ # →歸檔至指定目錄(Batch API);
│ │ │ # 多次執行的計票由獨立子命令承擔
│ │ ├── config.py # pydantic-settings 設定(.env
│ │ ├── database.py # SQLAlchemy engine / session / Base
│ │ ├── models.py # SQLAlchemy ORM 資料模型
│ │ └── utils.py # 共用工具(format_duration
│ └── tests/ # 單元測試(unittest
├── runs/ # 現行執行的完整稽核紀錄(進 git;
│ │ # 重跑同一 run 須明示 --replace
│ ├── <步驟名>/ # 一步一個目錄(1-tag、3a-code、
│ │ # 4-group、5a-read、
│ │ # 5b-consolidate、
│ │ # 5c-synthesize、5d-annotate
│ │ └── run<N>/ # LLM 步驟:每個 run 一份自我
│ │ ├── prompt.md # 完備歸檔(定義檔快照)
│ │ ├── output.jsonl # 該次執行原始輸出
│ │ └── meta.json # model ID、temperature、時間戳、
│ │ # batch ID、token 用量
│ └── 2-cluster/ # 確定性步驟:無執行變異,
│ # 不分 run<N> 層
├── results/ # 論文引用的定案表 CSV(計票子命令
│ │ # 產出;「可再生仍 commit」的例外)
│ ├── codings.csv # 步驟 3 定案編碼
│ ├── groups.csv # 步驟 4 定案編碼群
│ ├── patterns.csv # 步驟 5c 定案樣態表
│ ├── annotations.csv # 步驟 5d 定案歌×樣態
│ └── pattern-matrix.csv # 前四者的人讀寬表
├── docs/
│ ├── conventions.md # 常設工作規範(原 CLAUDE.md
│ │ # 移入 docs/ 使 subagent 不繼承)
│ ├── research-plan.md # 研究步驟規劃(本檔之姊妹篇)
│ ├── project-structure.md # 本檔
│ ├── output-validation.md # LLM 輸出的契約查核紀錄
│ ├── decision-log.md # 決策日誌:每次改定義檔的原因
│ ├── run-costs.md # 每次執行的 token 用量與費用
│ ├── reliability.md # 信度:量測方式與結果
│ ├── pilot-study.md # 先導研究的來歷與地位
│ └── methodology.md # 方法細節(全文方法節底稿;
│ # 映射分析方法須在看結果前寫定)
└── paper/
├── abstract.md # 摘要
└── 流行音樂中「女性力量」….odt # 全文
```
## 設計理由
- **`runs/` 自我完備**:每個執行目錄含定義檔快照 + 原始輸出 +
meta,讀者不需 git 考古即可稽核任一筆結果。
- **`runs/`(原始稽核資料)與 `results/`(最終表)分離**
論文只引 `results/`,其來源可回溯至 `runs/`
- **`prompts/` 檔名不帶版本號**:版本即 git 歷史,失敗的
版本不保留;論文引用的單位是 `runs/` 內隨執行保存的定義檔
快照(每個執行目錄自我完備),不需檔名可指的版本名。
- **工作儲存的資料表**`songs`(含 `performer_gender`=演唱
聲音的性別)、`chart_entries``artists``song_artists`
`codings`(定案編碼:一歌一標籤一列,`quotes` 存該標籤所據的
歌詞引述,多句以 `|` 相接)、`groups`(語意編碼群)、
`patterns`(深讀樣態)、`annotations`(歌×樣態定案矩陣)。
各定案表經 `build-db``--codings``--groups`
`--patterns``--annotations` 匯入,性別修正經
`--gender-corrections` 套用,與其餘資料同一交易,儲存不會
半建;詳見 `research-plan.md`「資料儲存與模型」。
- **Commit 判準**:能由「committed 輸入+程式」決定性再生者不
commitSQLite 工作儲存、LLM 輸入檔);源頭、捕捉、人工著作
一律以文字 commit。「可再生仍 commit」的例外有二:
`results/` 報表(引用穩定性、審稿人零門檻、撰稿期可 diff)
`data/derived/` 人讀報表(與工作儲存同一動作產出,稽核
鏈無中間空缺)。詳見 `research-plan.md`「資料儲存與模型」。
- **設定**經 pydantic-settings 統一:`.env`gitignored,範本
`tools/.env.example`)供應 `SQLALCHEMY_DATABASE_URL`
`ANTHROPIC_API_KEY`,絕不寫入 repo。
- **不設 CLAUDE.md**:實測證實 Claude Code subagent 會繼承專案
CLAUDE.md 全文(原本因此只放極簡工作規則),2026-08-18 進一步
將其移為 `docs/conventions.md`——docs/ 不會自動注入 subagent
的 context,盲判型 agent 便不會看到工作規範;主會話動手前
自行閱讀之。
+4 -15
View File
@@ -1,8 +1,7 @@
# 信度:量測方式與結果
2026-08-16 量測。對象為步驟 3a 的三份執行歸檔
`runs/3a-code/run1``run3`,與步驟 5d 的三份執行歸檔
`runs/5d-annotate/run1``run3`;數字由原始輸出直接計算。)
2026-08-16 量測。對象為步驟 3a 與步驟 5d 各三次執行的
原始輸出;數字由原始輸出直接計算。)
## 一、本研究的信度是什麼
@@ -34,11 +33,7 @@ Krippendorff 依產生資料的設計,把信度分成三型
**精密度(precision)**,而**準確度(accuracy)** 未經量測。
若儀器是確定性的,重複性無須量測;但 LLM 不是——同樣的提示、
同樣的輸入,三次會給出不同結果,因此重複性是必須報告的儀器
規格,而非慣例儀式
這一點與 LLM 標註的近期文獻一致:同一模型重複取樣量測的是
自我一致性(self-consistency),而重複執行後取多數決可提升
標註穩定度(如 Prompt Stability Scoring,arXiv:2407.02039)。
規格。
## 二、三個常用指標
@@ -106,7 +101,7 @@ Jaccard(只看「有標到」的格子,忽略雙方都沒標的)約 0.89–0.91,
比百分比一致率低而更誠實——因為 82% 的格子是雙方都判「無」,
那些一致並不費力。
## 四、隨機性的規模與三票制的作用
## 四、隨機性的規模
信度數字回答的實際問題是:**這台儀器的隨機性,大到會不會
改變結論?**
@@ -116,12 +111,6 @@ Jaccard(只看「有標到」的格子,忽略雙方都沒標的)約 0.89–0.91,
一半會成為誤收、另一半會成為漏收。三票多數決把兩票以上者
收入、一票者剔除,處理的正是這一批。
三票制並非消除隨機性,而是**把隨機性往案例原本的傾向推**。
設某個邊緣案例的符合程度為 p,單次執行以機率 p 標出,三次
多數決則以 p³+3p²(1p) 標出:p=0.9 者由 0.9 提高到 0.972,
p=0.1 者由 0.1 壓低到 0.028,而 p=0.5 者仍是 0.5——真正
模稜兩可的案例,任何票制都救不了。
以此規模判斷,結論層的三條帶狀結構(女性力量與陽剛群共現、
與脆弱群互斥、與厭女群獨立)不可能由這個量級的雜訊翻轉。
反過來說,若一致率只有 0.6,同一組結論就不能採信——信度
+20 -81
View File
@@ -11,81 +11,33 @@
**不與先導研究做比較**
全文數字一律以正式研究結果為準。先導研究僅作為假說的
內部來源,記於決策日誌,不進入論文敘事。
- **執行原則**:主會話只做討論;所有分析由 deterministic
script 執行。LLM 步驟以 Python script 呼叫 Anthropic
Messages API(個人 Console 帳號、Batch API 五折),定義
檔逐字作為 system prompt。可逐項機械比對的判斷(步驟 3
編碼、步驟 4 選群、步驟 5d 樣態標註)採「同一定義檔
獨立執行三次+多數決」,計票由確定性子命令完成。自由
生成(步驟 1 自由標註)兩次執行全數進池。步驟 5a 至
5c 為質性閱讀協定:三次獨立閱讀為分析者三角檢核、
逐首整合、樣態統整產出草稿,不適用投票與仲裁。詞彙表
不經 LLM,由詞向量嵌入+確定性分群產生。驗證結果不符
預期則修訂定義檔重跑該循環,絕不手改結果。
- **執行原則**:主會話只做討論,分析一律由確定性程序
執行。可逐項機械比對的判斷(步驟 3 編碼、步驟 4 選群、
步驟 5d 樣態標註)採「三次獨立執行+多數決」定案,
自由生成(步驟 1 自由標註)採兩次執行全數進池,步驟
5a 至 5c 定為質性閱讀協定;程序細節見 `methodology.md`
工作規範見 `conventions.md`
- **提示詞只定格式、不定語意**:研究對象是通用 LLM 以其
網路語料知識背景所做的自然編碼與閱讀,其結果本身是
批判對象。定義檔只規定任務形狀(輸入、數量範圍、輸出
格式),不給任何主題的定義、判準或範例。LLM 判斷一律
要求逐項引述歌詞原句,作為檢視偏差的依據。
- **模型**:步驟 1、3 用 `claude-sonnet-4-6`temperature=0、
thinking 關閉);步驟 4、5 用 `claude-fable-5`(兩參數
不適用,均不送出;取樣變異由多數決或整合吸收)——實測
發現 sonnet 將「Women Power」拆讀為 womenpower 的組合
語意,fable-5 讀為詞彙化概念,語意層任務因此換用
fable-5(經過見決策日誌)。
- **模型**:步驟 1、3 用 `claude-sonnet-4-6`;步驟 4、5
`claude-fable-5`——實測發現 sonnet 將「Women Power」
拆讀為 womenpower 的組合語意,fable-5 讀為詞彙化
概念,語意層任務因此換用 fable-5(經過見決策日誌)。
- **信度與效度**:三次獨立執行量測穩定性(intra-rater
reliability,可報告兩兩一致率);封閉母體全量檢查取代
抽樣防衛。可重現性定義為「程序透明+可稽核」:公開定義
檔、記錄 model ID 與執行時間、保存全部原始輸出。
## 資料儲存與模型
## 範圍性定案
- **Commit 判準**:凡能由「committed 的輸入+committed 的
程式」決定性再生者,不 commit;凡不能者——源頭資料、
外部世界的捕捉(Wikidata 快照、LLM 原始輸出)、人工
著作——一律以文字格式 commit。格式跟著層次走。
- **分層**
- 源頭:原始榜單 CSV(進 git)。
- 捕捉:Wikidata 快照 CSV、`runs/` JSONL(皆進 git);
歌詞 `.txt` 快取(版權因素 gitignored,為已知的稽核
缺口)。
- 人工:`data/manual/`(僅研究者親手寫入:編碼修正、
演唱者性別修正)。
- 工作儲存:SQLite 單檔(`tools/instance/`generated、
不進 git),SQLAlchemy 2.0 typed ORM 定義 schema
設定經 pydantic-settings`.env` 供應
`SQLALCHEMY_DATABASE_URL``ANTHROPIC_API_KEY`)。
歌詞全文入 DB(不進 git 故無版權疑慮)。
- 衍生:`build-db` 建置工作儲存的同一動作產出人讀報表
`data/derived/`,進 git),與 SQLite 同交易語意。
- 報表:論文引用的定案表進 `results/`——
`codings.csv`(步驟 3 定案編碼)、`groups.csv`
(步驟 4 定案編碼群)、`patterns.csv`(步驟 5c 定案
樣態表)、`annotations.csv`(步驟 5d 定案歌×樣態
矩陣)。衍生與報表為「可再生仍 commit」的例外,理由:
引用穩定性、審稿人零門檻、撰稿期數字變動可 diff。
- **資料模型**`songs`(含 lyrics、`performer_gender`——
演唱聲音之性別,由署名藝人之 Wikidata 性別推導後套用
`data/manual/performer-gender-corrections.csv` 手工修正)、
`chart_entries``artists``song_artists``codings`
`groups`(語意編碼群)、`patterns`(深讀樣態)、
`annotations`(歌×樣態定案矩陣)。領域不變量(恰 1000
筆榜單、每歌至少一 primary 歌手等)檢查內建於
`build-db`,違規即建置失敗。
- **歌手背景防火牆**:歌手背景資料只進人工解讀階段,
**絕不進 LLM 輸入**——LLM 任務的 user message 維持
歌詞-only 或管線中間產物-only,避免光環偏誤。
- **Pilot 歌詞沿用(私人匯入,不進發布管線)**:先導研究
捕捉檔(lyrics.json684 首,20182025)以私人腳本
匯入歌詞快取;讀者的重現路徑純粹是
`fetch-lyrics`;沿用之事實記於
`data/captures/lyrics-provenance.csv`(進 git)。
- **子命令**`pop-fem-audit-tools <cmd>`):`build-db`
`--codings``--groups``--gender-corrections`
`--patterns``--annotations`)、`cluster-keywords`
`export-llm-input``fetch-artists``fetch-lyrics`
`run-llm``--model` 於模型登錄表中擇一)、
`tally-codings``tally-groups``tally-annotations`
- **Pilot 歌詞沿用**:先導歌詞沿用之來歷與細節詳見該檔
`pilot-study.md`)。
## 分析管線(五步驟,全部完成)
@@ -96,27 +48,19 @@
階層式聚合分群 k=100,組名取 medoid;再併入研究者先驗
主題詞 `women-power`(據實揭露的儀器介入),共 101 碼。
3. **編碼(步驟 3**:以定稿詞彙表對全 883 首編碼,逐
標籤附引述,×3+多數決`tally-codings`),定案
`results/codings.csv`。「女性力量」候選集(wp 66 首、
fe 144 首、wpfe 145 首)由此浮現。
標籤附引述,×3+多數決,產出定案編碼表。「女性力量」
候選集(wp 66 首、fe 144 首、wpfe 145 首)由此浮現。
4. **語意編碼群(步驟 4**:LLM 依編碼字面語意將 101 碼
選入研究者指定的四個主題群(women-powermisogyny
masculinevulnerable),×3+多數決`tally-groups`),
定案 `results/groups.csv`wp/fe 與各編碼、各編碼群之
關聯統計(BH-FDR 校正)入論文。
masculinevulnerable),×3+多數決,產出定案分群表;
wp/fe 與各編碼、各編碼群之關聯統計(BH-FDR 校正)入
論文。
5. **女性主義問題之質性深讀(步驟 5**:對 wpfe 145 首
——5a 逐首盲讀(僅歌詞全文)×3;5b 逐首整合(收斂
註記、主清單限兩讀以上);5c 樣態統整(全體基底+
男聲/女聲/混合三個發話脈絡分組);5d 樣態標註——
以分組樣態表逐首標註「有問題」的 111 首,×3+多數決
`tally-annotations`),定案 `results/patterns.csv`
`results/annotations.csv`
定義檔命名 `prompts/<步><次步>-<task>.md`,次步以字母標示
(與論文正文的步驟編號一致);編號的所指是工序而非定義檔
(確定性的第 2 步無定義檔仍佔編號);檔名
不帶版本號——版本即 git 歷史;每次執行的定義檔快照隨
`runs/` 自我完備,token 費用逐筆記於 `docs/run-costs.md`
以分組樣態表逐首標註「有問題」的 111 首,×3+多數決
產出定案樣態表與歌×樣態定案矩陣。
## 時程與剩餘工作
@@ -125,8 +69,3 @@
污染」結論(步驟 5 素材已備)、信度說明、fe 與新自由
主義敘事之詮釋標註。
## 其他已定案事項
- 歌詞受版權保護:完整歌詞不進 git
`data/captures/lyrics/` gitignored),論文與 repo 只留
分析所引摘錄。
+2 -2
View File
@@ -3,7 +3,7 @@
# Authors:
# imacat@mail.imacat.idv.tw (imacat), 2026/7/31
# The SQLAlchemy database URL.
SQLALCHEMY_DATABASE_URL="postgresql://user:password@host/db"
# The SQLAlchemy database URI.
SQLALCHEMY_DATABASE_URI="postgresql://user:password@host/db"
# The Anthropic API key
ANTHROPIC_API_KEY=sk-ant-...
+10
View File
@@ -0,0 +1,10 @@
Change Log
==========
version 1.0.0
-------------
Released 2026/8/19
Initial release.
+2
View File
@@ -13,6 +13,8 @@ This is a collection of supporting tools for the conference paper "流行音樂
:maxdepth: 2
:caption: Contents:
changelog
Indices and tables
==================
+1 -1
View File
@@ -6,5 +6,5 @@
"""Tools for A Feminist Audit of Pop Music."""
VERSION: str = "0.0.0"
VERSION: str = "1.0.0"
"""The package version."""
+2 -2
View File
@@ -14,12 +14,12 @@ class Settings(BaseSettings):
"""The application name."""
admin_email: str = "imacat@mail.imacat.idv.tw"
"""The administrator email address."""
SQLALCHEMY_DATABASE_URL: str
SQLALCHEMY_DATABASE_URI: str
"""The SQLAlchemy database URL."""
ANTHROPIC_API_KEY: str
"""The Anthropic API key."""
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
model_config = SettingsConfigDict(env_file=".env")
"""The model configuration."""
+30 -21
View File
@@ -7,10 +7,11 @@
"""
from functools import cached_property
from pathlib import Path
from typing import Any
import sqlalchemy as sa
from sqlalchemy.engine.interfaces import DBAPICursor, DBAPIConnection
from sqlalchemy.orm import DeclarativeBase, sessionmaker, Session
from sqlalchemy.pool import ConnectionPoolEntry
from .config import Settings, get_settings
@@ -29,7 +30,7 @@ class DataSource:
:return: The database engine.
"""
settings: Settings = get_settings()
return self.__create_engine(settings.SQLALCHEMY_DATABASE_URL)
return self.__create_engine(settings.SQLALCHEMY_DATABASE_URI)
@cached_property
def __session_local(self) -> sessionmaker:
@@ -51,9 +52,6 @@ class DataSource:
def __create_engine(cls, url: str) -> sa.Engine:
"""Constructs and returns the database engine.
The foreign key enforcement is enabled on every connection
of a SQLite engine.
:param url: The SQLAlchemy database URL.
:return: The database engine.
"""
@@ -65,24 +63,10 @@ class DataSource:
poolclass=sa.StaticPool)
else:
engine = sa.create_engine(url)
if engine.url.get_backend_name() == "sqlite":
sa.event.listen(engine, "connect",
cls.__enable_sqlite_foreign_keys)
if engine.dialect.name == "sqlite":
cls.__enable_sqlite_foreign_keys(engine)
return engine
@staticmethod
def __enable_sqlite_foreign_keys(dbapi_connection: Any,
_: Any) -> None:
"""Enables the foreign key enforcement on a new connection.
:param dbapi_connection: The DBAPI connection.
:param _: The connection record (unused).
:return: None.
"""
cursor: Any = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
@staticmethod
def __resolve_sqlite_relative_url(url: str) -> str:
"""Resolves the SQLite relative URL to the instance folder.
@@ -101,6 +85,31 @@ class DataSource:
path = base / "instance" / path
return f"sqlite:///{path}"
@staticmethod
def __enable_sqlite_foreign_keys(engine: sa.Engine) -> None:
"""Turns on the foreign key enforcement of SQLite.
The ``foreign_keys`` pragma is turned on for every
connection of the engine, so that the ``ON DELETE``
actions of the schema run.
:param engine: The SQLite database engine.
:return: None.
"""
def on_connect(dbapi_connection: DBAPIConnection,
_: ConnectionPoolEntry) -> None:
"""Turns on the pragma on a new connection.
:param dbapi_connection: The DB-API connection.
:param _: The connection record (unused).
:return: None.
"""
cursor: DBAPICursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
sa.event.listen(engine, "connect", on_connect)
ds: DataSource = DataSource()
"""The data source."""
+1 -1
View File
@@ -278,7 +278,7 @@ class TestBuildDB(unittest.TestCase):
self.__annotations: Path = self.__dir / "annotations.csv"
self.__write_chart(self.CHART_CSV)
config.set_settings(config.Settings(
SQLALCHEMY_DATABASE_URL="sqlite://",
SQLALCHEMY_DATABASE_URI="sqlite://",
ANTHROPIC_API_KEY="test-key"))
self.__ds: DataSource = DataSource()
self.addCleanup(self.__ds.engine.dispose)
+1 -1
View File
@@ -36,7 +36,7 @@ class TestExportLlmInput(unittest.TestCase):
self.__dir: Path = Path(tmp.name)
self.__output: Path = self.__dir / "llm-input.jsonl"
config.set_settings(config.Settings(
SQLALCHEMY_DATABASE_URL="sqlite://",
SQLALCHEMY_DATABASE_URI="sqlite://",
ANTHROPIC_API_KEY="test-key"))
self.__ds: DataSource = DataSource()
self.addCleanup(self.__ds.engine.dispose)
+1 -1
View File
@@ -45,7 +45,7 @@ class TestFetchArtists(unittest.TestCase):
self.__snapshot: Path = \
self.__dir / "artists_wikidata.csv"
config.set_settings(config.Settings(
SQLALCHEMY_DATABASE_URL="sqlite://",
SQLALCHEMY_DATABASE_URI="sqlite://",
ANTHROPIC_API_KEY="test-key"))
self.__ds: DataSource = DataSource()
self.addCleanup(self.__ds.engine.dispose)
+1 -1
View File
@@ -44,7 +44,7 @@ class TestFetchLyrics(unittest.TestCase):
self.__provenance: Path = \
self.__dir / "lyrics-provenance.csv"
config.set_settings(config.Settings(
SQLALCHEMY_DATABASE_URL="sqlite://",
SQLALCHEMY_DATABASE_URI="sqlite://",
ANTHROPIC_API_KEY="test-key"))
self.__ds: DataSource = DataSource()
self.addCleanup(self.__ds.engine.dispose)
+1 -1
View File
@@ -27,7 +27,7 @@ class TestModels(unittest.TestCase):
def setUp(self) -> None:
"""Create the schema on an in-memory SQLite database."""
config.set_settings(config.Settings(
SQLALCHEMY_DATABASE_URL="sqlite://",
SQLALCHEMY_DATABASE_URI="sqlite://",
ANTHROPIC_API_KEY="test-key"))
self.__ds: DataSource = DataSource()
self.addCleanup(self.__ds.engine.dispose)
+3 -2
View File
@@ -188,7 +188,8 @@ class TestRequestBuilding(RunLLMTestCase):
:return: The parsed request.
"""
stdout: io.StringIO = io.StringIO()
with redirect_stdout(stdout):
stderr: io.StringIO = io.StringIO()
with redirect_stdout(stdout), redirect_stderr(stderr):
run_llm.main([
str(self.__prompt), str(self.__input),
str(self.__archive_dir), "--dry-run"] + extra_argv)
@@ -239,7 +240,7 @@ class TestMainFlow(RunLLMTestCase):
str(self.__prompt), str(self.__input),
str(self.__archive_dir)]
self.__settings: config.Settings = config.Settings(
SQLALCHEMY_DATABASE_URL="sqlite://",
SQLALCHEMY_DATABASE_URI="sqlite://",
ANTHROPIC_API_KEY="test-key")
config.set_settings(self.__settings)
+1 -1
View File
@@ -50,7 +50,7 @@ class TestTallyAnnotations(unittest.TestCase):
run_dir.mkdir()
self.__runs.append(run_dir)
config.set_settings(config.Settings(
SQLALCHEMY_DATABASE_URL="sqlite://",
SQLALCHEMY_DATABASE_URI="sqlite://",
ANTHROPIC_API_KEY="test-key"))
self.__ds: DataSource = DataSource()
self.addCleanup(self.__ds.engine.dispose)
+1 -1
View File
@@ -39,7 +39,7 @@ class TestTallyCodings(unittest.TestCase):
self.__output_csv: Path \
= self.__dir / "results" / "codings.csv"
config.set_settings(config.Settings(
SQLALCHEMY_DATABASE_URL="sqlite://",
SQLALCHEMY_DATABASE_URI="sqlite://",
ANTHROPIC_API_KEY="test-key"))
self.__ds: DataSource = DataSource()
self.addCleanup(self.__ds.engine.dispose)