-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbase-plugin.ts
More file actions
136 lines (115 loc) · 4.69 KB
/
Copy pathbase-plugin.ts
File metadata and controls
136 lines (115 loc) · 4.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// ============================================================
// AbstractVerticalPlugin — convenience base class for verticals
//
// Vertical implementations extend this class and provide:
// - signalPatterns : column-name regexes that identify the vertical
// - detectionThreshold: how many signal matches = score of 1.0
// - verticalHierarchies: ordered level-pattern sequences
//
// The base class handles detect(), inferHierarchies(), and inferMeasures()
// using those declarations; subclasses override as needed.
// ============================================================
import { ColumnMeta, SemanticHierarchy, SemanticMeasure, toTitleCase } from "../types.js";
import { InferencePlugin } from "./plugin.js";
// ----------------------------------------------------------
// Hierarchy sequence definition
// ----------------------------------------------------------
/**
* Declares one named hierarchy as an ordered list of regex patterns.
* Each pattern is tested against lowercased column names; the first
* matching column for each level is used. The hierarchy is emitted
* only when ≥ minLevels patterns match.
*/
export interface HierarchySequence {
name: string;
/** Ordered from broadest → most granular. */
levelPatterns: RegExp[];
/** Minimum number of levels that must match to emit the hierarchy. Default: 2. */
minLevels?: number;
}
// ----------------------------------------------------------
// Abstract base
// ----------------------------------------------------------
export abstract class AbstractVerticalPlugin implements InferencePlugin {
abstract readonly name: string;
abstract readonly description: string;
/**
* Column-name regexes that strongly indicate this vertical.
* The more patterns match, the higher the detect() score.
*/
protected abstract readonly signalPatterns: RegExp[];
/**
* Number of signal matches that yields a detect() score of 1.0.
* Scores are capped at 1.0, so 2–4 is typically a good value.
*/
protected abstract readonly detectionThreshold: number;
/** Hierarchy sequences to attempt for this vertical. */
protected abstract readonly verticalHierarchies: HierarchySequence[];
// ----------------------------------------------------------
// Default implementations
// ----------------------------------------------------------
detect(columns: ColumnMeta[]): number {
const lowerNames = columns.map((c) => c.columnName.toLowerCase());
const matches = this.signalPatterns.filter((p) =>
lowerNames.some((n) => p.test(n)),
).length;
return Math.min(matches / this.detectionThreshold, 1.0);
}
inferHierarchies(columns: ColumnMeta[]): SemanticHierarchy[] {
const hierarchies: SemanticHierarchy[] = [];
const usedColumns = new Set<string>();
for (const seq of this.verticalHierarchies) {
const hierarchy = this.buildHierarchy(columns, seq, usedColumns);
if (hierarchy) {
hierarchies.push(hierarchy);
hierarchy.levels.forEach((l) => usedColumns.add(l.sourceColumn.toLowerCase()));
}
}
return hierarchies;
}
/** Default: no additional measures. Subclasses override when needed. */
inferMeasures(_columns: ColumnMeta[]): SemanticMeasure[] {
return [];
}
// ----------------------------------------------------------
// Protected helpers available to subclasses
// ----------------------------------------------------------
/**
* Attempt to build one SemanticHierarchy from an ordered sequence of
* level patterns. Returns null when fewer than minLevels columns match.
*/
protected buildHierarchy(
columns: ColumnMeta[],
seq: HierarchySequence,
alreadyUsed: Set<string> = new Set(),
): SemanticHierarchy | null {
const min = seq.minLevels ?? 2;
const levels: Array<{ name: string; sourceColumn: string }> = [];
for (const pattern of seq.levelPatterns) {
const col = columns.find(
(c) =>
!alreadyUsed.has(c.columnName.toLowerCase()) &&
pattern.test(c.columnName.toLowerCase()),
);
if (col) {
levels.push({ name: toTitleCase(col.columnName), sourceColumn: col.columnName });
}
}
if (levels.length < min) return null;
return { name: seq.name, levels };
}
/** Find the first column matching a pattern (case-insensitive). */
protected findColumn(
columns: ColumnMeta[],
pattern: RegExp,
): ColumnMeta | undefined {
return columns.find((c) => pattern.test(c.columnName.toLowerCase()));
}
/** Find all columns matching a pattern (case-insensitive). */
protected findColumns(
columns: ColumnMeta[],
pattern: RegExp,
): ColumnMeta[] {
return columns.filter((c) => pattern.test(c.columnName.toLowerCase()));
}
}