跳到正文
DEV Community· bao han·· 2 小時前AI 評分39

如何按參考資料分組候選項目並保留證據鏈

Group reference candidates without deleting the evidence trail

AI 導讀

RigorMed 團隊示範一個 Python 程式,按修剪後完全相同的 source ID 分組候選記錄,同時保留所有原始行,並列出缺少 source ID 的記錄。分組不代表文獻資料已核實或可以合併;合併前應回查原始記錄及出版者或資料庫來源,並記錄決定與不確定之處。

正文

A reference-cleaning program can finish successfully while an important question remains unresolved: should two imported records actually be merged? Build the first pass to expose candidate groups and retain every original row. Make deletion a separate, reviewed decision.

We are the RigorMed team. The small program below was run locally with Python's standard library. All three input rows are self-written synthetic teaching metadata. The identifiers beginning with teaching- are invented labels, not real DOIs, publications or a bibliographic search result. No model, research Skill or production research task was executed.

Declare the narrow input contract

Our CSV has a unique citation_key, an optional source_id and an uninterpreted version_note. A citation key locates a record in this import. The source ID is an assertion supplied by the person preparing the file; the script does not verify it.

The first two rows declare the same source ID but have different citation keys and notes. The third row has no source ID. We want a candidate group for the first pair and an explicit missing-identifier list for the third. We want all three rows preserved.

The function deliberately compares trimmed source IDs exactly. It does not resolve URLs, normalize DOI syntax, match titles, infer authors, identify versions, or conclude that a preprint and a published article are interchangeable. Do not silently turn this limited grouping rule into a universal deduplicator.

Run the complete example

import csv
import io
import json

FIELDS = ["citation_key", "source_id", "version_note"]


def inspect_import(text):
    reader = csv.DictReader(io.StringIO(text))
    if reader.fieldnames != FIELDS:
        raise ValueError("Unexpected columns")
    rows = list(reader)
    keys, groups, unkeyed = set(), {}, []
    for row in rows:
        if None in row or any(value is None for value in row.values()):
            raise ValueError("Malformed row")
        key = row["citation_key"].strip()
        if not key or key in keys:
            raise ValueError("Missing or repeated citation key")
        keys.add(key)
        source = row["source_id"].strip()
        if source:
            groups.setdefault(source, []).append(key)
        else:
            unkeyed.append(key)
    return {
        "retained_rows": rows,
        "candidate_groups": {k: v for k, v in groups.items() if len(v) > 1},
        "missing_source_id": unkeyed,
    }


sample = """citation_key,source_id,version_note
ref_a,teaching-R1,version not checked
ref_b,teaching-R1,imported from another library
ref_c,,version not checked
"""
report = inspect_import(sample)
if len(report["retained_rows"]) != 3:
    raise RuntimeError("A record was lost")
if report["candidate_groups"] != {"teaching-R1": ["ref_a", "ref_b"]}:
    raise RuntimeError("Unexpected grouping")
if report["missing_source_id"] != ["ref_c"]:
    raise RuntimeError("A missing identifier was concealed")
print(json.dumps(report, indent=2))

The actual local output was:

{
  "retained_rows": [
    {
      "citation_key": "ref_a",
      "source_id": "teaching-R1",
      "version_note": "version not checked"
    },
    {
      "citation_key": "ref_b",
      "source_id": "teaching-R1",
      "version_note": "imported from another library"
    },
    {
      "citation_key": "ref_c",
      "source_id": "",
      "version_note": "version not checked"
    }
  ],
  "candidate_groups": {
    "teaching-R1": [
      "ref_a",
      "ref_b"
    ]
  },
  "missing_source_id": [
    "ref_c"
  ]
}

The retained_rows array still has three records. candidate_groups lists ref_a and ref_b under the declared teaching-R1 label. missing_source_id contains ref_c. The checks at the bottom make these three properties visible; they do not test bibliographic truth.

A blank identifier is not a shared identifier. Putting every blank value into one duplicate group would create an unsupported merge suggestion. A repeated citation key is also a different input problem: it prevents a reviewer from reliably referring to individual records, so this example rejects it instead of guessing which row to keep.

Review the candidate, then preserve the decision

Before merging a real candidate group, return to the original records and the publisher or repository source. Compare the title, authors, identifier and version relationship. Keep the old keys and any manuscript locations that refer to them. Record which fields were retained, which were uncertain, and why a merge was accepted or declined.

Even a correct bibliographic merge does not prove that the cited paper supports a particular sentence. The claim-to-source check needs the actual passage and its limitations. A clean import report is an organizational artifact, not a completed literature review or submission approval.

For an AI-assisted handoff, provide authorized reference metadata and the candidate report, specify that uncertain fields must stay unresolved, and request a comparison table. Do not ask a text editor to invent a missing DOI or upgrade an unchecked version label to verified.

RigorMed's browser research entry can receive a concrete task and authorized text materials: research-material handoff. Model use follows displayed credit terms and researcher verification. This teaching function is not presented as a shipped RigorMed validator or an installed third-party Skill.

Sources checked on 2026-11-05: Python csv documentation, for DictReader's parsing contract, and Python json documentation, for serialization. The input contract, grouping rule, examples and interpretation are our own teaching material.

來源:DEV Community · dev.to