vulture 在 60% 信心下報了 394 條「沒人用的方法/類別/函式」,逐條核過動態呼叫路徑後, 真正可刪的只有 63 條、約 611 行,其餘 331 條要嘛是框架約定(Flask 的 HTTP 動詞、 marshmallow 的 Meta 內部類),要嘛是被 jedi 套件端呼叫的介面實作。 工具的誤報率是 84%——這正是這一棒要人工核對而非直接照單刪的理由。
| 項目 | 值 |
|---|---|
| 掃描工具 | vulture 2.16(裝在 scratchpad,未進 pyproject.toml) |
| 掃描指令 | vulture api app domain infra common config di_containers core --min-confidence 60 |
| 原始輸出 | 1,386 行,其中 method 217/class 147/function 30(另有 attribute 127/variable 847 依卡片指示不看) |
| 掃描對象 | 394 條(method + class + function) |
| 排除 | 95 條落在第 1~3 棒要整檔刪的檔案(dead-files.txt 73 檔 + subtask_status_history 模組 19 檔) |
| 實際逐條核對 | 299 條 |
| branch | feature/FR-075(BE) |
| 日期 | 2026-09-12 |
卡片要求三個 grep,實作時發現三路不夠,補成五路——漏掉任何一路都會把活的當成死的:
| 路 | 查什麼 | 為什麼需要 |
|---|---|---|
| ① BE 直接呼叫 | 全 BE 原始碼(含 scripts/、test/、main.py)的識別字比對 |
基本款 |
| ② BE 字串引用 | 同上,但看被引號包住的出現 | DI 容器 method="xxx"、AI 儀表板申報表 |
| ③ jedi 套件端 | ~/Projects/Jedicogy/module/jedi-python-package/ 全樹 |
主專案寫的是套件 port 實作,呼叫者在套件裡 |
| ④ 前端(卡片未列,自行補) | compliance-manager-fe/src/ 的 js/vue/ts |
SocketIO 事件名由前端 emit 觸發 |
| ⑤ 同檔內字串引用(卡片未列,自行補) | 定義檔自己的其他行 | marshmallow 的 fields.Method("get_xxx")/fields.Nested("XxxSchema") 幾乎都寫在同一個檔 |
另加兩個機械判準:
@post_load、@validates_schema、@app.before_request、 @app.errorhandler、@event.listens_for 等,函式由框架呼叫,永遠沒有 caller。 (注意 vulture 報的行號是 decorator 那行、不是 def 那行,判斷時要往下找。)get/post/put/delete 等 HTTP 動詞是 Flask-RESTful 依請求方法分派的; Meta 是 marshmallow 的內部設定類。| 判定 | 條數 | 意思 |
|---|---|---|
| 確定 | 63 | 五路全 0,也不是框架回呼——可刪 |
| 活-直接呼叫 | 86 | BE 內有人直接呼叫 |
| 活-套件端 | 38 | jedi 套件呼叫的 port 實作 |
| 活-字串引用 | 2 | DI 申報表/SocketIO 註冊表用字串指名 |
| FP-框架 | 92 | 框架回呼或保留名 |
| FP-同檔字串 | 13 | 同檔 fields.Method/fields.Nested 字串引用 |
| 疑似-跨棒 | 1 | 現在活的,但第 4 棒刪完就變死的 |
| 排除-平行棒 | 4 | 檔案已在工作樹被刪(FR-091 license 搬移,尚未 commit) |
| 合計 | 299 |
誤報的組成(共 105 條 FP):Flask HTTP 動詞 45、marshmallow Meta 30、 同檔字串引用 13、各式框架 decorator 16、stdlib 覆寫 1。
活的 126 條中,套件端 38 條按套件分佈:jedi-iam 9、jedi-compliance-audit 9、 jedi-task-platform 5、jedi-survey 4,其餘八個套件各 1~2。
行數=含 decorator 與 docstring 的完整定義區塊。 全部 63 條在
test/零引用,刪除不會弄壞任何既有測試。
app/flow_engine/service/workflow_execution_service.py — 6 條 / 約 231 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 468 | 方法 | _is_all_project_jobs_completed |
43 |
| 1033 | 方法 | add_job_comment |
33 |
| 1123 | 方法 | get_ext_main_workflow_executions_and_pager |
15 |
| 1139 | 方法 | get_main_workflow_executions_and_pager |
16 |
| 1375 | 方法 | notify_control_reviewers_on_task_complete |
89 |
| 1520 | 方法 | get_ext_workflow_execution_by_uid |
35 |
api/oscal/serializers/framework/oscal_framework.py — 5 條 / 約 52 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 5 | 類別 | CatalogSchema |
11 |
| 18 | 類別 | ProfileSchema |
11 |
| 31 | 類別 | SystemSecurityPlanSchema |
11 |
| 44 | 類別 | AssessmentPlanSchema |
11 |
| 181 | 類別 | OscalFrameworkVersionImportRequestSchema |
8 |
common/util/system_asset_snapshot.py — 1 條 / 約 50 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 54 | 函式 | resolve_system_asset_fields |
50 |
api/project/serializers/project.py — 6 條 / 約 38 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 18 | 類別 | AuditSystemInputSchema |
8 |
| 38 | 類別 | DeviceInputSchema |
8 |
| 91 | 類別 | ProjectPageQueryRequest |
4 |
| 123 | 類別 | ProjectPageQueryResponse |
4 |
| 129 | 類別 | ProjectCreateRequestSchema |
11 |
| 157 | 類別 | ProjectSystemInfoQuerySchema |
3 |
app/module_frame/service/ssp_import_template_app_service.py — 3 條 / 約 32 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 1404 | 方法 | _fetch_devices_by_ids |
10 |
| 1415 | 方法 | _fetch_info_systems_by_ids |
15 |
| 1431 | 方法 | _party_by_uid |
7 |
domain/oscal/service/reconciliation/_normalizers.py — 3 條 / 約 31 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 53 | 函式 | parse_device_lookup_label |
12 |
| 67 | 函式 | parse_info_system_lookup_label |
11 |
| 80 | 函式 | _normalize_control_id |
8 |
app/module_frame/service/module_frame_service.py — 2 條 / 約 22 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 56 | 方法 | _create_template_ssp |
15 |
| 317 | 方法 | update_assessment_workflow_job_link |
7 |
api/oscal/serializers/ssp/ssp_docx_import.py — 2 條 / 約 18 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 5 | 類別 | SspDocxImportParseRequestSchema |
6 |
| 114 | 類別 | ObjectiveDiffSchema |
12 |
common/exception_error/common_exception.py — 4 條 / 約 16 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 13 | 類別 | ValueMismatchError |
4 |
| 19 | 類別 | DataIsRevokeError |
4 |
| 37 | 類別 | AuthError |
4 |
| 43 | 類別 | MailError |
4 |
domain/module_frame/service/module_frame_domain_service.py — 4 條 / 約 16 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 38 | 方法 | get_module_frames_by_ids |
3 |
| 76 | 方法 | delete_module_frames |
3 |
| 87 | 方法 | verify_module_frame_exists_by_id |
6 |
| 94 | 方法 | verify_module_frame_exists_by_name |
4 |
api/system_config/serializers/system_config.py — 3 條 / 約 12 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 17 | 類別 | SystemConfigGroupResponse |
3 |
| 21 | 類別 | SystemConfigValueResponse |
2 |
| 24 | 類別 | SmtpConfigRequest |
7 |
api/oscal/serializers/ssp/ssp_excel_import.py — 1 條 / 約 11 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 5 | 類別 | SspExcelImportParseRequestSchema |
11 |
domain/feedback/service/feedback_issue_domain_service.py — 3 條 / 約 10 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 33 | 方法 | get_feedback_issue |
3 |
| 37 | 方法 | get_feedback_issue_by_id |
2 |
| 52 | 方法 | verify_feedback_issue_is_exist |
5 |
api/project/serializers/job_execution_device.py — 1 條 / 約 9 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 61 | 類別 | JobExecutionDeviceViewQueryResponse |
9 |
api/flow_engine/serializers/flow_engine/workflow_execution.py — 3 條 / 約 8 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 28 | 類別 | WorkflowExecutionUpdateRequest |
4 |
| 33 | 類別 | WorkflowExecutionPageQueryRequest |
2 |
| 67 | 類別 | WorkflowExecutionPageQueryResponse |
2 |
common/authz/license.py — 1 條 / 約 8 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 200 | 函式 | resolve_license_tenant_id |
8 |
api/flow_engine/serializers/flow_engine/element_variable.py — 1 條 / 約 7 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 4 | 類別 | ElementVariableRequest |
7 |
app/associations/service/project_system_characteristic_mapping_service.py — 1 條 / 約 7 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 63 | 方法 | get_info_by_project_uid |
7 |
domain/flow_engine/service/stage_completion_registry.py — 3 條 / 約 6 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 179 | 方法 | has_handler |
2 |
| 182 | 方法 | has_precondition |
2 |
| 185 | 方法 | has_rollback_handler |
2 |
infra/system_config/runtime_config.py — 1 條 / 約 6 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 77 | 函式 | get_runtime_value |
6 |
domain/project_summary_report/service/project_summary_report_history_domain_service.py — 2 條 / 約 5 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 17 | 方法 | get_project_summary_report_history |
3 |
| 21 | 方法 | get_project_summary_report_history_by_id |
2 |
api/cloud_integration/serializers/google_drive_integration.py — 1 條 / 約 3 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 22 | 類別 | GoogleDriveCallbackQuerySchema |
3 |
app/module_frame/service/module_frame_import_service.py — 1 條 / 約 3 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 262 | 方法 | module_frame_dto_to_ap_inputs |
3 |
app/oscal/service/excel_parser/types.py — 1 條 / 約 2 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 90 | 方法 | has_blocking_errors |
2 |
app/oscal/service/ssp_resources_context_service.py — 1 條 / 約 2 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 116 | 方法 | resolve_sys_impl_main_id |
2 |
config/config_util.py — 1 條 / 約 2 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 18 | 方法 | get_current_config |
2 |
domain/flow_engine/service/ext_workflow_execution_domain_service.py — 1 條 / 約 2 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 28 | 方法 | get_workflow_execution_by_ids |
2 |
domain/oscal/adapter/adapter_registry.py — 1 條 / 約 2 行
| 行 | 類型 | 成員 | 行數 |
|---|---|---|---|
| 23 | 方法 | known_frameworks |
2 |
① notify_control_reviewers_on_task_complete(89 行,最大一塊) 檔案裡自己寫明「目前無呼叫點(CM-1504)」,且說「刻意保留不刪」——理由是 FR-038 2B 要重接「任務完成通知」時會用到,而且它已登記在系統設計文件產生器 docs/system-design/scripts/generate_business_logic_v2.py:1992 的功能表裡。 這條要決策者裁:是照原註解繼續留(那就在修正卡標明「刻意保留」,不要下次又被掃出來), 還是判定 2B 會另寫、現在就刪。刪的話文件產生器那行要同步移除。
② common/util/system_asset_snapshot.py 的 resolve_system_asset_fields(50 行,單檔最大) 整個檔就這一支公開函式,零呼叫。刪掉這支等於整檔可刪,屬「第 1 棒沒抓到的漏網之魚」 ——建議併進第 1 棒的整檔刪除清單,不要拆成成員刪除。
③ common/authz/license.py 的 resolve_license_tenant_id(8 行) docstring 自稱是「整條讀照鏈的統一入口⋯⋯共用同一份解析,不各自沿 parent 鏈自兜第二套」, 但實際上零 caller,而且沒有在 common/authz/__init__.py export。 真正在用的是同檔 LicenseEnforcer 內部的 self._license_tenant_resolver.resolve()。 這是「說一套做一套」的文件陷阱——後人讀 docstring 會以為該走這支。 要嘛刪掉、要嘛把 docstring 改成實話。注意 license 模組正被 FR-091 搬進套件, 這條要等那邊落定再動(工作樹已有 5 個 license 檔被刪但未 commit)。
on_reload(app/notification/handler/notification_socketio_handler.py:43)——現在活的,第 4 棒之後會變死的。 它是 SocketIO 事件回呼,前端 emit reload 才會觸發。全前端只有一處在 emit, 就在 views/_archived/projects/oscal/ProjectTaskManage.vue:174——那正是第 4 棒(CM-1707) 要刪的目錄。另外兩處 socket.on('reload') 只是「監聽」不是「觸發」,不算 caller。 第 4 棒做完後這支就成孤兒,屆時可一併清掉。
四條 license 相關成員暫時排除。 infra/license/notifier.py、tenant_directory_adapter.py、tenant_admin_directory_adapter.py 在掃描當下已被 git rm 但尚未 commit(FR-091 把 license 搬進套件)。 這四條不列入本次盤點,等那邊 commit 後若還在再說。
63 條分佈在 28 個檔,按模組切成四張修正卡,彼此無相依可平行:
| 卡 | 範圍 | 條數 | 行數 | 檔數 | 備註 |
|---|---|---|---|---|---|
| A | flow_engine | 14 | 254 | 5 | 含最大的 notify_control_reviewers_on_task_complete,要先裁那條的去留 |
| B | OSCAL | 14 | 118 | 7 | CatalogSchema 等四支是 v1 殘骸,與第 1 棒同性質;_normalizers.py 三支 parser 未接線 |
| C | module_frame + 其他 app/domain | 16 | 95 | 7 | domain service 的 verify_*/get_*_by_ids 系列,純孤兒 |
| D | api serializers + common/infra/config | 19 | 144 | 9 | 未掛 route 的 request/response schema;system_asset_snapshot.py 整檔可刪 |
每張卡的共通紀律:
system_asset_snapshot.py、 element_variable.py 可能會),空掉就走整檔刪並更新 dead-files.txt。docs/ 被提及(多為 docs/api/*/api-spec.md 的 schema 描述與 generate_docx.py 的產生器),那些是描述性引用不是執行性引用,不影響刪除, 但刪完要順手更新文件,免得文件描述不存在的東西。test/ 對這 63 條零引用,刪除後跑既有測試即可,不必補新測試。首腦初掃的四個熱點,核完後三個是誤報。
api/project/serializers/audit_round.py 的「18 個未使用 class」全部是 marshmallow 的 class Meta 內部類,一條都不能刪。api/flow_control/serializers/job.py 的 15 條中,get_id 六條是 fields.Method("get_id") 同檔字串引用,put 是 HTTP 動詞。infra/*/adapters.py 三檔(survey/detection_tools/evidence_classification) 如卡片預判,全是套件 port 實作,開 jedi_survey/domain/ports.py 核過,零可刪。workflow_execution_service.py——10 條裡 6 條確定死、4 條活 (update_job_comment/add_workflow_execution 是 jedi-flow-engine 的覆寫; notify_users_batch_assigned 被 jedi-task-platform 注入呼叫; get_sub_workflow_executions 被 AI 儀表板申報表用字串指名)。core/plugins/*.py 的 27 條全是活的。 那是 FR-069 插件契約的 port 實作,CM-1700 剛收進去,由各套件端呼叫。 vulture 對這類「宿主實作、套件呼叫」的結構完全無能為力,是本次最大的誤報來源之一。
def 那行。用行號去比對定義時要往下找。--min-confidence 60 的誤報率 84%;卡片提到 80% 信心下只剩 16 import + 10 variable, 等於把所有 method/class 都濾掉了——信心值不是可靠的篩選軸,該靠結構判準(框架名、 decorator、port 介面)分流。.venv,全樹 grep 會撈到第三方套件的同名符號。 核對時要把 /.venv/ 與 site-packages 的命中排掉。docs/ 目錄 1.6 GB(含 HTML 站產物),全掃會跑很久。只掃 docs/system-design/scripts、docs/api、docs/spec-site/current、docs/claude 即足夠。