Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 85 additions & 33 deletions bun.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion integrations/confluence/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
"devDependencies": {
"typescript": "^5"
},
"version": "0.2.0-rc.7"
"version": "0.2.0-rc.8"
}
2 changes: 1 addition & 1 deletion integrations/confluence/src/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface ConfluencePage {
status: string;
title: string;
spaceId?: string;
parentId?: string;
parentId?: string | null;
authorId?: string;
createdAt?: string;
version?: { number: number; message?: string; createdAt?: string };
Expand Down
2 changes: 1 addition & 1 deletion integrations/confluence/src/tools/get-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export let getPage = SlateTool.create(spec, {
title: page.title,
status: page.status,
spaceId: page.spaceId,
parentId: page.parentId,
parentId: page.parentId ?? undefined,
authorId: page.authorId,
createdAt: page.createdAt,
versionNumber: page.version?.number,
Expand Down
2 changes: 1 addition & 1 deletion integrations/confluence/src/tools/list-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export let listPages = SlateTool.create(spec, {
title: p.title,
status: p.status,
spaceId: p.spaceId,
parentId: p.parentId,
parentId: p.parentId ?? undefined,
versionNumber: p.version?.number,
createdAt: p.createdAt
}));
Expand Down
31 changes: 31 additions & 0 deletions integrations/stern-financial-data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# <img src="logo.svg" height="20"> Stern Financial Data

Query public NYU Stern financial datasets from Aswath Damodaran's data pages.

This integration reads public workbook datasets and falls back to the matching
HTML table when workbook extraction fails. No authentication is required.

## Tools

- `list_sources`: list supported Stern data sources, fields, source URLs, and filter hints.
- `get_source`: retrieve one source and return filtered rows. Full output is available
with `returnAll`, but filters and limits are recommended because rows are wide.

## Sources

- `erp`: country equity risk premiums, default spreads, corporate tax rates, and CDS data.
- `us_industry_betas`: US industry beta, leverage, tax, cash, risk, and volatility data.
- `global_industry_betas`: global industry beta, leverage, tax, cash, risk, and volatility data.

## Authentication

No authentication is required. The package uses `addNone()` and calls the public
Stern pages and workbooks directly.

## License

This integration is licensed under the [FSL-1.1](https://github.com/metorial/metorial-platform/blob/dev/LICENSE).

<div align="center">
<sub>Built with ❤️ by <a href="https://metorial.com">Metorial</a></sub>
</div>
67 changes: 67 additions & 0 deletions integrations/stern-financial-data/docs/SPEC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Slates Specification for Stern Financial Data

## Overview

Stern Financial Data reads public datasets from NYU Stern's Aswath Damodaran
data pages. The integration focuses on compact discovery and filtered reads for
large financial tables.

Supported sources:

- `erp`: country equity risk premium data from `ctryprem`.
- `us_industry_betas`: US industry beta data from `betas`.
- `global_industry_betas`: global industry beta data from `betaGlobal`.

## Authentication

These Stern datasets are public. No API key, OAuth flow, token, or account
configuration is needed. The integration uses the no-auth Slate authentication
method.

## Extraction

Each source has a public HTML page and workbook URL. `get_source` retrieves the
HTML page, then tries the workbook first because workbook cells preserve numeric
values and formatting. If workbook fetch or parsing fails, the tool falls back
to parsing the HTML table with the same header aliases and type coercion.

Percent values are returned as decimal numbers, for example `4.23%` becomes
`0.0423`. Original cell text can be included with `includeRaw`.

## Tools

### list_sources

Returns the supported source ids, source titles, page/workbook URLs, row fields,
and supported filters.

### get_source

Retrieves one source and applies source-specific filters.

ERP filters:

- exact country list or country text search
- Moody's rating list
- equity risk premium range
- country risk premium range
- corporate tax rate range
- sovereign CDS presence

Industry beta filters:

- exact industry list or industry text search
- row type: `industry` or `aggregate`
- beta range
- unlevered beta range
- minimum number of firms
- maximum debt-to-equity ratio

By default the tool returns up to 25 filtered rows. Use `returnAll: true` only
when full output is needed; filtered reads are preferred because rows are wide
and include many financial metrics.

## Events

The provider does not support webhooks or event subscriptions for these public
datasets. This integration is read-only and tool-only.
6 changes: 6 additions & 0 deletions integrations/stern-financial-data/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions integrations/stern-financial-data/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "@slates-integrations/stern-financial-data",
"main": "src/index.ts",
"type": "module",
"scripts": {
"build": "bunx @vercel/ncc build src/index.ts -o dist -m -s",
"test": "vitest run --passWithNoTests",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@lowerdeck/error": "^1.1.0",
"@mixmark-io/domino": "^2.2.0",
"@types/node": "^20",
"slates": "1.0.0-rc.10",
"xlsx": "^0.18.5",
"zod": "^4.2"
},
"devDependencies": {
"@slates/test": "1.0.0-rc.4",
"typescript": "^5",
"vitest": "^3.1.2"
},
"version": "0.1.0-rc.1"
}
13 changes: 13 additions & 0 deletions integrations/stern-financial-data/slate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "@metorial/stern-financial-data",
"description": "Query public NYU Stern financial datasets including country equity risk premiums and industry betas.",
"categories": ["finance-and-accounting", "data-and-analytics"],
"skills": [
"list NYU Stern financial datasets",
"query country equity risk premiums",
"filter country risk premium data",
"query US industry betas",
"query global industry betas",
"filter industry beta datasets"
]
}
4 changes: 4 additions & 0 deletions integrations/stern-financial-data/src/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { SlateAuth } from 'slates';
import { z } from 'zod';

export let auth = SlateAuth.create().output(z.object({})).addNone();
4 changes: 4 additions & 0 deletions integrations/stern-financial-data/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { SlateConfig } from 'slates';
import { z } from 'zod';

export let config = SlateConfig.create(z.object({}));
9 changes: 9 additions & 0 deletions integrations/stern-financial-data/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Slate } from 'slates';
import { spec } from './spec';
import { getSource, listSources } from './tools';

export let provider = Slate.create({
spec,
tools: [listSources, getSource],
triggers: []
});
111 changes: 111 additions & 0 deletions integrations/stern-financial-data/src/lib/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { sternFinancialDataApiError, sternFinancialDataServiceError } from './errors';
import { extractRowsForSource, parseWorkbook } from './extractor';
import { SOURCES, SourceId, SourceType, SternRow } from './sources';

export type SourceResult = {
metadata: {
source: SourceId;
title: string;
pageUrl: string;
workbookUrl: string;
retrievedAt: string;
sourceType: SourceType;
workbookFallbackReason?: string;
};
rows: SternRow[];
};

let FETCH_TIMEOUT_MS = 20_000;

let fetchWithTimeout = async (url: string, operation: string) => {
let controller = new AbortController();
let timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);

try {
return await fetch(url, { signal: controller.signal });
} catch (error) {
if (controller.signal.aborted) {
throw sternFinancialDataServiceError(
`Stern Financial Data ${operation} timed out after ${FETCH_TIMEOUT_MS}ms.`
);
}

throw error;
} finally {
clearTimeout(timeout);
}
};

let fetchText = async (url: string) => {
try {
let response = await fetchWithTimeout(url, 'HTML page fetch');

if (!response.ok) {
throw Object.assign(new Error(`${response.status} ${response.statusText}`), {
status: response.status,
statusText: response.statusText
});
}

return await response.text();
} catch (error) {
throw sternFinancialDataApiError(error, 'HTML page fetch');
}
};

let fetchBuffer = async (url: string) => {
try {
let response = await fetchWithTimeout(url, 'workbook fetch');

if (!response.ok) {
throw Object.assign(new Error(`${response.status} ${response.statusText}`), {
status: response.status,
statusText: response.statusText
});
}

return Buffer.from(await response.arrayBuffer());
} catch (error) {
throw sternFinancialDataApiError(error, 'workbook fetch');
}
};

let errorMessage = (error: unknown) =>
error instanceof Error ? error.message : String(error);

export class SternFinancialDataClient {
async getSource(sourceId: SourceId): Promise<SourceResult> {
let source = SOURCES[sourceId];
let retrievedAt = new Date().toISOString();
let pageHtml = await fetchText(source.pageUrl);
let rows: SternRow[] = [];
let sourceType: SourceType = 'workbook';
let workbookFallbackReason: string | undefined;

try {
let workbook = parseWorkbook(await fetchBuffer(source.workbookUrl));
rows = extractRowsForSource(sourceId, workbook, pageHtml);
} catch (error) {
workbookFallbackReason = errorMessage(error);
sourceType = 'html';
rows = extractRowsForSource(sourceId, null, pageHtml);
}

if (rows.length === 0) {
throw sternFinancialDataServiceError(`No rows extracted for Stern source ${sourceId}.`);
}

return {
metadata: {
source: sourceId,
title: source.title,
pageUrl: source.pageUrl,
workbookUrl: source.workbookUrl,
retrievedAt,
sourceType,
workbookFallbackReason
},
rows
};
}
}
Loading
Loading