Skip to content

Commit e429b7e

Browse files
IMNMVclaude
andcommitted
Field fixes from building the demo fixture (0.8.1)
From running the full audit stack against a realistic 20-page synthetic manuscript with a planted-defect ledger: - reconcile_values: bibliography lines (DOI/arXiv or APA year+page-range shape) are quarantined as reference_meta instead of flooding the registry with volume/page/DOI fragments (86 such values on the test manuscript); the structured extractor own [Table k, row j] markers are stripped before tokenizing so row indices are no longer treated as document values - crossref_get: base R reports HTTP status in a warning, so 404s were invisible to the error-message check and got retried for ~7s each; warnings are now sniffed and 404s return immediately (0.3s) - check_arxiv_impl: retries with a longer timeout (arXiv API is slow on cold requests) and reports lookup failure explicitly instead of returning nothing CI: reconcile quarantine/marker-stripping functional test. R CMD check: Status OK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent f47744e commit e429b7e

4 files changed

Lines changed: 73 additions & 16 deletions

File tree

‎.github/scripts/checks.R‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,24 @@ r <- tryCatch({
229229
}, error = function(e) conditionMessage(e))
230230
if (isTRUE(r)) pass("reconcile_values end-to-end: only planted value unmatched") else fail("reconcile e2e:", r)
231231

232+
# --- 9b. reconcile: reference-line quarantine + extractor-marker stripping ---
233+
r <- tryCatch({
234+
doc <- tempfile(fileext = ".txt")
235+
writeLines(c(
236+
"The effect was significant, d = 0.53.",
237+
"[Table 1, row 5] condition | 44.2 | 78.6",
238+
"Leroy, S. (2009). Why is it so hard? Journal, 109(2), 168-181. https://doi.org/10.1016/j.obhdp.2009.04.002"
239+
), doc)
240+
src <- tempfile(fileext = ".txt")
241+
writeLines(c("d 0.5337", "diff 44.1678", "sd 78.6487"), src)
242+
invisible(capture.output(env$reconcile_values(doc, src)))
243+
reg <- get("values_registry", envir = .GlobalEnv)
244+
sum(reg$status == "reference_meta") >= 4 && # 109, 2, 168, 181, DOI bits
245+
!any(reg$raw == "5" & reg$status == "unmatched") && # row marker stripped
246+
all(reg$status[reg$value %in% c(0.53, 44.2, 78.6)] == "matched")
247+
}, error = function(e) conditionMessage(e))
248+
if (isTRUE(r)) pass("reconcile: reference lines quarantined, cell markers stripped") else fail("reconcile v2:", r)
249+
232250
# --- 10. docx extractor: tables row-wise, headings marked (needs officer) ---
233251
if (requireNamespace("officer", quietly = TRUE)) {
234252
r <- tryCatch({

‎DESCRIPTION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: ClaudeR
22
Title: R Integration for Claude AI
3-
Version: 0.8.0
3+
Version: 0.8.1
44
Authors@R: person("Nykko", "Vitali", email = "nykvt@icloud.com", role = c("aut", "cre"))
55
Description: Connects RStudio with Claude AI to enable interactive coding sessions.
66
License: MIT + file LICENSE

‎R/citations.R‎

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,20 @@ crossref_get <- function(url, simplify = FALSE) {
8282
waits <- c(0, 1.5, 5)
8383
for (k in seq_along(waits)) {
8484
if (waits[k] > 0) Sys.sleep(waits[k])
85-
res <- tryCatch(jsonlite::fromJSON(url, simplifyVector = simplify),
86-
error = function(e) e)
85+
# base R surfaces the HTTP status in a *warning* ("HTTP status was '404
86+
# Not Found'") while the error message only says "cannot open URL", so
87+
# both conditions must be sniffed to tell a real 404 from rate limiting.
88+
saw_404 <- FALSE
89+
res <- withCallingHandlers(
90+
tryCatch(jsonlite::fromJSON(url, simplifyVector = simplify),
91+
error = function(e) e),
92+
warning = function(w) {
93+
if (grepl("404", conditionMessage(w))) saw_404 <<- TRUE
94+
invokeRestart("muffleWarning")
95+
}
96+
)
8797
if (!inherits(res, "error")) return(res)
88-
# 404 is a real answer (no such DOI/filter result), not rate limiting
89-
if (grepl("404", conditionMessage(res))) return(NULL)
98+
if (saw_404 || grepl("404", conditionMessage(res))) return(NULL)
9099
}
91100
NULL
92101
}
@@ -142,12 +151,24 @@ match_reference_impl <- function(ref_text) {
142151
# published (journal/proceedings) version so preprint citations of published
143152
# work get flagged. Atom parsed with regex to avoid an xml2 hard dependency.
144153
check_arxiv_impl <- function(arxiv_id) {
145-
res <- tryCatch({
146-
con <- url(paste0("https://export.arxiv.org/api/query?id_list=", arxiv_id))
147-
on.exit(try(close(con), silent = TRUE), add = TRUE)
148-
paste(readLines(con, warn = FALSE), collapse = "\n")
149-
}, error = function(e) NULL)
150-
if (is.null(res)) return(NULL)
154+
# export.arxiv.org is slow enough to trip a 10s timeout on cold requests;
155+
# retry with a longer allowance before giving up.
156+
res <- NULL
157+
for (k in 1:3) {
158+
if (k > 1) Sys.sleep(2)
159+
old_t <- options(timeout = if (k == 1) 10 else 25)
160+
res <- tryCatch({
161+
con <- url(paste0("https://export.arxiv.org/api/query?id_list=", arxiv_id))
162+
txt <- suppressWarnings(paste(readLines(con, warn = FALSE), collapse = "\n"))
163+
try(close(con), silent = TRUE)
164+
txt
165+
}, error = function(e) NULL)
166+
options(old_t)
167+
if (!is.null(res) && nzchar(res)) break
168+
}
169+
if (is.null(res) || !nzchar(res)) {
170+
return(sprintf("arXiv:%s -- lookup failed (arXiv API unreachable); verify manually.", arxiv_id))
171+
}
151172

152173
title <- regmatches(res, regexpr("<title>[^<]+</title>", res))
153174
# First <title> is the feed's own; the entry title is the second match

‎R/reconcile.R‎

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,25 @@ extract_numbers_from_line <- function(line) {
5858
do.call(rbind, out)
5959
}
6060

61+
# A bibliography line: contains a DOI/arXiv id, or looks like an APA entry
62+
# (a parenthesized year followed later by a page range). Values on such lines
63+
# are citation metadata (volumes, issues, pages, DOI fragments), not results.
64+
is_reference_line <- function(line) {
65+
grepl("doi\\.org/|\\barXiv:", line, ignore.case = TRUE) ||
66+
grepl("\\((17|18|19|20)\\d\\d[a-z]?\\)\\..*\\d+-\\d+\\.?$", line, perl = TRUE)
67+
}
68+
6169
# Extract all numbers from a character vector, with line numbers and context.
6270
extract_numbers_impl <- function(lines, label = "text") {
6371
res <- lapply(seq_along(lines), function(i) {
64-
d <- extract_numbers_from_line(lines[i])
72+
# Strip the structured-extractor cell markers ("[Table 2, row 5] ...")
73+
# before tokenizing: the table and row indices are our own metadata, not
74+
# values the document states.
75+
clean <- sub("^\\[Table \\d+, (row \\d+|header)\\] ", "", lines[i])
76+
d <- extract_numbers_from_line(clean)
6577
if (nrow(d) == 0) return(NULL)
6678
d$line <- i
79+
d$is_reference <- is_reference_line(lines[i])
6780
ctx <- trimws(lines[i])
6881
if (nchar(ctx) > 160) ctx <- paste0(substr(ctx, 1, 157), "...")
6982
d$context <- ctx
@@ -74,6 +87,7 @@ extract_numbers_impl <- function(lines, label = "text") {
7487
return(data.frame(raw = character(0), value = numeric(0), ulp = numeric(0),
7588
is_threshold = logical(0), threshold_dir = character(0),
7689
is_percent = logical(0), line = integer(0),
90+
is_reference = logical(0),
7791
context = character(0), source = character(0),
7892
stringsAsFactors = FALSE))
7993
}
@@ -146,7 +160,11 @@ reconcile_values <- function(document, sources, ignore_years = TRUE,
146160
for (i in seq_len(nrow(doc_nums))) {
147161
v <- doc_nums$value[i]
148162
u <- doc_nums$ulp[i]
149-
if (ignore_years && !doc_nums$is_percent[i] && !doc_nums$is_threshold[i] &&
163+
if (isTRUE(doc_nums$is_reference[i])) {
164+
# Volumes, issues, pages, and DOI fragments on bibliography lines are
165+
# citation metadata; verify_references audits those, not the sweep.
166+
status[i] <- "reference_meta"
167+
} else if (ignore_years && !doc_nums$is_percent[i] && !doc_nums$is_threshold[i] &&
150168
u == 1 && v >= 1900 && v <= 2100 && v == floor(v)) {
151169
status[i] <- "year_skipped"
152170
} else if (doc_nums$is_threshold[i]) {
@@ -179,7 +197,7 @@ reconcile_values <- function(document, sources, ignore_years = TRUE,
179197
n <- nrow(registry)
180198
counts <- table(factor(registry$status,
181199
levels = c("matched", "matched_scaled", "threshold_ok",
182-
"unmatched", "year_skipped")))
200+
"unmatched", "year_skipped", "reference_meta")))
183201
unmatched <- registry[registry$status == "unmatched", , drop = FALSE]
184202
shown <- utils::head(unmatched, max_unmatched_shown)
185203

@@ -188,9 +206,9 @@ reconcile_values <- function(document, sources, ignore_years = TRUE,
188206
sprintf("Document: %s (%d numeric values)\n", basename(document), n),
189207
sprintf("Corpus: %d unique values from %d source file(s)\n",
190208
length(corpus), length(sources)),
191-
sprintf("matched: %d | matched_scaled: %d | threshold_ok: %d | unmatched: %d | year_skipped: %d\n",
209+
sprintf("matched: %d | matched_scaled: %d | threshold_ok: %d | unmatched: %d | year_skipped: %d | reference_meta: %d\n",
192210
counts["matched"], counts["matched_scaled"], counts["threshold_ok"],
193-
counts["unmatched"], counts["year_skipped"]),
211+
counts["unmatched"], counts["year_skipped"], counts["reference_meta"]),
194212
"\n'values_registry' has been assigned to the global environment.\n",
195213
if (nrow(unmatched) == 0) {
196214
"\nEvery non-year value is accounted for.\n"

0 commit comments

Comments
 (0)