@kolmo/scout 0.1.0
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/dist/api/kolmoApiClient.d.ts +2 -0
- package/dist/api/kolmoApiClient.js +25 -0
- package/dist/commands/auth.d.ts +6 -0
- package/dist/commands/auth.js +41 -0
- package/dist/commands/explain.d.ts +2 -0
- package/dist/commands/explain.js +95 -0
- package/dist/commands/scan.d.ts +2 -0
- package/dist/commands/scan.js +151 -0
- package/dist/commands/sync.d.ts +2 -0
- package/dist/commands/sync.js +50 -0
- package/dist/config/authConfig.d.ts +8 -0
- package/dist/config/authConfig.js +26 -0
- package/dist/config/writeKolmoConfig.d.ts +2 -0
- package/dist/config/writeKolmoConfig.js +6 -0
- package/dist/detectors/assetDetector.d.ts +8 -0
- package/dist/detectors/assetDetector.js +7 -0
- package/dist/detectors/fileTypeDetector.d.ts +1 -0
- package/dist/detectors/fileTypeDetector.js +38 -0
- package/dist/detectors/projectTypeDetector.d.ts +4 -0
- package/dist/detectors/projectTypeDetector.js +36 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +65 -0
- package/dist/manifest/buildScoutManifest.d.ts +15 -0
- package/dist/manifest/buildScoutManifest.js +219 -0
- package/dist/profilers/tabularProfiler.d.ts +15 -0
- package/dist/profilers/tabularProfiler.js +131 -0
- package/dist/scanners/contentScanner.d.ts +6 -0
- package/dist/scanners/contentScanner.js +22 -0
- package/dist/scanners/csvScanner.d.ts +5 -0
- package/dist/scanners/csvScanner.js +26 -0
- package/dist/scanners/excelScanner.d.ts +5 -0
- package/dist/scanners/excelScanner.js +25 -0
- package/dist/scanners/fileScanner.d.ts +1 -0
- package/dist/scanners/fileScanner.js +24 -0
- package/dist/scanners/notebookScanner.d.ts +5 -0
- package/dist/scanners/notebookScanner.js +32 -0
- package/dist/scanners/packageScanner.d.ts +4 -0
- package/dist/scanners/packageScanner.js +46 -0
- package/dist/scanners/pythonScanner.d.ts +4 -0
- package/dist/scanners/pythonScanner.js +30 -0
- package/dist/summarizers/repoSummary.d.ts +16 -0
- package/dist/summarizers/repoSummary.js +101 -0
- package/dist/taxonomy/energyTaxonomy.d.ts +27 -0
- package/dist/taxonomy/energyTaxonomy.js +250 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.js +1 -0
- package/dist/workflows/workflowSuggestions.d.ts +2 -0
- package/dist/workflows/workflowSuggestions.js +61 -0
- package/package.json +38 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const PROJECT_TYPE_LABELS = {
|
|
2
|
+
data_folder_project: 'data folder',
|
|
3
|
+
fastapi_backend_project: 'FastAPI backend',
|
|
4
|
+
frontend_project: 'frontend application',
|
|
5
|
+
generic_project: 'general analytics project',
|
|
6
|
+
notebook_research_project: 'notebook research project',
|
|
7
|
+
python_analytics_project: 'Python analytics project',
|
|
8
|
+
supabase_project: 'Supabase-backed project',
|
|
9
|
+
};
|
|
10
|
+
const WORKFLOW_LABELS = {
|
|
11
|
+
carbon_emissions_analysis: 'carbon emissions analysis',
|
|
12
|
+
crude_oil_price_analysis: 'crude oil price analysis',
|
|
13
|
+
data_quality_check: 'data quality checks',
|
|
14
|
+
general_energy_analysis: 'general energy analysis',
|
|
15
|
+
lng_market_analysis: 'LNG market analysis',
|
|
16
|
+
natural_gas_analysis: 'natural gas analysis',
|
|
17
|
+
notebook_review: 'notebook review',
|
|
18
|
+
power_market_analysis: 'power market analysis',
|
|
19
|
+
python_model_review: 'Python model review',
|
|
20
|
+
};
|
|
21
|
+
export function buildRepoSummary(input) {
|
|
22
|
+
const fileCount = input.detectedFiles.length;
|
|
23
|
+
const fileTypeCounts = countBy(input.detectedFiles.map((file) => file.detected_type));
|
|
24
|
+
const topFileTypes = topEntries(fileTypeCounts, 3).filter(([type]) => type !== 'unknown');
|
|
25
|
+
const dataFileCount = input.detectedFiles.filter((file) => ['.csv', '.xls', '.xlsx'].includes(file.extension.toLowerCase())).length;
|
|
26
|
+
const pythonFileCount = input.detectedFiles.filter((file) => file.extension === '.py').length;
|
|
27
|
+
const notebookCount = input.detectedFiles.filter((file) => file.extension === '.ipynb').length;
|
|
28
|
+
const signals = unique(input.detectedFiles.flatMap((file) => file.signals));
|
|
29
|
+
const packages = unique([
|
|
30
|
+
...input.detectedPackages.python.slice(0, 4),
|
|
31
|
+
...input.detectedPackages.node.slice(0, 4),
|
|
32
|
+
]);
|
|
33
|
+
const projectLabel = PROJECT_TYPE_LABELS[input.projectType] ?? input.projectType.replace(/_/g, ' ');
|
|
34
|
+
const assetsText = input.detectedAssets.length
|
|
35
|
+
? `focused on ${formatAssetList(input.detailedAssets ?? [], input.detectedAssets)}`
|
|
36
|
+
: 'with no specific energy asset detected yet';
|
|
37
|
+
const workflowText = input.suggestedWorkflows.length
|
|
38
|
+
? formatList(input.suggestedWorkflows.map((workflow) => WORKFLOW_LABELS[workflow] ?? workflow))
|
|
39
|
+
: 'general energy analysis';
|
|
40
|
+
const details = [];
|
|
41
|
+
if (dataFileCount > 0)
|
|
42
|
+
details.push(`${dataFileCount} structured data ${plural(dataFileCount, 'file')}`);
|
|
43
|
+
if (pythonFileCount > 0)
|
|
44
|
+
details.push(`${pythonFileCount} Python ${plural(pythonFileCount, 'file')}`);
|
|
45
|
+
if (notebookCount > 0)
|
|
46
|
+
details.push(`${notebookCount} notebook${notebookCount === 1 ? '' : 's'}`);
|
|
47
|
+
if (topFileTypes.length > 0) {
|
|
48
|
+
details.push(`detected content such as ${formatList(topFileTypes.map(([type]) => type.replace(/_/g, ' ')))}`);
|
|
49
|
+
}
|
|
50
|
+
if (signals.length > 0) {
|
|
51
|
+
details.push(`signals around ${formatList(signals.slice(0, 5))}`);
|
|
52
|
+
}
|
|
53
|
+
if (packages.length > 0) {
|
|
54
|
+
details.push(`a stack using ${formatList(packages)}`);
|
|
55
|
+
}
|
|
56
|
+
const detailText = details.length
|
|
57
|
+
? ` The scan reviewed ${fileCount} ${plural(fileCount, 'file')} and found ${details.join(', ')}.`
|
|
58
|
+
: ` The scan reviewed ${fileCount} ${plural(fileCount, 'file')}, but not enough domain structure to classify the strategy deeply yet.`;
|
|
59
|
+
return (`Kolmo understands "${input.projectName}" as a ${projectLabel} ${assetsText}.` +
|
|
60
|
+
detailText +
|
|
61
|
+
` Recommended next workflows are ${workflowText}.` +
|
|
62
|
+
buildEvidenceSentence(input.evidenceCitations ?? []));
|
|
63
|
+
}
|
|
64
|
+
function countBy(values) {
|
|
65
|
+
const counts = new Map();
|
|
66
|
+
for (const value of values) {
|
|
67
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
68
|
+
}
|
|
69
|
+
return counts;
|
|
70
|
+
}
|
|
71
|
+
function topEntries(counts, limit) {
|
|
72
|
+
return Array.from(counts.entries())
|
|
73
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
74
|
+
.slice(0, limit);
|
|
75
|
+
}
|
|
76
|
+
function unique(values) {
|
|
77
|
+
return Array.from(new Set(values.filter(Boolean))).sort();
|
|
78
|
+
}
|
|
79
|
+
function formatList(values) {
|
|
80
|
+
if (values.length === 0)
|
|
81
|
+
return '';
|
|
82
|
+
if (values.length === 1)
|
|
83
|
+
return values[0];
|
|
84
|
+
if (values.length === 2)
|
|
85
|
+
return `${values[0]} and ${values[1]}`;
|
|
86
|
+
return `${values.slice(0, -1).join(', ')}, and ${values[values.length - 1]}`;
|
|
87
|
+
}
|
|
88
|
+
function plural(count, noun) {
|
|
89
|
+
return count === 1 ? noun : `${noun}s`;
|
|
90
|
+
}
|
|
91
|
+
function formatAssetList(detailedAssets, symbols) {
|
|
92
|
+
if (detailedAssets.length === 0)
|
|
93
|
+
return formatList(symbols);
|
|
94
|
+
const labels = detailedAssets.map((asset) => asset.inferred ? `${asset.symbol} (inferred from "crude")` : asset.symbol);
|
|
95
|
+
return formatList(labels);
|
|
96
|
+
}
|
|
97
|
+
function buildEvidenceSentence(citations) {
|
|
98
|
+
if (citations.length === 0)
|
|
99
|
+
return '';
|
|
100
|
+
return ` Evidence: ${citations.slice(0, 3).join('; ')}.`;
|
|
101
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ScoutAssetInfo } from '../types.js';
|
|
2
|
+
export interface EnergyAssetDefinition {
|
|
3
|
+
symbol: string;
|
|
4
|
+
family: string;
|
|
5
|
+
exchange?: string;
|
|
6
|
+
region?: string;
|
|
7
|
+
hub?: string;
|
|
8
|
+
unit?: string;
|
|
9
|
+
aliases: string[];
|
|
10
|
+
}
|
|
11
|
+
export interface ParsedTenor {
|
|
12
|
+
raw: string;
|
|
13
|
+
kind: 'relative_month' | 'month' | 'quarter' | 'calendar_year';
|
|
14
|
+
label: string;
|
|
15
|
+
year?: number;
|
|
16
|
+
month?: number;
|
|
17
|
+
quarter?: number;
|
|
18
|
+
sequence?: number;
|
|
19
|
+
}
|
|
20
|
+
export declare const ENERGY_ASSETS: EnergyAssetDefinition[];
|
|
21
|
+
export declare function detectEnergyAssets(input: {
|
|
22
|
+
file?: string;
|
|
23
|
+
filenames?: string[];
|
|
24
|
+
columnNames?: string[];
|
|
25
|
+
textValues?: string[];
|
|
26
|
+
}): ScoutAssetInfo[];
|
|
27
|
+
export declare function parseTenor(value: string): ParsedTenor | null;
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
export const ENERGY_ASSETS = [
|
|
2
|
+
{
|
|
3
|
+
symbol: 'BRENT',
|
|
4
|
+
family: 'crude_oil',
|
|
5
|
+
exchange: 'ICE',
|
|
6
|
+
region: 'North Sea',
|
|
7
|
+
hub: 'BFOE',
|
|
8
|
+
unit: 'bbl',
|
|
9
|
+
aliases: ['brent', 'bfoe', 'ice brent', 'brn'],
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
symbol: 'WTI',
|
|
13
|
+
family: 'crude_oil',
|
|
14
|
+
exchange: 'NYMEX',
|
|
15
|
+
region: 'United States',
|
|
16
|
+
hub: 'Cushing',
|
|
17
|
+
unit: 'bbl',
|
|
18
|
+
aliases: ['wti', 'west texas', 'nymex crude', 'cl futures', 'cl_'],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
symbol: 'TTF',
|
|
22
|
+
family: 'natural_gas',
|
|
23
|
+
exchange: 'ICE',
|
|
24
|
+
region: 'Netherlands',
|
|
25
|
+
hub: 'Title Transfer Facility',
|
|
26
|
+
unit: 'MWh',
|
|
27
|
+
aliases: ['ttf', 'dutch gas', 'ice endex ttf'],
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
symbol: 'NBP',
|
|
31
|
+
family: 'natural_gas',
|
|
32
|
+
exchange: 'ICE',
|
|
33
|
+
region: 'United Kingdom',
|
|
34
|
+
hub: 'National Balancing Point',
|
|
35
|
+
unit: 'therm',
|
|
36
|
+
aliases: ['nbp', 'uk gas', 'national balancing point'],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
symbol: 'JKM',
|
|
40
|
+
family: 'lng',
|
|
41
|
+
exchange: 'ICE',
|
|
42
|
+
region: 'North Asia',
|
|
43
|
+
hub: 'Japan Korea Marker',
|
|
44
|
+
unit: 'MMBtu',
|
|
45
|
+
aliases: ['jkm', 'japan korea marker', 'lng'],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
symbol: 'RBOB',
|
|
49
|
+
family: 'refined_products',
|
|
50
|
+
exchange: 'NYMEX',
|
|
51
|
+
region: 'United States',
|
|
52
|
+
hub: 'New York Harbor',
|
|
53
|
+
unit: 'gal',
|
|
54
|
+
aliases: ['rbob', 'gasoline', 'nymex rbob'],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
symbol: 'ULSD',
|
|
58
|
+
family: 'refined_products',
|
|
59
|
+
exchange: 'NYMEX',
|
|
60
|
+
region: 'United States',
|
|
61
|
+
hub: 'New York Harbor',
|
|
62
|
+
unit: 'gal',
|
|
63
|
+
aliases: ['ulsd', 'diesel', 'heating oil', 'nymex ho'],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
symbol: 'GASOIL',
|
|
67
|
+
family: 'refined_products',
|
|
68
|
+
exchange: 'ICE',
|
|
69
|
+
region: 'Europe',
|
|
70
|
+
hub: 'ARA',
|
|
71
|
+
unit: 'tonne',
|
|
72
|
+
aliases: ['gasoil', 'ice gasoil'],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
symbol: 'NG',
|
|
76
|
+
family: 'natural_gas',
|
|
77
|
+
exchange: 'NYMEX',
|
|
78
|
+
region: 'United States',
|
|
79
|
+
hub: 'Henry Hub',
|
|
80
|
+
unit: 'MMBtu',
|
|
81
|
+
aliases: ['henry hub', 'natural gas', 'natgas', 'ng futures', 'ng_'],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
symbol: 'EUA',
|
|
85
|
+
family: 'carbon',
|
|
86
|
+
exchange: 'ICE',
|
|
87
|
+
region: 'European Union',
|
|
88
|
+
hub: 'EU ETS',
|
|
89
|
+
unit: 'tCO2e',
|
|
90
|
+
aliases: ['eua', 'carbon', 'emissions', 'eu ets'],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
symbol: 'POWER',
|
|
94
|
+
family: 'power',
|
|
95
|
+
region: 'Europe',
|
|
96
|
+
unit: 'MWh',
|
|
97
|
+
aliases: ['power', 'electricity', 'baseload', 'peakload'],
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
const MONTHS = {
|
|
101
|
+
jan: 1,
|
|
102
|
+
feb: 2,
|
|
103
|
+
mar: 3,
|
|
104
|
+
apr: 4,
|
|
105
|
+
may: 5,
|
|
106
|
+
jun: 6,
|
|
107
|
+
jul: 7,
|
|
108
|
+
aug: 8,
|
|
109
|
+
sep: 9,
|
|
110
|
+
sept: 9,
|
|
111
|
+
oct: 10,
|
|
112
|
+
nov: 11,
|
|
113
|
+
dec: 12,
|
|
114
|
+
};
|
|
115
|
+
export function detectEnergyAssets(input) {
|
|
116
|
+
const evidenceBySymbol = new Map();
|
|
117
|
+
const values = [
|
|
118
|
+
...(input.filenames ?? []).map((value) => ({ source: 'filename', value })),
|
|
119
|
+
...(input.columnNames ?? []).map((value) => ({ source: 'column', value })),
|
|
120
|
+
...(input.textValues ?? []).map((value) => ({ source: 'text', value })),
|
|
121
|
+
];
|
|
122
|
+
for (const asset of ENERGY_ASSETS) {
|
|
123
|
+
for (const { source, value } of values) {
|
|
124
|
+
const matchedAlias = asset.aliases.find((alias) => matchesAlias(value, alias));
|
|
125
|
+
if (!matchedAlias)
|
|
126
|
+
continue;
|
|
127
|
+
const evidence = evidenceBySymbol.get(asset.symbol) ?? [];
|
|
128
|
+
evidence.push({
|
|
129
|
+
source,
|
|
130
|
+
value,
|
|
131
|
+
pattern: matchedAlias,
|
|
132
|
+
file: input.file,
|
|
133
|
+
});
|
|
134
|
+
evidenceBySymbol.set(asset.symbol, evidence);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Bare "crude" fallback — if no WTI or BRENT was explicitly matched but the word "crude"
|
|
138
|
+
// appears without "brent" context, default to WTI (US market convention).
|
|
139
|
+
const inferredSymbols = new Set();
|
|
140
|
+
const crudeSignal = values.find(({ value }) => {
|
|
141
|
+
const norm = normalize(value);
|
|
142
|
+
return /\bcrude\b/.test(norm) && !/\bbrent\b/.test(norm);
|
|
143
|
+
});
|
|
144
|
+
if (crudeSignal && !evidenceBySymbol.has('WTI')) {
|
|
145
|
+
inferredSymbols.add('WTI');
|
|
146
|
+
evidenceBySymbol.set('WTI', [{
|
|
147
|
+
source: crudeSignal.source,
|
|
148
|
+
value: crudeSignal.value,
|
|
149
|
+
pattern: 'crude',
|
|
150
|
+
file: input.file,
|
|
151
|
+
}]);
|
|
152
|
+
}
|
|
153
|
+
return Array.from(evidenceBySymbol.entries())
|
|
154
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
155
|
+
.map(([symbol, evidence]) => {
|
|
156
|
+
const asset = ENERGY_ASSETS.find((candidate) => candidate.symbol === symbol);
|
|
157
|
+
const inferred = inferredSymbols.has(symbol);
|
|
158
|
+
return {
|
|
159
|
+
symbol,
|
|
160
|
+
family: asset?.family ?? 'unknown',
|
|
161
|
+
exchange: asset?.exchange,
|
|
162
|
+
region: asset?.region,
|
|
163
|
+
hub: asset?.hub,
|
|
164
|
+
unit: asset?.unit,
|
|
165
|
+
evidence: dedupeEvidence(evidence),
|
|
166
|
+
inferred: inferred || undefined,
|
|
167
|
+
inference_note: inferred
|
|
168
|
+
? `"crude" detected — defaulting to WTI (US convention). Update to BRENT if this is European crude.`
|
|
169
|
+
: undefined,
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
export function parseTenor(value) {
|
|
174
|
+
const raw = value.trim();
|
|
175
|
+
if (!raw)
|
|
176
|
+
return null;
|
|
177
|
+
const lower = raw.toLowerCase();
|
|
178
|
+
const relativeMonth = lower.match(/\bm\s*([1-9][0-9]?)\b/);
|
|
179
|
+
if (relativeMonth) {
|
|
180
|
+
const sequence = Number(relativeMonth[1]);
|
|
181
|
+
return { raw, kind: 'relative_month', label: `M${sequence}`, sequence };
|
|
182
|
+
}
|
|
183
|
+
const calendarYear = lower.match(/\bcal(?:endar)?[-_\s']?([0-9]{2}|20[0-9]{2})\b/);
|
|
184
|
+
if (calendarYear) {
|
|
185
|
+
const year = normalizeYear(calendarYear[1]);
|
|
186
|
+
return { raw, kind: 'calendar_year', label: `Cal${String(year).slice(-2)}`, year };
|
|
187
|
+
}
|
|
188
|
+
const quarter = lower.match(/\bq([1-4])[-_\s']?([0-9]{2}|20[0-9]{2})\b/);
|
|
189
|
+
if (quarter) {
|
|
190
|
+
const quarterNumber = Number(quarter[1]);
|
|
191
|
+
const year = normalizeYear(quarter[2]);
|
|
192
|
+
return {
|
|
193
|
+
raw,
|
|
194
|
+
kind: 'quarter',
|
|
195
|
+
label: `Q${quarterNumber}-${String(year).slice(-2)}`,
|
|
196
|
+
quarter: quarterNumber,
|
|
197
|
+
year,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const month = lower.match(/\b(jan|feb|mar|apr|may|jun|jul|aug|sep|sept|oct|nov|dec)[a-z]*[-_\s']?([0-9]{2}|20[0-9]{2})\b/);
|
|
201
|
+
if (month) {
|
|
202
|
+
const monthNumber = MONTHS[month[1]];
|
|
203
|
+
const year = normalizeYear(month[2]);
|
|
204
|
+
return {
|
|
205
|
+
raw,
|
|
206
|
+
kind: 'month',
|
|
207
|
+
label: `${monthLabel(monthNumber)}${String(year).slice(-2)}`,
|
|
208
|
+
month: monthNumber,
|
|
209
|
+
year,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
function matchesAlias(value, alias) {
|
|
215
|
+
const normalizedValue = normalize(value);
|
|
216
|
+
const normalizedAlias = normalize(alias);
|
|
217
|
+
if (!normalizedAlias)
|
|
218
|
+
return false;
|
|
219
|
+
if (alias.endsWith('_')) {
|
|
220
|
+
const compactAlias = alias.toLowerCase();
|
|
221
|
+
const baseAlias = normalize(alias.replace(/_+$/g, ''));
|
|
222
|
+
return (value.toLowerCase().includes(compactAlias) ||
|
|
223
|
+
new RegExp(`(^|\\s)${escapeRegex(baseAlias)}(\\s|$)`, 'i').test(normalizedValue));
|
|
224
|
+
}
|
|
225
|
+
return new RegExp(`(^|\\s)${escapeRegex(normalizedAlias)}(\\s|$)`, 'i').test(normalizedValue);
|
|
226
|
+
}
|
|
227
|
+
function normalize(value) {
|
|
228
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
229
|
+
}
|
|
230
|
+
function normalizeYear(value) {
|
|
231
|
+
const year = Number(value);
|
|
232
|
+
return year < 100 ? 2000 + year : year;
|
|
233
|
+
}
|
|
234
|
+
function monthLabel(month) {
|
|
235
|
+
const entry = Object.entries(MONTHS).find(([, value]) => value === month);
|
|
236
|
+
return entry ? entry[0][0].toUpperCase() + entry[0].slice(1, 3) : 'Mon';
|
|
237
|
+
}
|
|
238
|
+
function dedupeEvidence(evidence) {
|
|
239
|
+
const seen = new Set();
|
|
240
|
+
return evidence.filter((item) => {
|
|
241
|
+
const key = `${item.source}:${item.value}:${item.pattern}:${item.file ?? ''}`;
|
|
242
|
+
if (seen.has(key))
|
|
243
|
+
return false;
|
|
244
|
+
seen.add(key);
|
|
245
|
+
return true;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function escapeRegex(value) {
|
|
249
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
250
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export interface ScoutFileInfo {
|
|
2
|
+
path: string;
|
|
3
|
+
extension: string;
|
|
4
|
+
detected_type: string;
|
|
5
|
+
detected_assets: string[];
|
|
6
|
+
columns: string[];
|
|
7
|
+
signals: string[];
|
|
8
|
+
metadata: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface ScoutAssetEvidence {
|
|
11
|
+
source: 'filename' | 'column' | 'text';
|
|
12
|
+
value: string;
|
|
13
|
+
pattern: string;
|
|
14
|
+
file?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ScoutAssetInfo {
|
|
17
|
+
symbol: string;
|
|
18
|
+
family: string;
|
|
19
|
+
exchange?: string;
|
|
20
|
+
region?: string;
|
|
21
|
+
hub?: string;
|
|
22
|
+
unit?: string;
|
|
23
|
+
evidence: ScoutAssetEvidence[];
|
|
24
|
+
inferred?: boolean;
|
|
25
|
+
inference_note?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ScoutFileTypeExplanation {
|
|
28
|
+
path: string;
|
|
29
|
+
detected_type: string;
|
|
30
|
+
reason: string;
|
|
31
|
+
evidence: string[];
|
|
32
|
+
}
|
|
33
|
+
export interface ScoutExplanations {
|
|
34
|
+
project_type_reasons: string[];
|
|
35
|
+
asset_evidence: ScoutAssetInfo[];
|
|
36
|
+
file_type_reasons: ScoutFileTypeExplanation[];
|
|
37
|
+
workflow_reasons: string[];
|
|
38
|
+
evidence_citations: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface ScoutFileContent {
|
|
41
|
+
path: string;
|
|
42
|
+
content: string;
|
|
43
|
+
truncated: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface ScoutManifest {
|
|
46
|
+
schema_version: string;
|
|
47
|
+
project_name: string;
|
|
48
|
+
created_by: string;
|
|
49
|
+
privacy_mode: string;
|
|
50
|
+
project_type: string;
|
|
51
|
+
repo_summary: string;
|
|
52
|
+
summary_source: 'generated' | 'provided' | 'user_edited';
|
|
53
|
+
detected_assets: string[];
|
|
54
|
+
detailed_assets: ScoutAssetInfo[];
|
|
55
|
+
detected_files: ScoutFileInfo[];
|
|
56
|
+
detected_packages: {
|
|
57
|
+
python: string[];
|
|
58
|
+
node: string[];
|
|
59
|
+
};
|
|
60
|
+
suggested_workflows: string[];
|
|
61
|
+
explanations: ScoutExplanations;
|
|
62
|
+
file_contents: ScoutFileContent[];
|
|
63
|
+
portfolio_name?: string;
|
|
64
|
+
}
|
|
65
|
+
export interface ScanOptions {
|
|
66
|
+
apiUrl: string;
|
|
67
|
+
dir?: string;
|
|
68
|
+
noUpload?: boolean;
|
|
69
|
+
noContent?: boolean;
|
|
70
|
+
portfolioName?: string;
|
|
71
|
+
summary?: string;
|
|
72
|
+
yes?: boolean;
|
|
73
|
+
verbose?: boolean;
|
|
74
|
+
}
|
|
75
|
+
export interface ExplainOptions {
|
|
76
|
+
dir?: string;
|
|
77
|
+
json?: boolean;
|
|
78
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function buildSuggestedWorkflows(projectType, assets) {
|
|
2
|
+
const workflows = [];
|
|
3
|
+
if (assets.includes('BRENT') || assets.includes('WTI')) {
|
|
4
|
+
workflows.push('crude_oil_price_analysis');
|
|
5
|
+
}
|
|
6
|
+
if (assets.includes('TTF') || assets.includes('NBP') || assets.includes('NG')) {
|
|
7
|
+
workflows.push('natural_gas_analysis');
|
|
8
|
+
}
|
|
9
|
+
if (assets.includes('EUA')) {
|
|
10
|
+
workflows.push('carbon_emissions_analysis');
|
|
11
|
+
}
|
|
12
|
+
if (assets.includes('POWER')) {
|
|
13
|
+
workflows.push('power_market_analysis');
|
|
14
|
+
}
|
|
15
|
+
if (assets.includes('JKM')) {
|
|
16
|
+
workflows.push('lng_market_analysis');
|
|
17
|
+
}
|
|
18
|
+
if (projectType === 'notebook_research_project') {
|
|
19
|
+
workflows.push('notebook_review');
|
|
20
|
+
}
|
|
21
|
+
if (projectType === 'python_analytics_project') {
|
|
22
|
+
workflows.push('python_model_review');
|
|
23
|
+
}
|
|
24
|
+
if (projectType === 'data_folder_project') {
|
|
25
|
+
workflows.push('data_quality_check');
|
|
26
|
+
}
|
|
27
|
+
if (workflows.length === 0) {
|
|
28
|
+
workflows.push('general_energy_analysis');
|
|
29
|
+
}
|
|
30
|
+
return workflows;
|
|
31
|
+
}
|
|
32
|
+
export function explainWorkflows(workflows, assets, projectType) {
|
|
33
|
+
return workflows.map((workflow) => {
|
|
34
|
+
if (workflow === 'crude_oil_price_analysis') {
|
|
35
|
+
return 'Crude workflow selected because Brent or WTI was detected.';
|
|
36
|
+
}
|
|
37
|
+
if (workflow === 'natural_gas_analysis') {
|
|
38
|
+
return 'Natural gas workflow selected because TTF, NBP, or Henry Hub gas was detected.';
|
|
39
|
+
}
|
|
40
|
+
if (workflow === 'carbon_emissions_analysis') {
|
|
41
|
+
return 'Carbon workflow selected because EUA or emissions data was detected.';
|
|
42
|
+
}
|
|
43
|
+
if (workflow === 'power_market_analysis') {
|
|
44
|
+
return 'Power workflow selected because power-market language was detected.';
|
|
45
|
+
}
|
|
46
|
+
if (workflow === 'lng_market_analysis') {
|
|
47
|
+
return 'LNG workflow selected because JKM or LNG data was detected.';
|
|
48
|
+
}
|
|
49
|
+
if (workflow === 'notebook_review') {
|
|
50
|
+
return 'Notebook review selected because the project contains Jupyter notebooks.';
|
|
51
|
+
}
|
|
52
|
+
if (workflow === 'python_model_review') {
|
|
53
|
+
return 'Python model review selected because the project looks like a Python analytics project.';
|
|
54
|
+
}
|
|
55
|
+
if (workflow === 'data_quality_check') {
|
|
56
|
+
return 'Data quality checks selected because most scanned files are structured data files.';
|
|
57
|
+
}
|
|
58
|
+
const assetText = assets.length ? ` for ${assets.join(', ')}` : '';
|
|
59
|
+
return `General energy analysis selected${assetText} because no more specific workflow matched project type ${projectType}.`;
|
|
60
|
+
});
|
|
61
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kolmo/scout",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Kolmo Scout — scan local energy projects and connect to Kolmo analytics",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"kolmo-scout": "dist/index.js",
|
|
8
|
+
"kolmo": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"!dist/tests"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"prepack": "npm run build",
|
|
18
|
+
"start": "node dist/index.js",
|
|
19
|
+
"test": "npm run build && node --test dist/tests/*.test.js"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"chalk": "^5.3.0",
|
|
23
|
+
"commander": "^12.0.0",
|
|
24
|
+
"fast-glob": "^3.3.2",
|
|
25
|
+
"ora": "^8.0.1",
|
|
26
|
+
"papaparse": "^5.4.1",
|
|
27
|
+
"xlsx": "^0.18.5"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^20.0.0",
|
|
31
|
+
"@types/papaparse": "^5.3.14",
|
|
32
|
+
"typescript": "^5.4.0"
|
|
33
|
+
},
|
|
34
|
+
"type": "module",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
}
|
|
38
|
+
}
|