Skip to content
Talk to our solutions team

Matching and confidence API

Every text lookup in the Rules document library is a distance calculation and every extracted value carries a score. This page is the symbol reference for that surface: the distance functions and their cost models, the confidence types and their fields, the calibration functions, the special-region detector, and the phrase / snippet / table query APIs.

For how these are used in a task — thresholds, tuning, worked examples — read Matching and confidence and Tables and repeating structures. The rule-language spellings are on Document functions. Both documented surfaces reach this API: the kis CLI over local .bbox files and rules.svc over a document sent with the request.

SymbolSignatureCost model
DistanceDistance(a, b string) intLevenshtein over runes; insert, delete and substitute each cost 1
DamerauDistanceDamerauDistance(a, b string) intLevenshtein plus adjacent transposition at cost 1
OCRAwareDistanceOCRAwareDistance(a, b string) float64Levenshtein with fractional substitution cost for known OCR confusions; insert and delete still cost 1.0
SimilaritySimilarity(a, b string) float641 - Distance(a,b) / max(runeLen(a), runeLen(b))
OCRAwareSimilarityOCRAwareSimilarity(a, b string) float64Same ratio computed over OCRAwareDistance
func Distance(a, b string) int
func DamerauDistance(a, b string) int
func OCRAwareDistance(a, b string) float64
func Similarity(a, b string) float64
func OCRAwareSimilarity(a, b string) float64

All five operate on runes, so a multi-byte character is one edit: Distance("café", "cafe") is 1. Identical strings short-circuit — Distance returns 0, the similarity functions return 1.0. Two empty strings return a similarity of 1.0.

Both similarity ratios divide by the longer string, so a short query compared against a long block scores low even when the query is fully contained in it. Range is 0.0 to 1.0.

OCRAwareDistance charges a reduced substitution cost for these pairs. The table is a compiled-in package variable — there is no config key, flag or environment variable that extends or overrides it.

CostPairs (both directions unless noted)
0.3l/1, O/0, o/0, I/1, I/l
0.4S/5, s/5, B/8, $/S, space/_, ,/.
0.5G/6, Z/2, c/e, n/h, u/v
0.6%9 (one direction only)

Every other substitution costs 1.0.

type MatchConfig struct {
MaxDistance int
CaseSensitive bool
OCRAware bool
MinSimilarity float64
}
func DefaultConfig() MatchConfig
FieldTypeDefaultMeaning
MaxDistanceint2Maximum edit distance accepted. Read only on the non-OCR-aware branch of Match
CaseSensitiveboolfalseWhen false both strings are lowercased before comparison
OCRAwarebooltrueCompare with OCRAwareSimilarity instead of Distance
MinSimilarityfloat640.8Similarity floor for an accepted match
func Match(target, candidate string, cfg MatchConfig) bool
func BestMatch(target string, candidates []string, cfg MatchConfig) (int, float64)
func NormalizeForMatch(s string) string
func GenerateOCRVariants(s string) []string
SymbolReturnsBehaviour
MatchboolExact hit returns true. With OCRAware it gates on MinSimilarity; otherwise on MaxDistance
BestMatchindex, similarityIndex of the highest-similarity candidate, or -1 and the best similarity found when that best is below MinSimilarity. Comparison is strictly greater-than, so the first of several equal-similarity candidates wins
NormalizeForMatchstringLowercases, trims, and collapses each run of whitespace to a single space
GenerateOCRVariants[]stringThe input plus every single-character substitution drawn from the confusion table

GenerateOCRVariants builds its result from a map, so the returned order varies between runs. Sort it if order matters.

type Score struct {
Value float64 // Overall score 0.0 - 1.0
Factors []Factor // Contributing factors
}
type Factor struct {
Name string // Descriptive name (e.g., "ocr", "alignment", "pattern_match")
Value float64 // Factor score 0.0 - 1.0
Weight float64 // Weight in overall calculation (default 1.0)
Detail string // Optional detail about this factor
}

Every confidence number in the block is a Score. Value is the weight-normalised mean of the factors, clamped to [0,1]. Detail is what makes an extraction explainable in a review queue.

func New(value float64) Score
func FromFactors(factors ...Factor) Score
func (s Score) WithFactor(f Factor) Score
SymbolReturnsBehaviour
NewScoreBare value, clamped to [0,1], no factors
FromFactorsScoreWeighted mean of the factors, clamped. A factor with Weight <= 0 is coerced to 1.0. No factors returns Value: 0
WithFactorScoreAppends one factor and recomputes through FromFactors
func (s Score) String() string // "%.2f"
func (s Score) Percent() float64 // Value * 100
func (s Score) IsHigh() bool // >= 0.8
func (s Score) IsMedium() bool // >= 0.5 and < 0.8
func (s Score) IsLow() bool // < 0.5
func (s Score) Level() Level
type Level int
const (
LevelVeryLow Level = iota
LevelLow
LevelMedium
LevelHigh
LevelVeryHigh
)
func (l Level) String() string // "VeryLow" | "Low" | "Medium" | "High" | "VeryHigh"
LevelBand
LevelVeryHighValue >= 0.9
LevelHighValue >= 0.8
LevelMediumValue >= 0.6
LevelLowValue >= 0.4
LevelVeryLowbelow 0.4

Each constructor fixes the factor name and weight. The weights are compiled in, not configuration.

ConstructorNameWeightValue
OCRFactor(ocrConfidence float64)ocr1.0the OCR confidence, clamped
ValidationFactor(isValid bool, validatorName string)validation1.01.0 when valid, else 0.0
PatternMatchFactor(matchScore float64, pattern string)pattern_match0.9the match score, clamped
AlignmentFactor(alignmentScore float64)alignment0.8the alignment score, clamped
ConsistencyFactor(isConsistent bool, detail string)consistency0.81.0 when consistent, else 0.0
FuzzyMatchFactor(similarity float64, editDistance int)fuzzy_match0.7the similarity, clamped
ProximityFactor(distance, maxDistance float64)proximity0.61 - distance/maxDistance, clamped; 1.0 when either argument is <= 0
CountFactor(found, expected int)count0.5found/expected, clamped; 0 when expected <= 0
func OCRFactor(ocrConfidence float64) Factor
func AlignmentFactor(alignmentScore float64) Factor
func PatternMatchFactor(matchScore float64, pattern string) Factor
func FuzzyMatchFactor(similarity float64, editDistance int) Factor
func ProximityFactor(distance, maxDistance float64) Factor
func CountFactor(found, expected int) Factor
func ConsistencyFactor(isConsistent bool, detail string) Factor
func ValidationFactor(isValid bool, validatorName string) Factor
func Average(scores ...Score) Score
func WeightedAverage(scores []Score, weights []float64) Score
func Min(scores ...Score) Score
func Max(scores ...Score) Score
func Geometric(scores ...Score) Score
func Harmonic(scores ...Score) Score
SymbolResultNotes
Averagearithmetic meanConcatenates every input’s factors onto the result
WeightedAverageweight-normalised meanReturns Value: 0 when the slice lengths differ; negative weights are treated as 0
Min / Maxthe extreme input ScoreReturns the whole score, factors included
Geometricgeometric meanReturns Value: 0 and no factors if any input is <= 0
Harmonicharmonic meanSame zero collapse; penalises low scores hardest

Average and WeightedAverage do not clamp — they are safe only because factor-built inputs are already clamped. Geometric and Harmonic discard all factors.

func (s Score) Penalize(penalty float64) Score
func (s Score) PenalizeIf(condition bool, penalty float64) Score
func (s Score) Boost(boost float64) Score
func (s Score) BoostIf(condition bool, boost float64) Score
func (s Score) AtLeast(threshold float64) Score
func (s Score) Cap(maxValue float64) Score
func (s Score) Floor(minValue float64) Score
SymbolResult
Penalize(p)Value * (1 - p), clamped
PenalizeIf(cond, p)Penalize(p) when cond, otherwise unchanged
Boost(b)Value + (1 - Value) * b, with b clamped to [0,1]
BoostIf(cond, b)Boost(b) when cond, otherwise unchanged
AtLeast(t)the score when Value >= t, otherwise Value: 0 keeping the factors
Cap(max)Value lowered to max when above it
Floor(min)Value raised to min when below it

All seven preserve the factor slice, so the audit trail survives shaping.

func FromEditDistance(editDist, maxLen int) float64
func FromOverlap(overlapArea, box1Area, box2Area float64) float64
func FromAlignment(offset, tolerance float64) float64
SymbolResultRange
FromEditDistance1 - editDist/maxLen; 0 when maxLen <= 0goes negative if editDist > maxLen — clamp before use
FromOverlapintersection over union of the two box areas; 0 when either area or the union is <= 00.0 to 1.0
FromAlignment1 - abs(offset)/tolerance, 0 at or beyond tolerance; with tolerance <= 0 returns 1.0 only for an exact 0 offset0.0 to 1.0
type Report struct {
Overall Score
Components map[string]Score
Issues []string
Suggestions []string
}
func NewReport() *Report
func (r *Report) Add(name string, score Score)
func (r *Report) AddIssue(format string, args ...any)
func (r *Report) AddSuggestion(format string, args ...any)
func (r *Report) Compute()
func (r *Report) Summary() string

Compute sets Overall to the unweighted Average of every component. Summary renders the overall percentage and level, then components, issues and suggestions as a text block. Components are iterated from a map, so Summary lists them in arbitrary order.

A raw score of 0.8 is a heuristic weighted average, not a probability. Calibration is what turns it into one, and what answers the operating-point question: if you auto-accept everything at or above a threshold and review the rest, what precision do you get and how much did you cover?

type Sample struct {
Predicted float64 // confidence the extractor reported, 0.0 - 1.0
Correct bool // whether the extracted value matched ground truth
}
type Calibration struct{ /* opaque */ }
func Fit(samples []Sample) *Calibration
func (c *Calibration) Apply(raw float64) float64
func (c *Calibration) SampleCount() int64
SymbolBehaviour
FitIsotonic regression by Pool-Adjacent-Violators. Samples are aggregated by identical raw score first, so the fit is insensitive to the order of equal-score samples. Returns nil for empty input
ApplyLinear interpolation between fitted knots; holds the end value outside the fitted range. A nil receiver is the identity map, still clamped to [0,1]. Safe for concurrent use after Fit
SampleCountNumber of labelled samples the fit used; 0 on a nil receiver
// scores that violate monotonicity are pooled to the same calibrated value
c := confidence.Fit(samples)
c.Apply(0.50) // == c.Apply(0.70) when raw 0.50 outperformed raw 0.70
var uncalibrated *confidence.Calibration
uncalibrated.Apply(0.37) // 0.37
uncalibrated.Apply(1.5) // 1.0
type ReliabilityBin struct {
Low float64
High float64
Count int64
MeanPredicted float64
Empirical float64
}
func Reliability(samples []Sample, bins int) []ReliabilityBin
func ECE(samples []Sample, bins int) float64

Reliability buckets samples into bins equal-width bands over [0,1]bins <= 0 means 10 — and omits empty bands. A well-calibrated scorer has MeanPredicted close to Empirical in every band.

ECE is the Expected Calibration Error: the sample-weighted mean absolute gap between MeanPredicted and Empirical. Range 0.0 to 1.0; 0 is perfect. It is the single headline number for whether the confidences are honest. With no samples it returns 0 — indistinguishable from a perfectly calibrated scorer, so check the sample count before reading it.

type ThresholdStat struct {
Threshold float64
Coverage float64 // fraction of all values auto-accepted
Precision float64 // fraction correct among auto-accepted
Accepted int64
AcceptedWrong int64 // auto-accepted but actually wrong
ReviewedCount int64 // routed to human review
}
func PrecisionCoverageCurve(samples []Sample, steps int) []ThresholdStat
func ThresholdForPrecision(samples []Sample, targetPrecision float64, minAccepted int64) (stat ThresholdStat, ok bool)
func CurveReport(curve []ThresholdStat) string
SymbolBehaviour
PrecisionCoverageCurveSweeps the accept threshold from 0 to 1 in steps increments and returns steps+1 points. steps <= 0 means 20
ThresholdForPrecisionThe lowest threshold meeting targetPrecision, maximising coverage. Candidates are the distinct predicted scores plus a 0.01 sweep. Thresholds with fewer than minAccepted accepted samples are skipped. ok is false when no threshold reaches the target
CurveReportText table with the header threshold coverage precision accepted wrong reviewed

With nothing accepted, Precision is defined as 1.0 — vacuously precise. Read Accepted alongside it before believing a precision figure.

type RegionType int
const (
RegionUnknown RegionType = iota
RegionSignature
RegionHandwriting
RegionBarcode1D
RegionBarcodeQR
RegionBarcode2D // DataMatrix, PDF417, etc.
)
func (rt RegionType) String() string // "Unknown" | "Signature" | "Handwriting" | "Barcode1D" | "QRCode" | "Barcode2D"
type Region struct {
Type RegionType
Box geometry.Box
Page int
Confidence confidence.Score
NearLabel string // Nearby label text (e.g., "Signature:", "Sign here")
OCRText string // Any OCR text detected in region (often garbage for signatures)
OCRConfidence float64 // Average OCR confidence in region
AspectRatio float64 // Width / Height
WordDensity float64 // Words per unit area
IsIsolated bool // True if not part of main text flow
}
func (r Region) GetPage() int

Detection is inference over OCR geometry and confidence. No image is read, and nothing is decoded — OCRText on a barcode region is whatever the OCR engine emitted there, never a barcode payload.

GetPage returns Page; it exists so a Region satisfies the page-filter interface shared with the other extracted shapes.

type Detector struct{ /* config DetectorConfig, unexported */ }
func NewDetector() *Detector
func NewDetectorWithConfig(cfg DetectorConfig) *Detector
func (d *Detector) DetectAll(pages []ocr.Page, blocks []text.Block) []Region
func (d *Detector) DetectSignatures(pages []ocr.Page, blocks []text.Block) []Region
func (d *Detector) DetectHandwriting(pages []ocr.Page, blocks []text.Block) []Region
func (d *Detector) DetectBarcodes(pages []ocr.Page) []Region

NewDetector uses DefaultDetectorConfig; the config is unexported afterwards, so every threshold is fixed at construction. DetectAll is DetectSignatures then DetectHandwriting then DetectBarcodes, concatenated. DetectBarcodes takes pages only — it reads no text blocks.

type DetectorConfig struct {
SignatureMinWidth float64
SignatureMaxHeight float64
SignatureOCRThreshold float64
SignatureLabelDistance float64
HandwritingOCRMin float64
HandwritingOCRMax float64
HandwritingVarianceMin float64
BarcodeMinAspectRatio float64
BarcodeMaxAspectRatio float64
QRCodeAspectTolerance float64
BarcodeMinWidth float64
BarcodeMinHeight float64
}
func DefaultDetectorConfig() DetectorConfig
FieldDefaultMeaning
SignatureMinWidth100Minimum width for a signature region
SignatureMaxHeight80Maximum height — signatures are typically short
SignatureOCRThreshold0.3OCR confidence below this suggests a signature
SignatureLabelDistance150Maximum distance from a signature label
HandwritingOCRMin0.3Below this the region reads as a signature instead
HandwritingOCRMax0.75Above this the region reads as printed text
HandwritingVarianceMin0.15Minimum character-size variance to consider handwriting
BarcodeMinAspectRatio3.01D barcodes are at least 3× wider than tall
BarcodeMaxAspectRatio20.0Upper bound for 1D
QRCodeAspectTolerance0.3QR aspect ratio must fall within 0.7–1.3
BarcodeMinWidth50Minimum barcode width
BarcodeMinHeight20Minimum barcode height

A signature found next to a label emits at whatever it scores; an isolated low-confidence candidate in the bottom third of the page emits only at ≥ 0.4. Handwriting emits at ≥ 0.5 and merges same-type regions within 50px on a page. Barcodes emit at ≥ 0.4.

func FilterByConfidence(regions []Region, minConfidence float64) []Region
func FilterByType(regions []Region, regionType RegionType) []Region
func GetSignatures(regions []Region) []Region
func GetHandwriting(regions []Region) []Region
func GetBarcodes(regions []Region) []Region

GetBarcodes returns all three barcode types. All five return nil when nothing matches.

var DefaultSignatureAnchors = []string{
"signature", "sign here", "sign:", "signed:", "authorized signature",
"x_", "x:", "sign below", "your signature",
}
func AddSignatureAnchors(anchors ...string)
func RegisteredSignatureAnchors() []string
func ResetSignatureAnchors()
SymbolBehaviour
DefaultSignatureAnchorsThe domain-neutral anchor set. Matching is case-insensitive substring
AddSignatureAnchorsAppends domain anchors; never replaces the defaults. Mutex-protected, typically called from init()
RegisteredSignatureAnchorsDefaults followed by registered anchors in registration order, as a fresh copy
ResetSignatureAnchorsClears registered anchors, leaving the defaults. Tests only
special.AddSignatureAnchors(
"borrower signature", "co-borrower signature", "coborrower signature",
"applicant signature", "co-applicant signature",
"seller signature", "witness signature", "notary signature",
)
type PhraseMatch struct {
Phrase string // The matched phrase text
Page int // Page number where found
Box geometry.Box // Bounding box of the phrase
LineIndex int // Index of the line containing the phrase
MatchedLine string // Full text of the line containing the match
LinesBefore []string // Lines before the match (context)
LinesAfter []string // Lines after the match (context)
Blocks []text.Block // The blocks that form this phrase
}
type FuzzyPhraseMatch struct {
PhraseMatch
TotalDistance int // Total Levenshtein distance across all words
MatchedWords []string // The actual words that matched (from document)
}
func (d *Document) FindPhrase(phrase string, linesBefore, linesAfter int64) []PhraseMatch
func (d *Document) FindIPhrase(phrase string, linesBefore, linesAfter int64) []PhraseMatch
func (d *Document) FindPhraseFuzzy(phrase string, maxDistancePerWord, maxTotalDistance, linesBefore, linesAfter int64) []FuzzyPhraseMatch
func (d *Document) FindIPhraseFuzzy(phrase string, maxDistancePerWord, maxTotalDistance, linesBefore, linesAfter int64) []FuzzyPhraseMatch

The I variants are case-insensitive. Box is the union of the matched blocks. The fuzzy walk finds candidates for the first word within maxDistancePerWord, then extends across the same page, ranking candidates by editDistance*100 + spatialDistance.

All are case-insensitive and built on FindIPhrase / FindIPhraseFuzzy. The fuzzy forms derive the total budget as maxDistancePerWord × wordCount; there is no separate total argument.

SignatureReturns
HasPhrase(phrase string) boolphrase present anywhere
HasPhraseFuzzy(phrase string, maxDistancePerWord int64) boolas above, fuzzy
HasPhraseOnPage(phrase string, page int64) boolphrase present on one page
HasPhraseOnPageFuzzy(phrase string, page int64, maxDistancePerWord int64) boolas above, fuzzy
HasPhraseInBox(phrase string, box geometry.Box) boolphrase present inside a box
HasPhraseInBoxFuzzy(phrase string, box geometry.Box, maxDistancePerWord int64) boolas above, fuzzy
HasAnyPhrase(phrases ...string) boolany of the phrases present
HasAnyPhraseFuzzy(maxDistancePerWord int64, phrases ...string) boolas above, fuzzy
HasAllPhrases(phrases ...string) boolevery phrase present
HasAllPhrasesFuzzy(maxDistancePerWord int64, phrases ...string) boolas above, fuzzy
HasAnyPhraseInBox(box geometry.Box, phrases ...string) boolany phrase inside a box
HasAnyPhraseOnPage(page int64, phrases ...string) boolany phrase on one page
CountPhrase(phrase string) int64occurrence count
CountPhraseFuzzy(phrase string, maxDistancePerWord int64) int64fuzzy occurrence count
CountPhraseOnPage(phrase string, page int64) int64occurrence count on one page
PhraseNear(a, b string, maxPixels float64) boolboth phrases present within maxPixels
PhraseBetween(startAnchor, endAnchor string) stringtext between two anchors

Note the argument order on the variadic fuzzy forms: the distance budget comes first, before the phrases.

func (q QueryResult) ContainingPhraseFuzzy(phrase string, maxDistPerWord, maxTotalDist int64) QueryResult
func (q QueryResult) ContainingIPhraseFuzzy(phrase string, maxDistPerWord, maxTotalDist int64) QueryResult
func (q QueryResult) ContainingAnyPhraseFuzzy(phrases string, maxDistPerWord, maxTotalDist int64) QueryResult
func (q QueryResult) ContainingAnyIPhraseFuzzy(phrases string, maxDistPerWord, maxTotalDist int64) QueryResult

These flatten the region’s blocks into a word stream, normalise quotes and dashes, then slide the phrase over the stream comparing with OCRAwareDistance. A match requires every word within maxDistPerWord and the running total within maxTotalDist. The result carries the blocks that participated in the match, or is empty.

The Any forms split phrases on | and return the first phrase that matches.

doc.InRegionPct(0, 0, 100, 20).ContainingAnyIPhraseFuzzy(
"ANTI-COERCION INSURANCE|Anti-Coercion Statement|STATEMENT OF ANTI-COERCION", 2, 4)
type Snippet struct {
StartPhrase string // The starting phrase that was matched
EndPhrase string // The ending phrase that was matched
StartPage int // Page where the start phrase was found
EndPage int // Page where the end phrase was found (or last page if not found)
Lines []string // The extracted lines of text
}
func (d *Document) ExtractSnippet(startPhrase, endPhrase string, caseInsensitive, includeEnd bool) *Snippet
func (d *Document) ExtractSnippetFuzzy(startPhrase, endPhrase string, caseInsensitive, includeEnd bool, maxDist int64) *Snippet
BehaviourDetail
Start linealways included
End lineexcluded unless includeEnd is true
Page boundariesextraction spans them
Not foundreturns nil, and only when the start phrase is missing
No end matchEndPage is the page count and extraction runs to the end of the document
Fuzzy budgettotal is maxDist × max(startWordCount, endWordCount), applied to both phrases
func (d *Document) TableCount() int64
func (d *Document) TableAt(index int64) *table.Table
func (d *Document) TableNear(anchorText string) *table.Table
func (d *Document) TableNearPhrase(anchorPhrase string) *table.Table

TableNear delegates to TableNearPhrase when the anchor contains a space, and otherwise finds the first block containing the text. TableNearPhrase uses the case-sensitive FindPhrase and falls back to a single-block contains search. Both then return the table with the smallest box-to-box distance.

func (d *Document) TableBelow(anchor string) *TableResult
func (d *Document) TableAbove(anchor string) *TableResult
func (d *Document) TableBetween(startAnchor, endAnchor string) *TableResult
func (d *Document) TablesBetween(startAnchor, endAnchor string) []*TableResult
func (d *Document) TableBelowFuzzy(anchor string, maxDistPerWord, maxTotalDist int64) *TableResult
func (d *Document) TableAboveFuzzy(anchor string, maxDistPerWord, maxTotalDist int64) *TableResult
func (d *Document) TableBetweenFuzzy(startAnchor, endAnchor string, maxDistPerWord, maxTotalDist int64) *TableResult

Nil-safe wrappers return a zero-valued result instead of nil, so a rule cannot fault on a missing table:

func (d *Document) SafeTable(index int64) *SafeTableResult
func (d *Document) SafeTableBelow(anchor string) *SafeTableResult
func (d *Document) SafeTableAbove(anchor string) *SafeTableResult
func (d *Document) SafeTableBetween(startAnchor, endAnchor string) *SafeTableResult
func (d *Document) SafeTablesBetween(startAnchor, endAnchor string) []*TableResult
func (d *Document) SafeTableBelowFuzzy(anchor string, maxDistPerWord, maxTotalDist int64) *SafeTableResult
func (d *Document) SafeTableAboveFuzzy(anchor string, maxDistPerWord, maxTotalDist int64) *SafeTableResult
func (d *Document) SafeTableBetweenFuzzy(startAnchor, endAnchor string, maxDistPerWord, maxTotalDist int64) *SafeTableResult
type Table struct {
Rows []Row
Columns []Column
Box geometry.Box
HeaderRows int
Page int
Confidence float64 // Overall (backward compatibility)
TableScore confidence.Score // Detailed table confidence
}
func NewDetector() *Detector
func (d *Detector) WithMinDimensions(rows, cols int) *Detector
func (d *Detector) DetectTables(lines []text.Line, docStats text.DocStats) []Table

This Detector is the table detector, distinct from the special-region Detector above — the two NewDetector constructors live in different packages. Table detector defaults: minimum 2 rows, minimum 2 columns, alignment tolerance 0.1, gap multiplier 2.0.

Table.Confidence is (consistencyScore + sizeScore) / 2, where consistencyScore is 1 / (1 + variance(cellsPerRow) * 0.1) and sizeScore is 0.5 by default, 0.8 at ≥ 3 rows and ≥ 2 columns, and 1.0 at ≥ 5 rows and ≥ 3 columns. It is a shape score: it says nothing about whether the cell text is right, and it is not comparable with field-extraction confidence.

Header detection yields at most one header row, from either of two signals: the first row contains no digits and no cell longer than 50 characters, or the gap after the first row exceeds 1.2× the average row gap. Multi-row headers are never detected.

The full Table, TableResult and SafeTableResult accessor surface is on Tables and repeating structures.

ScoreRangeMeaning
fuzzy.Similarity, fuzzy.OCRAwareSimilarity0.0 – 1.01.0 is identical; the denominator is the longer string
confidence.Score.Value0.0 – 1.0Weighted mean of the factors. A raw heuristic belief, not a probability
confidence.Factor.Value0.0 – 1.0One signal’s contribution, clamped by its constructor
Calibration.Apply0.0 – 1.0Empirical probability of correctness, once fitted against labelled outcomes
ECE0.0 – 1.0Mean gap between claimed and actual accuracy; 0 is perfect
ThresholdStat.Coverage0.0 – 1.0Fraction of values auto-accepted at that threshold
ThresholdStat.Precision0.0 – 1.0Fraction correct among the accepted; 1.0 when nothing is accepted
special.Region.Confidence0.0 – 1.0Heuristic detection belief over OCR geometry; not comparable with extraction confidence
table.Table.Confidence0.0 – 1.0Table shape consistency and size only