citefind: a note that cites three works is three references

Measured against the citations an LLM had already extracted from 40 books of
the same corpus — 19,340 of them over 3,424 chunks — and improved where the
comparison showed a real gap rather than a difference of task.

On pages of notes, where most citations live: year agreement with the model
44% -> 56%, title 18% -> 23%, author 16% -> 24%.

What changed:

- A note listing several works, semicolon-separated, becomes one record per
  work. The note number is kept on all of them.
- A full stop is not a separator. It parts the author from the title in every
  bibliography entry there is.
- A page of body text with notes at its foot no longer reads the body as the
  first note.
- A run carrying a page reference or a semicolon is not reported as a title.
- Six sentences with no imprint, no page and no identifier is a remark about
  the argument, not a reference.
- A translator standing where the journal goes is a credit, not a container,
  and `(London, 1973)` is a place, not a publisher.

The model is not ground truth and the comparison is not a score: on several
sampled disagreements it named the journal where the article was wanted, or
the collected works instead of the essay.
This commit is contained in:
funkpower
2026-09-20 11:10:42 -04:00
parent c071f84829
commit f3e70b9bf7
4 changed files with 243 additions and 13 deletions
+41
View File
@@ -171,6 +171,47 @@ fn a_reply_that_is_not_json_is_refused_rather_than_half_applied() {
assert_eq!(found, before);
}
#[test]
fn a_note_that_cites_two_works_gives_two_records() {
let found = read(
"12. Theodor W. Adorno, *Negative Dialectics*, trans. E. B. Ashton (London, 1973), 365; \
Jeffrey C. Alexander, \"On the Social Construction of Moral Universals,\" *Cultural Trauma* 5 (2002), 196.\n\
13. Smith, John. Thinking, Fast and Slow. New York: Farrar, Straus and Giroux, 2011.",
);
assert_eq!(found.len(), 3, "{found:#?}");
// Both halves of note 12 are note 12.
assert_eq!(
found
.iter()
.map(|one| one.marker.as_deref())
.collect::<Vec<_>>(),
[Some("12"), Some("12"), Some("13")]
);
assert_eq!(found[0].title.as_deref(), Some("Negative Dialectics"));
assert_eq!(
found[1].title.as_deref(),
Some("On the Social Construction of Moral Universals")
);
// The full stop between an author and a title is not a separator, or
// every bibliography entry ever written would come apart at it.
assert_eq!(found[2].title.as_deref(), Some("Thinking, Fast and Slow"));
}
#[test]
fn the_body_of_a_page_is_not_the_first_of_the_notes_at_its_foot() {
let page = format!(
"{}\n\n1. John MacGowan, *The Imperial History of China* (London: Curzon, 1897), p. 154.\n\
2. Johanna Bockman, *Markets in the Name of Socialism* (Stanford, CA: Stanford University Press, 2011), 40.",
"The obligation ran both ways, and contemporaries said so plainly whenever they were asked. ".repeat(8)
);
let found = read(&page);
assert_eq!(found.len(), 2, "{found:#?}");
assert!(
found.iter().all(|one| !one.text.contains("obligation")),
"{found:#?}"
);
}
#[test]
fn nothing_citation_shaped_gives_nothing() {
assert!(read("Bibliography\n\nSee below.\n\n233\n").is_empty());