This is an automated email from the ASF dual-hosted git repository.

tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to refs/heads/main by this push:
     new 9a7bcad2aa TIKA-5027 follow up (#3538)
9a7bcad2aa is described below

commit 9a7bcad2aaf5840f0c56719df990041d730f42d6
Author: Tim Allison <[email protected]>
AuthorDate: Wed Nov 4 16:32:07 2026 -0500

    TIKA-5027 follow up (#3538)
---
 CHANGES.txt                                        |  6 ++---
 .../tika/ml/junkdetect/tools/TrainJunkModel.java   |  4 +++-
 .../ml/junkdetect/tools/TrainingProvenance.java    | 28 ++++++++++++++++++++++
 .../junkdetect/tools/TrainingProvenanceTest.java   |  9 +++++++
 .../tika-ml-junkdetect-tools/train-junk-model.sh   | 14 +++++++----
 .../apache/tika/ml/junkdetect/JunkDetector.java    | 11 +++++----
 6 files changed, 59 insertions(+), 13 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 289d382705..a851f5c04a 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -118,9 +118,9 @@ Release 4.3.0 - ???
    * The junk detector no longer scores text in scripts its model has no 
usable table
      for (Yi, Mongolian, Canadian syllabics, Cherokee, Adlam and 37 others) as
      junk: it scored -3 to -19 before. TextQualityScore gains getCoverage(), 
and
-     tika-eval reports languageness for such text as unknown. Text with no 
letters
-     (whitespace, digits, symbols) now scores -1 instead of +3, below clean 
prose
-     (TIKA-5027).
+     tika-eval reports languageness for such text as unknown. 
JunkDetector.knownScripts()
+     no longer lists those scripts. Text with no letters (whitespace, digits, 
symbols) now
+     scores -1 instead of +3, below clean prose (TIKA-5027).
 
    * The file-to-markdown-docker agent skill turns OCR off with an empty 
text-recognizers
      list and a server block (its tesseract-ocr-parser config failed to load 
since 4.2.0),
diff --git 
a/tika-ml/tika-ml-junkdetect-tools/src/main/java/org/apache/tika/ml/junkdetect/tools/TrainJunkModel.java
 
b/tika-ml/tika-ml-junkdetect-tools/src/main/java/org/apache/tika/ml/junkdetect/tools/TrainJunkModel.java
index c7ff4cd7ad..db2fa3d3f7 100644
--- 
a/tika-ml/tika-ml-junkdetect-tools/src/main/java/org/apache/tika/ml/junkdetect/tools/TrainJunkModel.java
+++ 
b/tika-ml/tika-ml-junkdetect-tools/src/main/java/org/apache/tika/ml/junkdetect/tools/TrainJunkModel.java
@@ -204,7 +204,9 @@ public class TrainJunkModel {
         Path output = null;
         String gitCommit = null;
         for (int i = 0; i < args.length; i++) {
-            if (i + 1 >= args.length) {
+            boolean known = args[i].equals("--data-dir") || 
args[i].equals("--output")
+                    || args[i].equals("--git-commit");
+            if (known && i + 1 >= args.length) {
                 System.err.println("ERROR: " + args[i] + " needs a value");
                 System.exit(1);
             }
diff --git 
a/tika-ml/tika-ml-junkdetect-tools/src/main/java/org/apache/tika/ml/junkdetect/tools/TrainingProvenance.java
 
b/tika-ml/tika-ml-junkdetect-tools/src/main/java/org/apache/tika/ml/junkdetect/tools/TrainingProvenance.java
index eeec4e5c36..a21c5a2201 100644
--- 
a/tika-ml/tika-ml-junkdetect-tools/src/main/java/org/apache/tika/ml/junkdetect/tools/TrainingProvenance.java
+++ 
b/tika-ml/tika-ml-junkdetect-tools/src/main/java/org/apache/tika/ml/junkdetect/tools/TrainingProvenance.java
@@ -66,9 +66,37 @@ public final class TrainingProvenance {
         Map<String, String> kv = new LinkedHashMap<>();
         kv.put("source_corpus", 
sourceCorpus.toAbsolutePath().normalize().getFileName().toString());
         kv.put("source_manifest_sha256", sha256(m));
+        kv.put("source_content_sha256", contentSha256(sourceCorpus));
         writeKv(builtDir.resolve(SOURCE_FILE), kv);
     }
 
+    /**
+     * SHA-256 over every regular file under {@code dir}, in path order: each 
file's relative
+     * path and SHA-256.  The manifest only lists languages and counts, so 
this is what pins the
+     * corpus text itself.
+     */
+    static String contentSha256(Path dir) throws IOException {
+        List<Path> files;
+        try (java.util.stream.Stream<Path> s = Files.walk(dir)) {
+            files = 
s.filter(Files::isRegularFile).sorted().collect(java.util.stream.Collectors.toList());
+        }
+        MessageDigest md;
+        try {
+            md = MessageDigest.getInstance("SHA-256");
+        } catch (NoSuchAlgorithmException e) {
+            throw new IllegalStateException(e);
+        }
+        for (Path f : files) {
+            String rel = dir.relativize(f).toString().replace('\\', '/');
+            md.update((rel + "\t" + sha256(f) + 
"\n").getBytes(StandardCharsets.UTF_8));
+        }
+        StringBuilder sb = new StringBuilder(64);
+        for (byte b : md.digest()) {
+            sb.append(String.format(java.util.Locale.ROOT, "%02x", b));
+        }
+        return sb.toString();
+    }
+
     /** Reads a key/value file written by {@link #writeKv}. */
     static Map<String, String> readKv(Path p) throws IOException {
         Map<String, String> kv = new LinkedHashMap<>();
diff --git 
a/tika-ml/tika-ml-junkdetect-tools/src/test/java/org/apache/tika/ml/junkdetect/tools/TrainingProvenanceTest.java
 
b/tika-ml/tika-ml-junkdetect-tools/src/test/java/org/apache/tika/ml/junkdetect/tools/TrainingProvenanceTest.java
index 92d1ba3dc5..760a7f7e8d 100644
--- 
a/tika-ml/tika-ml-junkdetect-tools/src/test/java/org/apache/tika/ml/junkdetect/tools/TrainingProvenanceTest.java
+++ 
b/tika-ml/tika-ml-junkdetect-tools/src/test/java/org/apache/tika/ml/junkdetect/tools/TrainingProvenanceTest.java
@@ -40,7 +40,16 @@ public class TrainingProvenanceTest {
 
         assertThrows(IOException.class, () -> 
TrainingProvenance.writeSource(corpus, built));
         Files.writeString(corpus.resolve("manifest.tsv"), "lang\tscript\n", 
StandardCharsets.UTF_8);
+        Files.createDirectories(corpus.resolve("eng"));
+        Files.writeString(corpus.resolve("eng/sentences_wikipedia.txt"), 
"1\tHello.\n", StandardCharsets.UTF_8);
         TrainingProvenance.writeSource(corpus, built);
+        String content = 
TrainingProvenance.readKv(built.resolve(TrainingProvenance.SOURCE_FILE))
+                .get("source_content_sha256");
+        // editing a sentence file without touching the manifest changes the 
content digest
+        Files.writeString(corpus.resolve("eng/sentences_wikipedia.txt"), 
"1\tHullo.\n", StandardCharsets.UTF_8);
+        assertTrue(!content.equals(TrainingProvenance.contentSha256(corpus)));
+        Files.writeString(corpus.resolve("eng/sentences_wikipedia.txt"), 
"1\tHello.\n", StandardCharsets.UTF_8);
+        assertEquals(content, TrainingProvenance.contentSha256(corpus));
 
         Files.writeString(model, "model", StandardCharsets.UTF_8);
         assertThrows(IOException.class, () -> 
TrainingProvenance.writeModelProvenance(
diff --git a/tika-ml/tika-ml-junkdetect-tools/train-junk-model.sh 
b/tika-ml/tika-ml-junkdetect-tools/train-junk-model.sh
index 77f9ebe07d..dab8312dc2 100755
--- a/tika-ml/tika-ml-junkdetect-tools/train-junk-model.sh
+++ b/tika-ml/tika-ml-junkdetect-tools/train-junk-model.sh
@@ -36,16 +36,22 @@ SRC="${TIKA_JUNK_DATA}/${CORPUS}"
 ROOT=$(git rev-parse --show-toplevel)
 cd "${ROOT}"
 
-# An uncommitted tree is allowed (experiments), but the exact diff is kept 
beside the log.
+# An uncommitted tree is allowed (experiments), but the exact diff is kept 
beside the log,
+# untracked (not ignored) files included: they are built into the jar too.
+UNCOMMITTED=$(mktemp)
+git diff HEAD --binary > "${UNCOMMITTED}"
+git ls-files --others --exclude-standard -z | while IFS= read -r -d '' f; do
+  git diff --no-index --binary /dev/null "$f" >> "${UNCOMMITTED}" || true
+done
 GIT=$(git rev-parse HEAD)
-if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
-  DIFF_SHA=$(git diff HEAD | sha256sum | cut -c1-12)
+if [ -s "${UNCOMMITTED}" ]; then
+  DIFF_SHA=$(sha256sum < "${UNCOMMITTED}" | cut -c1-12)
   GIT="${GIT}-dirty-${DIFF_SHA}"
 fi
 RUN="${TIKA_JUNK_DATA}/${CORPUS}-runs/$(date +%Y%m%d-%H%M%S)-${GIT:0:12}"
 BUILT="${RUN}/built"
 mkdir -p "${RUN}"
-git diff HEAD > "${RUN}/uncommitted.diff"
+mv "${UNCOMMITTED}" "${RUN}/uncommitted.diff"
 LOG="${RUN}/train.log"
 echo "corpus=${CORPUS} git=${GIT} run=${RUN}" | tee "${LOG}"
 
diff --git 
a/tika-ml/tika-ml-junkdetect/src/main/java/org/apache/tika/ml/junkdetect/JunkDetector.java
 
b/tika-ml/tika-ml-junkdetect/src/main/java/org/apache/tika/ml/junkdetect/JunkDetector.java
index e6a4a8fc46..5a7aa0c081 100644
--- 
a/tika-ml/tika-ml-junkdetect/src/main/java/org/apache/tika/ml/junkdetect/JunkDetector.java
+++ 
b/tika-ml/tika-ml-junkdetect/src/main/java/org/apache/tika/ml/junkdetect/JunkDetector.java
@@ -615,13 +615,14 @@ public final class JunkDetector implements 
TextQualityDetector {
             // No scoreable LETTER at all (zero runs) — doc-level fallback:
             // density=0 -> very negative; density=1 coherence=1 -> positive
             // (unmodeled coherent script); density=1 coherence=0 -> very 
negative.
-            // z6 is included so a letter-free but FFFD/anomaly-heavy doc 
(which can
-            // no longer flood z1) is still penalized here — no path ignores 
the
-            // replacement ratio.  z6 is 1 for anomaly-free text, so (z6 - 1) 
keeps
-            // it inert there: all-whitespace -1, coherent unmodeled script 
~+2.4.
+            // The raw replacement ratio penalizes a letter-free but 
FFFD/anomaly-heavy
+            // doc (which can no longer flood z1); it is 0 for anomaly-free 
text:
+            // all-whitespace -1, coherent unmodeled script ~+2.4.  Raw, not 
z6, so a
+            // retrained z6 calibration cannot shift these scores.
             agg.dominantScript = null;
             agg.z1 = Float.NaN;
-            agg.logit = -7f + 4f * agg.z7 + 6f * agg.z8 + 4f * (agg.z6 - 1f);
+            double rr = TextQualityFeatures.replacementRatio(text);
+            agg.logit = -7f + 4f * agg.z7 + 6f * agg.z8 - 4f * 
(Double.isNaN(rr) ? 0f : (float) rr);
             return agg;
         }
         agg.z1 = (float) (weightedZ1 / z1Count);

Reply via email to