sxng-cli 1.0.9 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -1
- package/dist/commands/graph-add.d.ts.map +1 -1
- package/dist/commands/graph-add.js +9 -1
- package/dist/commands/graph-add.js.map +1 -1
- package/dist/commands/graph-drill.d.ts +11 -0
- package/dist/commands/graph-drill.d.ts.map +1 -0
- package/dist/commands/graph-drill.js +26 -0
- package/dist/commands/graph-drill.js.map +1 -0
- package/dist/commands/graph-explore.d.ts +10 -0
- package/dist/commands/graph-explore.d.ts.map +1 -0
- package/dist/commands/graph-explore.js +32 -0
- package/dist/commands/graph-explore.js.map +1 -0
- package/dist/commands/graph-obfuscate.d.ts +17 -0
- package/dist/commands/graph-obfuscate.d.ts.map +1 -0
- package/dist/commands/graph-obfuscate.js +110 -0
- package/dist/commands/graph-obfuscate.js.map +1 -0
- package/dist/commands/graph-search.d.ts +11 -0
- package/dist/commands/graph-search.d.ts.map +1 -0
- package/dist/commands/graph-search.js +19 -0
- package/dist/commands/graph-search.js.map +1 -0
- package/dist/commands/graph-traverse.d.ts +10 -0
- package/dist/commands/graph-traverse.d.ts.map +1 -0
- package/dist/commands/graph-traverse.js +39 -0
- package/dist/commands/graph-traverse.js.map +1 -0
- package/dist/commands/query-graph.d.ts +2 -0
- package/dist/commands/query-graph.d.ts.map +1 -1
- package/dist/commands/query-graph.js +66 -0
- package/dist/commands/query-graph.js.map +1 -1
- package/dist/deep/co-occurrence.d.ts +50 -0
- package/dist/deep/co-occurrence.d.ts.map +1 -0
- package/dist/deep/co-occurrence.js +95 -0
- package/dist/deep/co-occurrence.js.map +1 -0
- package/dist/deep/degree-utils.d.ts +39 -0
- package/dist/deep/degree-utils.d.ts.map +1 -0
- package/dist/deep/degree-utils.js +42 -0
- package/dist/deep/degree-utils.js.map +1 -0
- package/dist/deep/extractor.d.ts +10 -2
- package/dist/deep/extractor.d.ts.map +1 -1
- package/dist/deep/extractor.js +158 -80
- package/dist/deep/extractor.js.map +1 -1
- package/dist/deep/graph-explore.d.ts +115 -0
- package/dist/deep/graph-explore.d.ts.map +1 -0
- package/dist/deep/graph-explore.js +586 -0
- package/dist/deep/graph-explore.js.map +1 -0
- package/dist/deep/graph-obfuscate.d.ts +51 -0
- package/dist/deep/graph-obfuscate.d.ts.map +1 -0
- package/dist/deep/graph-obfuscate.js +226 -0
- package/dist/deep/graph-obfuscate.js.map +1 -0
- package/dist/deep/graph-preprocess.d.ts +41 -0
- package/dist/deep/graph-preprocess.d.ts.map +1 -0
- package/dist/deep/graph-preprocess.js +32 -0
- package/dist/deep/graph-preprocess.js.map +1 -0
- package/dist/deep/graph-sampling.d.ts +51 -0
- package/dist/deep/graph-sampling.d.ts.map +1 -0
- package/dist/deep/graph-sampling.js +409 -0
- package/dist/deep/graph-sampling.js.map +1 -0
- package/dist/deep/graph.d.ts +24 -1
- package/dist/deep/graph.d.ts.map +1 -1
- package/dist/deep/graph.js +42 -1
- package/dist/deep/graph.js.map +1 -1
- package/dist/deep/iteration-data.d.ts +38 -0
- package/dist/deep/iteration-data.d.ts.map +1 -0
- package/dist/deep/iteration-data.js +48 -0
- package/dist/deep/iteration-data.js.map +1 -0
- package/dist/deep/path-extraction.d.ts +53 -0
- package/dist/deep/path-extraction.d.ts.map +1 -0
- package/dist/deep/path-extraction.js +229 -0
- package/dist/deep/path-extraction.js.map +1 -0
- package/dist/deep/quality-assess.d.ts +45 -0
- package/dist/deep/quality-assess.d.ts.map +1 -0
- package/dist/deep/quality-assess.js +125 -0
- package/dist/deep/quality-assess.js.map +1 -0
- package/dist/deep/query-redundancy.d.ts +39 -0
- package/dist/deep/query-redundancy.d.ts.map +1 -0
- package/dist/deep/query-redundancy.js +109 -0
- package/dist/deep/query-redundancy.js.map +1 -0
- package/dist/deep/query-suggest.d.ts +35 -0
- package/dist/deep/query-suggest.d.ts.map +1 -0
- package/dist/deep/query-suggest.js +98 -0
- package/dist/deep/query-suggest.js.map +1 -0
- package/dist/deep/recovery-analysis.d.ts +61 -0
- package/dist/deep/recovery-analysis.d.ts.map +1 -0
- package/dist/deep/recovery-analysis.js +188 -0
- package/dist/deep/recovery-analysis.js.map +1 -0
- package/dist/deep/search-strategy.d.ts +37 -0
- package/dist/deep/search-strategy.d.ts.map +1 -0
- package/dist/deep/search-strategy.js +99 -0
- package/dist/deep/search-strategy.js.map +1 -0
- package/dist/deep/session.d.ts +2 -0
- package/dist/deep/session.d.ts.map +1 -1
- package/dist/deep/session.js +1 -1
- package/dist/deep/session.js.map +1 -1
- package/dist/deep/simhash.d.ts +5 -0
- package/dist/deep/simhash.d.ts.map +1 -1
- package/dist/deep/simhash.js +16 -0
- package/dist/deep/simhash.js.map +1 -1
- package/dist/deep/tfidf.d.ts +38 -0
- package/dist/deep/tfidf.d.ts.map +1 -0
- package/dist/deep/tfidf.js +103 -0
- package/dist/deep/tfidf.js.map +1 -0
- package/dist/runCli.d.ts +3 -1
- package/dist/runCli.d.ts.map +1 -1
- package/dist/runCli.js +769 -307
- package/dist/runCli.js.map +1 -1
- package/package.json +9 -4
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Co-occurrence matrix and cross-results frequency statistics.
|
|
3
|
+
*
|
|
4
|
+
* Co-occurrence: word pairs that appear within the same result.
|
|
5
|
+
* Frequency: how many results contain each term.
|
|
6
|
+
* Both derived from TF-IDF tokenization output.
|
|
7
|
+
*/
|
|
8
|
+
import { DirectedGraph } from 'graphology';
|
|
9
|
+
import { GraphNodeAttrs, GraphEdgeAttrs } from './graph.js';
|
|
10
|
+
export interface CoOccurrencePair {
|
|
11
|
+
term1: string;
|
|
12
|
+
term2: string;
|
|
13
|
+
count: number;
|
|
14
|
+
}
|
|
15
|
+
export interface CoOccurrenceOutput {
|
|
16
|
+
pairs: CoOccurrencePair[];
|
|
17
|
+
truncated: boolean;
|
|
18
|
+
maxTerms: number;
|
|
19
|
+
}
|
|
20
|
+
/** Build co-occurrence matrix from results.
|
|
21
|
+
* Only results with `content` are processed.
|
|
22
|
+
* If unique terms > maxTerms, take the top by frequency and set truncated=true.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildCoOccurrence(results: Array<{
|
|
25
|
+
title?: string;
|
|
26
|
+
content?: string;
|
|
27
|
+
}>, opts?: {
|
|
28
|
+
threshold?: number;
|
|
29
|
+
maxTerms?: number;
|
|
30
|
+
}): CoOccurrenceOutput;
|
|
31
|
+
export interface TermFrequency {
|
|
32
|
+
term: string;
|
|
33
|
+
count: number;
|
|
34
|
+
}
|
|
35
|
+
/** Count how many results contain each term. */
|
|
36
|
+
export declare function computeCrossResultFrequency(results: Array<{
|
|
37
|
+
title?: string;
|
|
38
|
+
content?: string;
|
|
39
|
+
}>, opts?: {
|
|
40
|
+
top?: number;
|
|
41
|
+
}): TermFrequency[];
|
|
42
|
+
export interface EntityContext {
|
|
43
|
+
id: string;
|
|
44
|
+
label: string;
|
|
45
|
+
degree: number;
|
|
46
|
+
entityType?: string;
|
|
47
|
+
}
|
|
48
|
+
/** Get existing entity nodes from the graph with their on-demand degree. */
|
|
49
|
+
export declare function getExistingEntityContext(graph: DirectedGraph<GraphNodeAttrs, GraphEdgeAttrs>): EntityContext[];
|
|
50
|
+
//# sourceMappingURL=co-occurrence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"co-occurrence.d.ts","sourceRoot":"","sources":["../../src/deep/co-occurrence.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAK5D,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IAC/B,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAKD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC7B,OAAO,EAAE,KAAK,CAAC;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EACpD,IAAI,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD,kBAAkB,CAwDpB;AAID,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,gDAAgD;AAChD,wBAAgB,2BAA2B,CACvC,OAAO,EAAE,KAAK,CAAC;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,EACpD,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACxB,aAAa,EAAE,CAkBjB;AAID,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,4EAA4E;AAC5E,wBAAgB,wBAAwB,CACpC,KAAK,EAAE,aAAa,CAAC,cAAc,EAAE,cAAc,CAAC,GACrD,aAAa,EAAE,CAajB"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Co-occurrence matrix and cross-results frequency statistics.
|
|
3
|
+
*
|
|
4
|
+
* Co-occurrence: word pairs that appear within the same result.
|
|
5
|
+
* Frequency: how many results contain each term.
|
|
6
|
+
* Both derived from TF-IDF tokenization output.
|
|
7
|
+
*/
|
|
8
|
+
import { tokenize } from './tfidf.js';
|
|
9
|
+
import { getEntityDegree } from './degree-utils.js';
|
|
10
|
+
const MAX_TERMS_FOR_CO_OCCURRENCE = 50;
|
|
11
|
+
const DEFAULT_THRESHOLD = 2;
|
|
12
|
+
/** Build co-occurrence matrix from results.
|
|
13
|
+
* Only results with `content` are processed.
|
|
14
|
+
* If unique terms > maxTerms, take the top by frequency and set truncated=true.
|
|
15
|
+
*/
|
|
16
|
+
export function buildCoOccurrence(results, opts) {
|
|
17
|
+
const threshold = opts?.threshold ?? DEFAULT_THRESHOLD;
|
|
18
|
+
const maxTerms = opts?.maxTerms ?? MAX_TERMS_FOR_CO_OCCURRENCE;
|
|
19
|
+
const withContent = results.filter(r => r.content);
|
|
20
|
+
// Per-result term sets
|
|
21
|
+
const perResultTermSets = [];
|
|
22
|
+
const globalFreq = new Map(); // term → # results containing it
|
|
23
|
+
for (const result of withContent) {
|
|
24
|
+
const text = `${result.title || ''} ${result.content || ''}`;
|
|
25
|
+
const tokens = tokenize(text);
|
|
26
|
+
const termSet = new Set(tokens);
|
|
27
|
+
perResultTermSets.push(termSet);
|
|
28
|
+
for (const term of termSet) {
|
|
29
|
+
globalFreq.set(term, (globalFreq.get(term) || 0) + 1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Truncate to top maxTerms by frequency
|
|
33
|
+
const sortedTerms = Array.from(globalFreq.entries())
|
|
34
|
+
.sort((a, b) => b[1] - a[1])
|
|
35
|
+
.slice(0, maxTerms)
|
|
36
|
+
.map(([term]) => term);
|
|
37
|
+
const truncated = globalFreq.size > maxTerms;
|
|
38
|
+
const termIndex = new Set(sortedTerms);
|
|
39
|
+
// Count co-occurrences
|
|
40
|
+
const pairCounts = new Map();
|
|
41
|
+
for (const termSet of perResultTermSets) {
|
|
42
|
+
const filtered = sortedTerms.filter(t => termSet.has(t));
|
|
43
|
+
for (let i = 0; i < filtered.length; i++) {
|
|
44
|
+
for (let j = i + 1; j < filtered.length; j++) {
|
|
45
|
+
const key = filtered[i] < filtered[j]
|
|
46
|
+
? `${filtered[i]}|${filtered[j]}`
|
|
47
|
+
: `${filtered[j]}|${filtered[i]}`;
|
|
48
|
+
pairCounts.set(key, (pairCounts.get(key) || 0) + 1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const pairs = [];
|
|
53
|
+
for (const [key, count] of pairCounts) {
|
|
54
|
+
if (count >= threshold) {
|
|
55
|
+
const [term1, term2] = key.split('|');
|
|
56
|
+
pairs.push({ term1, term2, count });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
pairs.sort((a, b) => b.count - a.count);
|
|
60
|
+
return { pairs, truncated, maxTerms };
|
|
61
|
+
}
|
|
62
|
+
/** Count how many results contain each term. */
|
|
63
|
+
export function computeCrossResultFrequency(results, opts) {
|
|
64
|
+
const top = opts?.top ?? 50;
|
|
65
|
+
const withContent = results.filter(r => r.content);
|
|
66
|
+
const freq = new Map();
|
|
67
|
+
for (const result of withContent) {
|
|
68
|
+
const text = `${result.title || ''} ${result.content || ''}`;
|
|
69
|
+
const tokens = tokenize(text);
|
|
70
|
+
const seen = new Set(tokens);
|
|
71
|
+
for (const term of seen) {
|
|
72
|
+
freq.set(term, (freq.get(term) || 0) + 1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return Array.from(freq.entries())
|
|
76
|
+
.map(([term, count]) => ({ term, count }))
|
|
77
|
+
.sort((a, b) => b.count - a.count)
|
|
78
|
+
.slice(0, top);
|
|
79
|
+
}
|
|
80
|
+
/** Get existing entity nodes from the graph with their on-demand degree. */
|
|
81
|
+
export function getExistingEntityContext(graph) {
|
|
82
|
+
const entities = [];
|
|
83
|
+
graph.forEachNode((node, attrs) => {
|
|
84
|
+
if (attrs.type === 'entity') {
|
|
85
|
+
entities.push({
|
|
86
|
+
id: node,
|
|
87
|
+
label: attrs.label,
|
|
88
|
+
degree: getEntityDegree(graph, node),
|
|
89
|
+
entityType: attrs.entityType,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return entities.sort((a, b) => b.degree - a.degree);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=co-occurrence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"co-occurrence.js","sourceRoot":"","sources":["../../src/deep/co-occurrence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-demand degree computation for entity nodes.
|
|
3
|
+
*
|
|
4
|
+
* Degree is NOT stored in node attributes — it's computed on-the-fly
|
|
5
|
+
* from the graph topology using graphology's O(1) inDegree/outDegree.
|
|
6
|
+
* This ensures degree is always consistent with the current graph state.
|
|
7
|
+
*/
|
|
8
|
+
import { DirectedGraph } from 'graphology';
|
|
9
|
+
import { GraphNodeAttrs, GraphEdgeAttrs } from './graph.js';
|
|
10
|
+
/** Get the total degree (in + out) for a single node. O(1) via graphology. */
|
|
11
|
+
export declare function getEntityDegree(graph: DirectedGraph<GraphNodeAttrs, GraphEdgeAttrs>, nodeId: string): number;
|
|
12
|
+
/** Get degree for all entity nodes (type === 'entity') in the graph. */
|
|
13
|
+
export declare function getEntitiesWithDegrees(graph: DirectedGraph<GraphNodeAttrs, GraphEdgeAttrs>): Array<{
|
|
14
|
+
id: string;
|
|
15
|
+
degree: number;
|
|
16
|
+
label: string;
|
|
17
|
+
}>;
|
|
18
|
+
/** Compute adaptive degree range for filtering entity nodes.
|
|
19
|
+
* - d_max = min(50, floor(graph.order / 5))
|
|
20
|
+
* - d_min = 1 by default
|
|
21
|
+
* Small graphs auto-shrink: 30 nodes → d_max=6, 100 nodes → d_max=20
|
|
22
|
+
*/
|
|
23
|
+
export declare function adaptiveDegreeRange(graph: DirectedGraph<GraphNodeAttrs, GraphEdgeAttrs>, opts?: {
|
|
24
|
+
minDegree?: number;
|
|
25
|
+
maxDegreeCap?: number;
|
|
26
|
+
}): {
|
|
27
|
+
min: number;
|
|
28
|
+
max: number;
|
|
29
|
+
};
|
|
30
|
+
/** Filter entity nodes within the adaptive degree range. */
|
|
31
|
+
export declare function filterEntitiesByDegree(graph: DirectedGraph<GraphNodeAttrs, GraphEdgeAttrs>, opts?: {
|
|
32
|
+
minDegree?: number;
|
|
33
|
+
maxDegreeCap?: number;
|
|
34
|
+
}): Array<{
|
|
35
|
+
id: string;
|
|
36
|
+
degree: number;
|
|
37
|
+
label: string;
|
|
38
|
+
}>;
|
|
39
|
+
//# sourceMappingURL=degree-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"degree-utils.d.ts","sourceRoot":"","sources":["../../src/deep/degree-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5D,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,CAAC,cAAc,EAAE,cAAc,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE5G;AAED,wEAAwE;AACxE,wBAAgB,sBAAsB,CAClC,KAAK,EAAE,aAAa,CAAC,cAAc,EAAE,cAAc,CAAC,GACrD,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAYtD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAC/B,KAAK,EAAE,aAAa,CAAC,cAAc,EAAE,cAAc,CAAC,EACpD,IAAI,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GACrD;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAK9B;AAED,4DAA4D;AAC5D,wBAAgB,sBAAsB,CAClC,KAAK,EAAE,aAAa,CAAC,cAAc,EAAE,cAAc,CAAC,EACpD,IAAI,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GACrD,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAGtD"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-demand degree computation for entity nodes.
|
|
3
|
+
*
|
|
4
|
+
* Degree is NOT stored in node attributes — it's computed on-the-fly
|
|
5
|
+
* from the graph topology using graphology's O(1) inDegree/outDegree.
|
|
6
|
+
* This ensures degree is always consistent with the current graph state.
|
|
7
|
+
*/
|
|
8
|
+
/** Get the total degree (in + out) for a single node. O(1) via graphology. */
|
|
9
|
+
export function getEntityDegree(graph, nodeId) {
|
|
10
|
+
return graph.inDegree(nodeId) + graph.outDegree(nodeId);
|
|
11
|
+
}
|
|
12
|
+
/** Get degree for all entity nodes (type === 'entity') in the graph. */
|
|
13
|
+
export function getEntitiesWithDegrees(graph) {
|
|
14
|
+
const result = [];
|
|
15
|
+
graph.forEachNode((node, attrs) => {
|
|
16
|
+
if (attrs.type === 'entity') {
|
|
17
|
+
result.push({
|
|
18
|
+
id: node,
|
|
19
|
+
degree: getEntityDegree(graph, node),
|
|
20
|
+
label: attrs.label,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return result.sort((a, b) => b.degree - a.degree);
|
|
25
|
+
}
|
|
26
|
+
/** Compute adaptive degree range for filtering entity nodes.
|
|
27
|
+
* - d_max = min(50, floor(graph.order / 5))
|
|
28
|
+
* - d_min = 1 by default
|
|
29
|
+
* Small graphs auto-shrink: 30 nodes → d_max=6, 100 nodes → d_max=20
|
|
30
|
+
*/
|
|
31
|
+
export function adaptiveDegreeRange(graph, opts) {
|
|
32
|
+
const dMin = opts?.minDegree ?? 1;
|
|
33
|
+
const cap = opts?.maxDegreeCap ?? 50;
|
|
34
|
+
const dMax = Math.min(cap, Math.floor(graph.order / 5));
|
|
35
|
+
return { min: dMin, max: Math.max(dMax, dMin) };
|
|
36
|
+
}
|
|
37
|
+
/** Filter entity nodes within the adaptive degree range. */
|
|
38
|
+
export function filterEntitiesByDegree(graph, opts) {
|
|
39
|
+
const range = adaptiveDegreeRange(graph, opts);
|
|
40
|
+
return getEntitiesWithDegrees(graph).filter(e => e.degree >= range.min && e.degree <= range.max);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=degree-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"degree-utils.js","sourceRoot":"","sources":["../../src/deep/degree-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,8EAA8E;AAC9E,MAAM,UAAU,eAAe,CAAC,KAAoD,EAAE,MAAc;IAChG,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AAC5D,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,sBAAsB,CAClC,KAAoD;IAEpD,MAAM,MAAM,GAAyD,EAAE,CAAC;IACxE,KAAK,CAAC,WAAW,CAAC,CAAC,IAAY,EAAE,KAAqB,EAAE,EAAE;QACtD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC;gBACR,EAAE,EAAE,IAAI;gBACR,MAAM,EAAE,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC;gBACpC,KAAK,EAAE,KAAK,CAAC,KAAK;aACrB,CAAC,CAAC;QACP,CAAC;IACL,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;AACtD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAC/B,KAAoD,EACpD,IAAoD;IAEpD,MAAM,IAAI,GAAG,IAAI,EAAE,SAAS,IAAI,CAAC,CAAC;IAClC,MAAM,GAAG,GAAG,IAAI,EAAE,YAAY,IAAI,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IACxD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;AACpD,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,sBAAsB,CAClC,KAAoD,EACpD,IAAoD;IAEpD,MAAM,KAAK,GAAG,mBAAmB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC/C,OAAO,sBAAsB,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;AACrG,CAAC"}
|
package/dist/deep/extractor.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Content extraction
|
|
2
|
+
* Content extraction: Defuddle (linkedom) → Obscura fallback
|
|
3
3
|
*/
|
|
4
4
|
export interface ExtractorOptions {
|
|
5
5
|
timeoutMs?: number;
|
|
6
6
|
concurrency?: number;
|
|
7
7
|
maxResponseBytes?: number;
|
|
8
|
+
obscura?: boolean;
|
|
9
|
+
obscuraPath?: string;
|
|
10
|
+
obscuraDumpFormat?: 'html' | 'markdown';
|
|
8
11
|
}
|
|
9
12
|
export interface ExtractedContent {
|
|
10
13
|
title: string;
|
|
@@ -16,14 +19,19 @@ export interface ExtractedContent {
|
|
|
16
19
|
length: number;
|
|
17
20
|
extractedAt: number;
|
|
18
21
|
error?: string;
|
|
22
|
+
method?: 'defuddle' | 'obscura';
|
|
19
23
|
}
|
|
20
24
|
export declare class ContentExtractor {
|
|
21
25
|
private timeoutMs;
|
|
22
26
|
private concurrency;
|
|
23
27
|
private maxResponseBytes;
|
|
28
|
+
private useObscura;
|
|
29
|
+
private obscuraPath?;
|
|
30
|
+
private obscuraDumpFormat;
|
|
24
31
|
constructor(options?: ExtractorOptions);
|
|
25
32
|
extract(url: string): Promise<ExtractedContent>;
|
|
26
|
-
|
|
33
|
+
private fetchHtml;
|
|
34
|
+
extractFromHtml(html: string, url: string): Promise<ExtractedContent>;
|
|
27
35
|
extractBatch(urls: string[]): Promise<ExtractedContent[]>;
|
|
28
36
|
}
|
|
29
37
|
//# sourceMappingURL=extractor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/deep/extractor.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/deep/extractor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAuBH,MAAM,WAAW,gBAAgB;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;CAC3C;AAED,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACnC;AA8GD,qBAAa,gBAAgB;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,iBAAiB,CAAsB;gBAEnC,OAAO,CAAC,EAAE,gBAAgB;IAShC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAkCvC,SAAS;IAyBjB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAIrE,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;CASlE"}
|
package/dist/deep/extractor.js
CHANGED
|
@@ -1,65 +1,158 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Content extraction
|
|
2
|
+
* Content extraction: Defuddle (linkedom) → Obscura fallback
|
|
3
3
|
*/
|
|
4
4
|
import { parseHTML } from 'linkedom';
|
|
5
|
-
import {
|
|
5
|
+
import { Defuddle } from 'defuddle/node';
|
|
6
|
+
import { execFile } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
6
11
|
const DEFAULT_TIMEOUT_MS = 10_000;
|
|
7
12
|
const DEFAULT_CONCURRENCY = 3;
|
|
8
|
-
const MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
13
|
+
const MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
|
|
14
|
+
const MIN_CONTENT_LENGTH = 100;
|
|
15
|
+
const OBSCURA_THRESHOLD = 50;
|
|
16
|
+
const OBSCURA_SEARCH_PATHS = [
|
|
17
|
+
'obscura',
|
|
18
|
+
join(homedir(), '.local/bin/obscura'),
|
|
19
|
+
'/usr/local/bin/obscura',
|
|
20
|
+
];
|
|
21
|
+
let _obscuraAvailable = null;
|
|
22
|
+
async function findObscura(path) {
|
|
23
|
+
const candidates = path ? [path] : OBSCURA_SEARCH_PATHS;
|
|
24
|
+
for (const candidate of candidates) {
|
|
25
|
+
try {
|
|
26
|
+
await execFileAsync(candidate, ['--version'], { timeout: 5_000 });
|
|
27
|
+
return candidate;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
async function isObscuraAvailable(path) {
|
|
36
|
+
if (_obscuraAvailable !== null && !path)
|
|
37
|
+
return _obscuraAvailable;
|
|
38
|
+
const found = await findObscura(path);
|
|
39
|
+
const available = found !== null;
|
|
40
|
+
if (!path)
|
|
41
|
+
_obscuraAvailable = available;
|
|
42
|
+
return available;
|
|
43
|
+
}
|
|
44
|
+
async function defuddleExtract(html, url) {
|
|
45
|
+
try {
|
|
46
|
+
const { document } = parseHTML(html);
|
|
47
|
+
const result = await Defuddle(document, url, {
|
|
48
|
+
markdown: true,
|
|
49
|
+
useAsync: false,
|
|
50
|
+
});
|
|
51
|
+
const content = (result.content || '').trim();
|
|
52
|
+
if (!content) {
|
|
53
|
+
return {
|
|
54
|
+
title: result.title || '',
|
|
55
|
+
content: '',
|
|
56
|
+
excerpt: result.description || '',
|
|
57
|
+
url,
|
|
58
|
+
byline: result.author || undefined,
|
|
59
|
+
siteName: result.site || undefined,
|
|
60
|
+
length: 0,
|
|
61
|
+
extractedAt: Date.now(),
|
|
62
|
+
method: 'defuddle',
|
|
63
|
+
error: 'Defuddle could not extract content',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
title: result.title || '',
|
|
68
|
+
content,
|
|
69
|
+
excerpt: result.description || '',
|
|
70
|
+
url,
|
|
71
|
+
byline: result.author || undefined,
|
|
72
|
+
siteName: result.site || undefined,
|
|
73
|
+
length: result.wordCount || content.length,
|
|
74
|
+
extractedAt: Date.now(),
|
|
75
|
+
method: 'defuddle',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return {
|
|
80
|
+
title: '',
|
|
81
|
+
content: '',
|
|
82
|
+
excerpt: '',
|
|
83
|
+
url,
|
|
84
|
+
length: 0,
|
|
85
|
+
extractedAt: Date.now(),
|
|
86
|
+
method: 'defuddle',
|
|
87
|
+
error: error instanceof Error ? error.message : 'Defuddle parsing failed',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function obscuraExtract(url, options) {
|
|
92
|
+
const obscuraBin = await findObscura(options.obscuraPath);
|
|
93
|
+
if (!obscuraBin)
|
|
94
|
+
return null;
|
|
95
|
+
try {
|
|
96
|
+
const dumpFormat = options.dumpFormat ?? 'html';
|
|
97
|
+
const timeoutSec = Math.ceil(options.timeoutMs / 1000);
|
|
98
|
+
const { stdout } = await execFileAsync(obscuraBin, ['fetch', url, '--dump', dumpFormat, '--timeout', String(timeoutSec)], { timeout: options.timeoutMs + 5_000, maxBuffer: 10 * 1024 * 1024 });
|
|
99
|
+
if (!stdout || !stdout.trim())
|
|
100
|
+
return null;
|
|
101
|
+
if (dumpFormat === 'markdown') {
|
|
102
|
+
return {
|
|
103
|
+
title: '',
|
|
104
|
+
content: stdout.trim(),
|
|
105
|
+
excerpt: '',
|
|
106
|
+
url,
|
|
107
|
+
length: stdout.trim().length,
|
|
108
|
+
extractedAt: Date.now(),
|
|
109
|
+
method: 'obscura',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const html = stdout.trim();
|
|
113
|
+
const result = await defuddleExtract(html, url);
|
|
114
|
+
result.method = 'obscura';
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
9
121
|
export class ContentExtractor {
|
|
10
122
|
timeoutMs;
|
|
11
123
|
concurrency;
|
|
12
124
|
maxResponseBytes;
|
|
125
|
+
useObscura;
|
|
126
|
+
obscuraPath;
|
|
127
|
+
obscuraDumpFormat;
|
|
13
128
|
constructor(options) {
|
|
14
129
|
this.timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
15
130
|
this.concurrency = options?.concurrency ?? DEFAULT_CONCURRENCY;
|
|
16
131
|
this.maxResponseBytes = options?.maxResponseBytes ?? MAX_RESPONSE_BYTES;
|
|
132
|
+
this.useObscura = options?.obscura ?? false;
|
|
133
|
+
this.obscuraPath = options?.obscuraPath;
|
|
134
|
+
this.obscuraDumpFormat = options?.obscuraDumpFormat ?? 'html';
|
|
17
135
|
}
|
|
18
136
|
async extract(url) {
|
|
19
137
|
try {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
length: 0,
|
|
34
|
-
extractedAt: Date.now(),
|
|
35
|
-
error: `HTTP ${response.status}: ${response.statusText}`,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
const contentLength = response.headers.get('content-length');
|
|
39
|
-
if (contentLength && parseInt(contentLength, 10) > this.maxResponseBytes) {
|
|
40
|
-
return {
|
|
41
|
-
title: '',
|
|
42
|
-
content: '',
|
|
43
|
-
excerpt: '',
|
|
44
|
-
url,
|
|
45
|
-
length: 0,
|
|
46
|
-
extractedAt: Date.now(),
|
|
47
|
-
error: `Response too large: ${contentLength} bytes (max ${this.maxResponseBytes})`,
|
|
48
|
-
};
|
|
138
|
+
const html = await this.fetchHtml(url);
|
|
139
|
+
const result = await defuddleExtract(html, url);
|
|
140
|
+
if (result.content.length >= MIN_CONTENT_LENGTH)
|
|
141
|
+
return result;
|
|
142
|
+
// Defuddle insufficient → Obscura fallback
|
|
143
|
+
if (this.useObscura && result.content.length < OBSCURA_THRESHOLD) {
|
|
144
|
+
const obsResult = await obscuraExtract(url, {
|
|
145
|
+
timeoutMs: this.timeoutMs,
|
|
146
|
+
obscuraPath: this.obscuraPath,
|
|
147
|
+
dumpFormat: this.obscuraDumpFormat,
|
|
148
|
+
});
|
|
149
|
+
if (obsResult && obsResult.content.length > result.content.length)
|
|
150
|
+
return obsResult;
|
|
49
151
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
title: '',
|
|
54
|
-
content: '',
|
|
55
|
-
excerpt: '',
|
|
56
|
-
url,
|
|
57
|
-
length: 0,
|
|
58
|
-
extractedAt: Date.now(),
|
|
59
|
-
error: `Response too large: ${html.length} bytes (max ${this.maxResponseBytes})`,
|
|
60
|
-
};
|
|
152
|
+
if (result.content.length < MIN_CONTENT_LENGTH && !result.error) {
|
|
153
|
+
result.error = 'Extracted content too short';
|
|
61
154
|
}
|
|
62
|
-
return
|
|
155
|
+
return result;
|
|
63
156
|
}
|
|
64
157
|
catch (error) {
|
|
65
158
|
return {
|
|
@@ -73,44 +166,29 @@ export class ContentExtractor {
|
|
|
73
166
|
};
|
|
74
167
|
}
|
|
75
168
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
url,
|
|
87
|
-
length: 0,
|
|
88
|
-
extractedAt: Date.now(),
|
|
89
|
-
error: 'Readability could not extract content',
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
title: article.title || '',
|
|
94
|
-
content: article.textContent.replace(/\n{3,}/g, '\n\n').trim(),
|
|
95
|
-
excerpt: article.excerpt || '',
|
|
96
|
-
url,
|
|
97
|
-
byline: article.byline || undefined,
|
|
98
|
-
siteName: article.siteName || undefined,
|
|
99
|
-
length: article.length || article.textContent.length,
|
|
100
|
-
extractedAt: Date.now(),
|
|
101
|
-
};
|
|
169
|
+
async fetchHtml(url) {
|
|
170
|
+
const response = await fetch(url, {
|
|
171
|
+
headers: {
|
|
172
|
+
'User-Agent': 'Mozilla/5.0 (compatible; SxngDeepSearch/1.0)',
|
|
173
|
+
'Accept': 'text/html,application/xhtml+xml',
|
|
174
|
+
},
|
|
175
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
176
|
+
});
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
102
179
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
content: '',
|
|
107
|
-
excerpt: '',
|
|
108
|
-
url,
|
|
109
|
-
length: 0,
|
|
110
|
-
extractedAt: Date.now(),
|
|
111
|
-
error: error instanceof Error ? error.message : 'HTML parsing failed',
|
|
112
|
-
};
|
|
180
|
+
const contentLength = response.headers.get('content-length');
|
|
181
|
+
if (contentLength && parseInt(contentLength, 10) > this.maxResponseBytes) {
|
|
182
|
+
throw new Error(`Response too large: ${contentLength} bytes (max ${this.maxResponseBytes})`);
|
|
113
183
|
}
|
|
184
|
+
const html = await response.text();
|
|
185
|
+
if (html.length > this.maxResponseBytes) {
|
|
186
|
+
throw new Error(`Response too large: ${html.length} bytes (max ${this.maxResponseBytes})`);
|
|
187
|
+
}
|
|
188
|
+
return html;
|
|
189
|
+
}
|
|
190
|
+
async extractFromHtml(html, url) {
|
|
191
|
+
return defuddleExtract(html, url);
|
|
114
192
|
}
|
|
115
193
|
async extractBatch(urls) {
|
|
116
194
|
const results = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractor.js","sourceRoot":"","sources":["../../src/deep/extractor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"extractor.js","sourceRoot":"","sources":["../../src/deep/extractor.ts"],"names":[],"mappings":"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"}
|