Using AI at work

Check an AI summary sentence by sentence against the source

Break an AI summary into individual claims, link each claim to specific source sentences, and mark unsupported or overstated statements before reuse. A small synthetic document shows why fluent summaries still need evidence checks.

Show contents

Who this is forThis guide is for people who use AI to summarize reports, notes, or research material and want a traceable sentence-by-sentence review.

What you need
  • Python 3.12 and a terminal command that starts that version.
  • A text editor that can save UTF-8 text and CSV files.
  • A working folder where the script can create a new folder beneath outputs.
  • Only the Python standard library is required: csv and pathlib.

01Treat every summary sentence as a claim

An AI summary can be grammatically clean while still changing a cause, dropping a limitation, or adding a conclusion that the source never made. A useful review therefore asks a simple question for every summary sentence: which exact part of the source supports this statement?

This tutorial uses three verdicts. SUPPORTED means the source directly supports the sentence. PARTIAL means only part of the sentence is supported or important qualification is missing. UNSUPPORTED means the source does not support the claim or contradicts it.

02Create a synthetic source document and AI summary

The following source and summary are synthetic and were written specifically for this article. Save the first block as source.txt and the second as ai_summary.txt.

text
S1|The pilot processed 20 test cases.
S2|Eighteen cases completed successfully.
S3|Two cases stopped during input validation before the solver started.
S4|Among the 18 completed cases, the median runtime was 42 seconds.
S5|The test was performed on one workstation under the same software configuration.
S6|No production deployment or long-term reliability test was performed.
text
A1|The pilot processed 20 cases, and 18 completed successfully.
A2|Two cases failed because the solver crashed.
A3|The median runtime for completed cases was 42 seconds.
A4|The results show that the automation is reliable enough for production use.

The source contains 6 sentences. The AI summary contains 4 sentences. The values are intentionally small enough to verify without software.

03Compare each summary sentence with the source

A1 is supported by S1 and S2. A2 is contradicted by S3: the source says the two cases stopped during input validation before the solver started, not because of solver crashes. A3 is directly supported by S4. A4 goes beyond the evidence because S6 explicitly says no production deployment or long-term reliability test was performed.

Summary IDVerdictEvidenceReason
A1SUPPORTEDS1;S2The case count and successful completions match the source.
A2UNSUPPORTEDS3The source attributes the stops to input validation before solver start.
A3SUPPORTEDS4The 42-second median is stated for the 18 completed cases.
A4UNSUPPORTEDS6Production readiness was not tested.

The expected review therefore contains 2 supported sentences and 2 unsupported sentences. None of the four synthetic sentences requires the PARTIAL label.

04Record the review in a traceable CSV

Save the following as summary_review.csv. The evidence column contains source sentence IDs rather than copied paragraphs, which keeps the review compact while preserving traceability.

csv
summary_id,verdict,evidence,reason
A1,SUPPORTED,S1;S2,The case count and successful completions match the source.
A2,UNSUPPORTED,S3,The source attributes the stops to input validation before solver start.
A3,SUPPORTED,S4,The 42-second median is stated for the 18 completed cases.
A4,UNSUPPORTED,S6,Production readiness was not tested.

This review remains a human judgment. The next script does not decide whether a claim is true. It checks whether every AI summary sentence received a valid verdict and whether every cited source ID actually exists.

05Check that every sentence was reviewed

Save the following script as check_summary_review.py. It reads the source, summary, and completed review CSV, verifies IDs and verdicts, and writes a new validated report without modifying any input file.

python
import csv
from pathlib import Path

SOURCE = Path("source.txt")
SUMMARY = Path("ai_summary.txt")
REVIEW = Path("summary_review.csv")
OUTPUT_DIR = Path("outputs") / "summary_source_check"
OUTPUT = OUTPUT_DIR / "validated_review.csv"
VALID_VERDICTS = {"SUPPORTED", "PARTIAL", "UNSUPPORTED"}


def read_id_text(path: Path) -> dict[str, str]:
    result = {}
    with path.open("r", encoding="utf-8") as stream:
        for line_number, line in enumerate(stream, start=1):
            line = line.rstrip("\n")
            if not line:
                continue
            item_id, separator, text = line.partition("|")
            if not separator or not item_id or not text:
                raise ValueError(f"Invalid line {line_number} in {path}.")
            if item_id in result:
                raise ValueError(f"Duplicate ID {item_id} in {path}.")
            result[item_id] = text
    return result


def main() -> None:
    for path in (SOURCE, SUMMARY, REVIEW):
        if not path.is_file():
            raise FileNotFoundError(f"Missing input: {path}")
    if OUTPUT_DIR.exists():
        raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")

    source = read_id_text(SOURCE)
    summary = read_id_text(SUMMARY)

    reviewed = {}
    with REVIEW.open("r", encoding="utf-8-sig", newline="") as stream:
        reader = csv.DictReader(stream)
        required = {"summary_id", "verdict", "evidence", "reason"}
        if reader.fieldnames is None or not required.issubset(reader.fieldnames):
            raise ValueError("Review CSV is missing a required column.")

        for row in reader:
            summary_id = row["summary_id"].strip()
            verdict = row["verdict"].strip()
            evidence_ids = [x.strip() for x in row["evidence"].split(";") if x.strip()]

            if summary_id not in summary:
                raise ValueError(f"Unknown summary ID: {summary_id}")
            if summary_id in reviewed:
                raise ValueError(f"Duplicate review: {summary_id}")
            if verdict not in VALID_VERDICTS:
                raise ValueError(f"Invalid verdict for {summary_id}: {verdict}")
            if not evidence_ids:
                raise ValueError(f"No evidence listed for {summary_id}.")
            unknown = [item for item in evidence_ids if item not in source]
            if unknown:
                raise ValueError(f"Unknown source IDs for {summary_id}: {unknown}")
            if not row["reason"].strip():
                raise ValueError(f"Missing reason for {summary_id}.")

            reviewed[summary_id] = {
                "summary_id": summary_id,
                "summary_text": summary[summary_id],
                "verdict": verdict,
                "evidence": ";".join(evidence_ids),
                "reason": row["reason"].strip(),
            }

    missing = [item for item in summary if item not in reviewed]
    if missing:
        raise ValueError(f"Summary sentences not reviewed: {missing}")

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()
    fields = ["summary_id", "summary_text", "verdict", "evidence", "reason"]
    with OUTPUT.open("x", encoding="utf-8", newline="") as stream:
        writer = csv.DictWriter(stream, fieldnames=fields)
        writer.writeheader()
        for summary_id in summary:
            writer.writerow(reviewed[summary_id])

    counts = {verdict: 0 for verdict in VALID_VERDICTS}
    for row in reviewed.values():
        counts[row["verdict"]] += 1

    print(f"Summary sentences checked: {len(summary)}.")
    print(
        f"Supported: {counts['SUPPORTED']}; partial: {counts['PARTIAL']}; "
        f"unsupported: {counts['UNSUPPORTED']}."
    )
    print(f"Output: {OUTPUT.as_posix()}")


if __name__ == "__main__":
    main()

06Check the expected result

For the synthetic review above, all 4 summary sentences have a valid verdict and valid source references. The expected console output is shown below. It was derived by hand and is not an execution log.

text
Summary sentences checked: 4.
Supported: 2; partial: 0; unsupported: 2.
Output: outputs/summary_source_check/validated_review.csv
  • Confirm that every A-ID appears exactly once in the review.
  • Confirm that every cited S-ID exists in source.txt.
  • Confirm that unsupported claims are not silently rewritten as supported claims.
  • Keep the original AI summary unchanged so the review remains auditable.
  • Run the script again without changing OUTPUT_DIR. It should stop with FileExistsError instead of overwriting the previous validation.

07Recognize common errors and limits

ProblemWhat to do
One summary sentence contains two separate claimsSplit the sentence into claim-sized units or review each claim separately.
Evidence only supports part of a sentenceUse PARTIAL and write the missing qualification in the reason column.
The source is silent on a conclusionMark it UNSUPPORTED instead of treating a plausible inference as a sourced fact.
A source sentence is cited but does not actually support the claimFix the human review; valid IDs alone do not prove semantic support.
Different sources disagreeRecord the disagreement explicitly instead of forcing a single unsupported conclusion.

The Python script validates review completeness and reference IDs, not truth. Semantic support still requires human reading or a separate evidence-evaluation process. A wrong human verdict can pass the structural checker.

For long reports, use page numbers, paragraph IDs, table numbers, or quoted source locations instead of informal references. Also distinguish direct source statements from calculations, interpretations, and external knowledge. A summary should not quietly turn one category into another.

Execution and verification record

2026-09-20 · hand-checked example · target: Python 3.12 · standard library: csv, pathlib · no execution

  • Manually checked 4 synthetic summary sentences against 6 synthetic source sentences.
  • Classified A1 and A3 as SUPPORTED and A2 and A4 as UNSUPPORTED.
  • Confirmed that A2 conflicts with S3 because the source says input validation stopped the cases before the solver started.
  • Confirmed that A4 goes beyond the source because S6 says production deployment and long-term reliability were not tested.
  • Manually calculated the expected verdict counts as 2 supported, 0 partial, and 2 unsupported.
  • Inspected the script for missing reviews, duplicate IDs, invalid verdicts, unknown source IDs, output collision protection, and report generation.
Verification limits
  • The code was not executed by the author of this response; no validation CSV was created.
  • The structural checker does not determine whether a cited source semantically supports a claim.
  • Only the synthetic source and summary shown here were reviewed; long documents, tables, figures, and conflicting sources were not tested.
  • The official documentation URLs were provided from known documentation locations but were not checked live.

Site-wide writing and verification principles

References

The explanations and examples were written for this site. See the official sources below for the related behavior and concepts.