DetectionPipeline class exported ✓ 100.0%
Last updated: 2026-03-01T23:35:47.796Z
Metrics
LOC: 179
Complexity: 17
Params: 0
Coverage: 100.0% (48/48 lines, 0x executed)
Signature
class DetectionPipeline
Architecture violations
- [warning] max-cyclomatic-complexity: 'DetectionPipeline' has cyclomatic complexity 17 (max 10)
- [warning] max-lines: 'DetectionPipeline' has 179 lines (max 80)
Source Code
export class DetectionPipeline {
constructor(readonly classifiers: ReadonlyArray<FieldClassifier>) {}
/**
* Async variant — prefers `detectAsync` when available on a classifier
* (e.g. Chrome AI), falling back to the synchronous `detect` for all others.
*
* Cross-validation behaviour:
* When `html-type` produces a result, the pipeline does NOT stop immediately.
* Instead, the result is held as provisional and the TensorFlow classifier is
* still executed. If TensorFlow returns a *different* type with confidence ≥
* HTML_TYPE_CROSS_VALIDATE_THRESHOLD, TensorFlow wins (semantic context beats
* the structural HTML hint). Otherwise the original html-type result stands.
*
* Example: `<input type="date" name="birthDate">` with label "Nascimento" →
* html-type says "date" (100%), but TensorFlow may say "birthDate" (>50%)
* based on signals → TensorFlow result is used.
*/
async runAsync(field: FormField): Promise<PipelineResult> {
const t0 = performance.now();
const timings: PipelineResult["timings"] = [];
const predictions: PipelineResult["predictions"] = [];
const decisionTrace: string[] = [];
// Holds the provisional html-type result while we wait for TF cross-validation
let htmlTypeProvisional: ClassifierResult | null = null;
for (const classifier of this.classifiers) {
const ct = performance.now();
const result = classifier.detectAsync
? await classifier.detectAsync(field)
: classifier.detect(field);
const classifierMs = performance.now() - ct;
timings.push({ strategy: classifier.name, durationMs: classifierMs });
if (result === null) {
decisionTrace.push(`${classifier.name}: null — skipped`);
} else if (result.type === "unknown") {
decisionTrace.push(
`${classifier.name}: unknown (${(result.confidence * 100).toFixed(0)}%) — skipped`,
);
predictions.push({ type: result.type, confidence: result.confidence });
} else {
predictions.push({ type: result.type, confidence: result.confidence });
// html-type: store provisionally and continue to tensorflow for cross-validation
if (classifier.name === "html-type") {
htmlTypeProvisional = result;
decisionTrace.push(
`${classifier.name}: ${result.type} (${(result.confidence * 100).toFixed(0)}%) — provisional, awaiting tensorflow cross-validation`,
);
continue;
}
// tensorflow: check whether it should override the provisional html-type result
if (classifier.name === "tensorflow" && htmlTypeProvisional !== null) {
if (
result.type !== htmlTypeProvisional.type &&
result.confidence >= HTML_TYPE_CROSS_VALIDATE_THRESHOLD
) {
// TensorFlow has a different, confident semantic classification → override
decisionTrace.push(
`${classifier.name}: ${result.type} (${(result.confidence * 100).toFixed(0)}%) — overrides html-type (semantic context)`,
);
// Fall through to the normal return below
} else {
// TensorFlow confirms html-type or is not confident enough → html-type stands
decisionTrace.push(
`${classifier.name}: ${result.type} (${(result.confidence * 100).toFixed(0)}%) — html-type confirmed`,
);
return {
...htmlTypeProvisional,
method: "html-type",
durationMs: performance.now() - t0,
timings,
predictions,
decisionTrace,
};
}
}
decisionTrace.push(
`${classifier.name}: ${result.type} (${(result.confidence * 100).toFixed(0)}%) — selected`,
);
return {
...result,
method: classifier.name,
durationMs: performance.now() - t0,
timings,
predictions,
decisionTrace,
};
}
// After tensorflow processed (null or unknown): provisional html-type stands
if (htmlTypeProvisional !== null && classifier.name === "tensorflow") {
decisionTrace.push(
`html-type: ${htmlTypeProvisional.type} (100%) — confirmed (tensorflow skipped/unknown)`,
);
return {
...htmlTypeProvisional,
method: "html-type",
durationMs: performance.now() - t0,
timings,
predictions,
decisionTrace,
};
}
}
// End of pipeline — if html-type was provisional and tensorflow wasn't in the pipeline
if (htmlTypeProvisional !== null) {
decisionTrace.push(
`html-type: ${htmlTypeProvisional.type} (100%) — confirmed (no tensorflow in pipeline)`,
);
return {
...htmlTypeProvisional,
method: "html-type",
durationMs: performance.now() - t0,
timings,
predictions,
decisionTrace,
};
}
decisionTrace.push("html-fallback: unknown — no classifier matched");
return {
type: "unknown",
method: "html-fallback",
confidence: 0.1,
durationMs: performance.now() - t0,
timings,
predictions,
decisionTrace,
};
}
/**
* Returns a new pipeline with classifiers reordered by the given method names.
* Classifiers not listed are dropped.
*/
withOrder(names: DetectionMethod[]): DetectionPipeline {
const ordered = names
.map((n) => this.classifiers.find((c) => c.name === n))
.filter((c): c is FieldClassifier => c !== undefined);
return new DetectionPipeline(ordered);
}
/**
* Returns a new pipeline excluding the specified strategies.
*/
without(...names: DetectionMethod[]): DetectionPipeline {
return new DetectionPipeline(
this.classifiers.filter((c) => !names.includes(c.name)),
);
}
/**
* Returns a new pipeline with the given classifier appended at the end.
*/
with(classifier: FieldClassifier): DetectionPipeline {
return new DetectionPipeline([...this.classifiers, classifier]);
}
/**
* Returns a new pipeline with a classifier inserted before the one with
* the given name. Useful for injecting a strategy at a specific priority.
*/
insertBefore(
beforeName: DetectionMethod,
classifier: FieldClassifier,
): DetectionPipeline {
const idx = this.classifiers.findIndex((c) => c.name === beforeName);
if (idx === -1) return this.with(classifier);
const next = [...this.classifiers];
next.splice(idx, 0, classifier);
return new DetectionPipeline(next);
}
}
Members
| Name | Kind | Visibility | Status | Signature |
|---|---|---|---|---|
| runAsync | method | - | runAsync(field: FormField): : Promise<PipelineResult> | |
| insertBefore | method | - | insertBefore( beforeName: DetectionMethod, classifier: FieldClassifier, ): : DetectionPipeline |
No outgoing dependencies.
Impact (Incoming)
| Source | Type |
|---|---|
| FieldProcessingChain | uses |
| runAsync | instantiates |
| stream | instantiates |
| getActiveClassifiers | uses |
| withOrder | instantiates |
| without | instantiates |
| with | instantiates |
| insertBefore | instantiates |
| makeField | uses |