@kirjapro/mcp-server 0.8.2 → 0.8.5
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/CHANGELOG.md +58 -3
- package/dist/index.js +144 -142
- package/package.json +1 -1
- package/server.json +1 -1
- package/tool-fingerprints.json +4 -4
- package/dist/accounts.d.ts +0 -40
- package/dist/accounts.js +0 -45
- package/dist/auth.d.ts +0 -17
- package/dist/auth.js +0 -245
- package/dist/compliance/rates.d.ts +0 -53
- package/dist/compliance/rates.js +0 -57
- package/dist/database.types.d.ts +0 -6525
- package/dist/database.types.js +0 -20
- package/dist/index.d.ts +0 -2
- package/dist/lib/incomeStatementSynthesis.d.ts +0 -47
- package/dist/lib/incomeStatementSynthesis.js +0 -140
- package/dist/resources/company.d.ts +0 -2
- package/dist/resources/company.js +0 -75
- package/dist/resources/compliance.d.ts +0 -2
- package/dist/resources/compliance.js +0 -338
- package/dist/resources/index.d.ts +0 -2
- package/dist/resources/index.js +0 -10
- package/dist/resources/schema.d.ts +0 -2
- package/dist/resources/schema.js +0 -63
- package/dist/resources/security.d.ts +0 -2
- package/dist/resources/security.js +0 -33
- package/dist/schemas.d.ts +0 -12
- package/dist/schemas.js +0 -12
- package/dist/tools/accounts.d.ts +0 -2
- package/dist/tools/accounts.js +0 -48
- package/dist/tools/banking.d.ts +0 -2
- package/dist/tools/banking.js +0 -196
- package/dist/tools/bookings.d.ts +0 -2
- package/dist/tools/bookings.js +0 -294
- package/dist/tools/customers.d.ts +0 -2
- package/dist/tools/customers.js +0 -320
- package/dist/tools/financialNotes.d.ts +0 -2
- package/dist/tools/financialNotes.js +0 -255
- package/dist/tools/fingerprints.d.ts +0 -6
- package/dist/tools/fingerprints.js +0 -28
- package/dist/tools/fiscal-years.d.ts +0 -2
- package/dist/tools/fiscal-years.js +0 -35
- package/dist/tools/index.d.ts +0 -9
- package/dist/tools/index.js +0 -105
- package/dist/tools/invoices.d.ts +0 -2
- package/dist/tools/invoices.js +0 -489
- package/dist/tools/offers.d.ts +0 -2
- package/dist/tools/offers.js +0 -443
- package/dist/tools/received-invoices.d.ts +0 -2
- package/dist/tools/received-invoices.js +0 -134
- package/dist/tools/recurring-invoices.d.ts +0 -2
- package/dist/tools/recurring-invoices.js +0 -348
- package/dist/tools/reports.d.ts +0 -2
- package/dist/tools/reports.js +0 -869
- package/dist/tools/templates.d.ts +0 -89
- package/dist/tools/templates.js +0 -636
- package/dist/tools/transactions.d.ts +0 -2
- package/dist/tools/transactions.js +0 -597
- package/dist/tools/validation.d.ts +0 -26
- package/dist/tools/validation.js +0 -127
- package/dist/types.d.ts +0 -388
- package/dist/types.js +0 -34
- package/dist/utils.d.ts +0 -276
- package/dist/utils.js +0 -637
- package/dist/version.d.ts +0 -1
- package/dist/version.js +0 -3
package/dist/database.types.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export const Constants = {
|
|
2
|
-
public: {
|
|
3
|
-
Enums: {
|
|
4
|
-
account_type: ["asset", "liability", "equity", "revenue", "expense"],
|
|
5
|
-
company_role: ["owner", "admin", "accountant", "viewer"],
|
|
6
|
-
company_size: ["micro", "small", "large"],
|
|
7
|
-
platform_role: ["admin", "superadmin"],
|
|
8
|
-
subscription_tier: ["free", "entrepreneur", "growth", "company", "custom"],
|
|
9
|
-
vat_category: [
|
|
10
|
-
"standard",
|
|
11
|
-
"zero_rate_export",
|
|
12
|
-
"zero_rate_eu",
|
|
13
|
-
"reverse_charge",
|
|
14
|
-
"exempt",
|
|
15
|
-
"margin_scheme",
|
|
16
|
-
],
|
|
17
|
-
vat_period: ["monthly", "quarterly", "yearly"],
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
};
|
package/dist/index.d.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Income statement synthesis — calculated subtotals from leaf-line amounts.
|
|
3
|
-
*
|
|
4
|
-
* DUPLICATED BY INTENT from `src/hooks/useIncomeStatementCalculation.ts`
|
|
5
|
-
* (`synthesizeIncomeStatementLines`). The mcp-server is published as a
|
|
6
|
-
* separate npm package and cannot import from `src/`. The contract is:
|
|
7
|
-
*
|
|
8
|
-
* Any change to the web hook's synthesis switch (line_code cases,
|
|
9
|
-
* addends, sign conventions) MUST be mirrored here, and vice versa.
|
|
10
|
-
*
|
|
11
|
-
* Both paths consume the same `get_income_statement` RPC, which returns
|
|
12
|
-
* ONLY leaf rows (range-based line_codes). Calculated subtotals
|
|
13
|
-
* (LIIKEVOITTO, RAHOITUS_TOTAL, TILIKAUDEN_TULOS, ...) are synthesized
|
|
14
|
-
* client-side.
|
|
15
|
-
*/
|
|
16
|
-
import type { Database } from "../database.types.js";
|
|
17
|
-
type IncomeStatementLineType = Database["public"]["Tables"]["income_statement_line_types"]["Row"];
|
|
18
|
-
export interface CalculatedIncomeStatementLine {
|
|
19
|
-
lineCode: string;
|
|
20
|
-
lineName: string;
|
|
21
|
-
lineType: string | null;
|
|
22
|
-
indentLevel: number;
|
|
23
|
-
currentAmount: number;
|
|
24
|
-
previousAmount: number;
|
|
25
|
-
variance: number;
|
|
26
|
-
variancePercentage: number | null;
|
|
27
|
-
isCalculated: boolean;
|
|
28
|
-
signConvention: string | null;
|
|
29
|
-
}
|
|
30
|
-
export interface CalculationTotals {
|
|
31
|
-
liikevaihto: number;
|
|
32
|
-
liikevoitto: number;
|
|
33
|
-
voittoEnnenVeroja: number;
|
|
34
|
-
tilikausiTulos: number;
|
|
35
|
-
}
|
|
36
|
-
export interface SynthesisResult {
|
|
37
|
-
lines: CalculatedIncomeStatementLine[];
|
|
38
|
-
totals: CalculationTotals;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Synthesize calculated subtotals (LIIKEVOITTO, MATERIAALIT_TOTAL, ...)
|
|
42
|
-
* from pre-computed leaf-line amounts. Calculated subtotals are processed
|
|
43
|
-
* in lineTypes order (display_order); each subtotal MUST appear after its
|
|
44
|
-
* dependencies in the template.
|
|
45
|
-
*/
|
|
46
|
-
export declare function synthesizeIncomeStatementLines(lineTypes: IncomeStatementLineType[], currentByLine: Map<string, number>, previousByLine: Map<string, number>): SynthesisResult;
|
|
47
|
-
export {};
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Synthesize calculated subtotals (LIIKEVOITTO, MATERIAALIT_TOTAL, ...)
|
|
3
|
-
* from pre-computed leaf-line amounts. Calculated subtotals are processed
|
|
4
|
-
* in lineTypes order (display_order); each subtotal MUST appear after its
|
|
5
|
-
* dependencies in the template.
|
|
6
|
-
*/
|
|
7
|
-
export function synthesizeIncomeStatementLines(lineTypes, currentByLine, previousByLine) {
|
|
8
|
-
// Defensive copy — we mutate during the calculated-subtotal pass below.
|
|
9
|
-
const lineAmounts = new Map(currentByLine);
|
|
10
|
-
const prevLineAmounts = new Map(previousByLine);
|
|
11
|
-
lineTypes.forEach((lineType) => {
|
|
12
|
-
if (!lineType.is_calculated)
|
|
13
|
-
return;
|
|
14
|
-
let currentAmount = 0;
|
|
15
|
-
let previousAmount = 0;
|
|
16
|
-
const get = (code) => lineAmounts.get(code) || 0;
|
|
17
|
-
const getPrev = (code) => prevLineAmounts.get(code) || 0;
|
|
18
|
-
switch (lineType.line_code) {
|
|
19
|
-
case "MATERIAALIT_TOTAL":
|
|
20
|
-
currentAmount =
|
|
21
|
-
get("OSTOT_TILIKAUDEN") +
|
|
22
|
-
get("VARASTOJEN_MUUTOS_OSTOT") +
|
|
23
|
-
get("ULKOPUOLISET_PALVELUT");
|
|
24
|
-
previousAmount =
|
|
25
|
-
getPrev("OSTOT_TILIKAUDEN") +
|
|
26
|
-
getPrev("VARASTOJEN_MUUTOS_OSTOT") +
|
|
27
|
-
getPrev("ULKOPUOLISET_PALVELUT");
|
|
28
|
-
break;
|
|
29
|
-
case "HENKILOSTO_TOTAL":
|
|
30
|
-
currentAmount =
|
|
31
|
-
get("PALKAT_PALKKIOT") + get("ELAKEKULUT") + get("MUUT_HENKILOSIVUKULUT");
|
|
32
|
-
previousAmount =
|
|
33
|
-
getPrev("PALKAT_PALKKIOT") +
|
|
34
|
-
getPrev("ELAKEKULUT") +
|
|
35
|
-
getPrev("MUUT_HENKILOSIVUKULUT");
|
|
36
|
-
break;
|
|
37
|
-
case "POISTOT_TOTAL":
|
|
38
|
-
currentAmount =
|
|
39
|
-
get("SUUNNITELMAN_POISTOT") +
|
|
40
|
-
get("ARVONALENT_PYSYVAT") +
|
|
41
|
-
get("ARVONALENT_VAIHTUVAT");
|
|
42
|
-
previousAmount =
|
|
43
|
-
getPrev("SUUNNITELMAN_POISTOT") +
|
|
44
|
-
getPrev("ARVONALENT_PYSYVAT") +
|
|
45
|
-
getPrev("ARVONALENT_VAIHTUVAT");
|
|
46
|
-
break;
|
|
47
|
-
case "LIIKEVOITTO":
|
|
48
|
-
currentAmount =
|
|
49
|
-
get("LIIKEVAIHTO") +
|
|
50
|
-
get("VARASTOJEN_MUUTOS") +
|
|
51
|
-
get("VALMISTUS_OMAAN") +
|
|
52
|
-
get("MUUT_TUOTOT") +
|
|
53
|
-
get("MATERIAALIT_TOTAL") +
|
|
54
|
-
get("HENKILOSTO_TOTAL") +
|
|
55
|
-
get("POISTOT_TOTAL") +
|
|
56
|
-
get("MUUT_KULUT");
|
|
57
|
-
previousAmount =
|
|
58
|
-
getPrev("LIIKEVAIHTO") +
|
|
59
|
-
getPrev("VARASTOJEN_MUUTOS") +
|
|
60
|
-
getPrev("VALMISTUS_OMAAN") +
|
|
61
|
-
getPrev("MUUT_TUOTOT") +
|
|
62
|
-
getPrev("MATERIAALIT_TOTAL") +
|
|
63
|
-
getPrev("HENKILOSTO_TOTAL") +
|
|
64
|
-
getPrev("POISTOT_TOTAL") +
|
|
65
|
-
getPrev("MUUT_KULUT");
|
|
66
|
-
break;
|
|
67
|
-
case "RAHOITUS_TOTAL":
|
|
68
|
-
currentAmount =
|
|
69
|
-
get("TUOTOT_KONSERNI") +
|
|
70
|
-
get("TUOTOT_OMISTUSYHTEYS") +
|
|
71
|
-
get("TUOTOT_SIJOITUKSET") +
|
|
72
|
-
get("MUUT_KORKOTUOTOT") +
|
|
73
|
-
get("ARVONALENT_SIJOITUKSET") +
|
|
74
|
-
get("ARVONALENT_RAHOITUS") +
|
|
75
|
-
get("KORKOKULUT");
|
|
76
|
-
previousAmount =
|
|
77
|
-
getPrev("TUOTOT_KONSERNI") +
|
|
78
|
-
getPrev("TUOTOT_OMISTUSYHTEYS") +
|
|
79
|
-
getPrev("TUOTOT_SIJOITUKSET") +
|
|
80
|
-
getPrev("MUUT_KORKOTUOTOT") +
|
|
81
|
-
getPrev("ARVONALENT_SIJOITUKSET") +
|
|
82
|
-
getPrev("ARVONALENT_RAHOITUS") +
|
|
83
|
-
getPrev("KORKOKULUT");
|
|
84
|
-
break;
|
|
85
|
-
case "VOITTO_ENNEN_SIIRTOJA":
|
|
86
|
-
currentAmount = get("LIIKEVOITTO") + get("RAHOITUS_TOTAL");
|
|
87
|
-
previousAmount = getPrev("LIIKEVOITTO") + getPrev("RAHOITUS_TOTAL");
|
|
88
|
-
break;
|
|
89
|
-
case "SIIRROT_TOTAL":
|
|
90
|
-
currentAmount =
|
|
91
|
-
get("POISTOERON_MUUTOS") + get("VARAUSTEN_MUUTOS") + get("KONSERNIAVUSTUS");
|
|
92
|
-
previousAmount =
|
|
93
|
-
getPrev("POISTOERON_MUUTOS") +
|
|
94
|
-
getPrev("VARAUSTEN_MUUTOS") +
|
|
95
|
-
getPrev("KONSERNIAVUSTUS");
|
|
96
|
-
break;
|
|
97
|
-
case "TILIKAUDEN_TULOS":
|
|
98
|
-
currentAmount =
|
|
99
|
-
get("VOITTO_ENNEN_SIIRTOJA") +
|
|
100
|
-
get("SIIRROT_TOTAL") +
|
|
101
|
-
get("TULOVEROT") +
|
|
102
|
-
get("MUUT_VEROT");
|
|
103
|
-
previousAmount =
|
|
104
|
-
getPrev("VOITTO_ENNEN_SIIRTOJA") +
|
|
105
|
-
getPrev("SIIRROT_TOTAL") +
|
|
106
|
-
getPrev("TULOVEROT") +
|
|
107
|
-
getPrev("MUUT_VEROT");
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
lineAmounts.set(lineType.line_code, currentAmount);
|
|
111
|
-
prevLineAmounts.set(lineType.line_code, previousAmount);
|
|
112
|
-
});
|
|
113
|
-
const calculatedLines = lineTypes.map((lineType) => {
|
|
114
|
-
const currentAmount = lineAmounts.get(lineType.line_code) || 0;
|
|
115
|
-
const previousAmount = prevLineAmounts.get(lineType.line_code) || 0;
|
|
116
|
-
const variance = currentAmount - previousAmount;
|
|
117
|
-
const variancePercentage = previousAmount !== 0 ? (variance / Math.abs(previousAmount)) * 100 : null;
|
|
118
|
-
return {
|
|
119
|
-
lineCode: lineType.line_code,
|
|
120
|
-
lineName: lineType.line_name_fi,
|
|
121
|
-
lineType: lineType.line_type,
|
|
122
|
-
indentLevel: lineType.indent_level ?? 0,
|
|
123
|
-
currentAmount,
|
|
124
|
-
previousAmount,
|
|
125
|
-
variance,
|
|
126
|
-
variancePercentage,
|
|
127
|
-
isCalculated: lineType.is_calculated ?? false,
|
|
128
|
-
signConvention: lineType.sign_convention,
|
|
129
|
-
};
|
|
130
|
-
});
|
|
131
|
-
// `??` not `||` so a legitimate break-even 0 is preserved.
|
|
132
|
-
// Skill: fallback-chain-zero-respect.
|
|
133
|
-
const totals = {
|
|
134
|
-
liikevaihto: lineAmounts.get("LIIKEVAIHTO") ?? 0,
|
|
135
|
-
liikevoitto: lineAmounts.get("LIIKEVOITTO") ?? 0,
|
|
136
|
-
voittoEnnenVeroja: lineAmounts.get("VOITTO_ENNEN_SIIRTOJA") ?? 0,
|
|
137
|
-
tilikausiTulos: lineAmounts.get("TILIKAUDEN_TULOS") ?? 0,
|
|
138
|
-
};
|
|
139
|
-
return { lines: calculatedLines, totals };
|
|
140
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { getSupabase, getCompanyId } from '../auth.js';
|
|
2
|
-
import { helsinkiDateString, sanitizeDbError } from '../utils.js';
|
|
3
|
-
export function registerCompanyResources(server) {
|
|
4
|
-
server.registerResource('company-info', 'kirjapro://company/info', {
|
|
5
|
-
description: 'Current company details: name, business ID (Y-tunnus), address, and settings.',
|
|
6
|
-
mimeType: 'application/json',
|
|
7
|
-
}, async (uri) => {
|
|
8
|
-
const supabase = getSupabase();
|
|
9
|
-
const companyId = getCompanyId();
|
|
10
|
-
const { data: company, error } = await supabase
|
|
11
|
-
.from('companies')
|
|
12
|
-
.select('id, name, business_id, address, postal_code, city, country, created_at')
|
|
13
|
-
.eq('id', companyId)
|
|
14
|
-
.single();
|
|
15
|
-
// Don't swallow the error. An empty company object is indistinguishable
|
|
16
|
-
// from auth expiry, RLS denial, or PostgREST failure — and the agent
|
|
17
|
-
// would build decisions on a blank Y-tunnus. Surface the sanitized
|
|
18
|
-
// error so the agent can tell the user to re-auth or retry.
|
|
19
|
-
if (error || !company) {
|
|
20
|
-
return {
|
|
21
|
-
contents: [{
|
|
22
|
-
uri: uri.href,
|
|
23
|
-
mimeType: 'application/json',
|
|
24
|
-
text: JSON.stringify({
|
|
25
|
-
error: error
|
|
26
|
-
? sanitizeDbError('Failed to read company', error)
|
|
27
|
-
: 'Company not found for current context.',
|
|
28
|
-
}),
|
|
29
|
-
}],
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
return {
|
|
33
|
-
contents: [{
|
|
34
|
-
uri: uri.href,
|
|
35
|
-
mimeType: 'application/json',
|
|
36
|
-
text: JSON.stringify(company),
|
|
37
|
-
}],
|
|
38
|
-
};
|
|
39
|
-
});
|
|
40
|
-
server.registerResource('fiscal-years', 'kirjapro://fiscal-years', {
|
|
41
|
-
description: 'List of fiscal years (tilikaudet) for the company. Shows which are open/closed and their date ranges.',
|
|
42
|
-
mimeType: 'application/json',
|
|
43
|
-
}, async (uri) => {
|
|
44
|
-
const supabase = getSupabase();
|
|
45
|
-
const companyId = getCompanyId();
|
|
46
|
-
const { data: fiscalYears, error } = await supabase
|
|
47
|
-
.from('fiscal_years')
|
|
48
|
-
.select('id, start_date, end_date, is_closed, created_at')
|
|
49
|
-
.eq('company_id', companyId)
|
|
50
|
-
.order('start_date', { ascending: false });
|
|
51
|
-
if (error) {
|
|
52
|
-
return {
|
|
53
|
-
contents: [{
|
|
54
|
-
uri: uri.href,
|
|
55
|
-
mimeType: 'application/json',
|
|
56
|
-
text: JSON.stringify({
|
|
57
|
-
error: sanitizeDbError('Failed to read fiscal years', error),
|
|
58
|
-
}),
|
|
59
|
-
}],
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
const today = helsinkiDateString();
|
|
63
|
-
const enriched = (fiscalYears || []).map((fy) => ({
|
|
64
|
-
...fy,
|
|
65
|
-
is_current: !fy.is_closed && fy.start_date <= today && fy.end_date >= today,
|
|
66
|
-
}));
|
|
67
|
-
return {
|
|
68
|
-
contents: [{
|
|
69
|
-
uri: uri.href,
|
|
70
|
-
mimeType: 'application/json',
|
|
71
|
-
text: JSON.stringify(enriched),
|
|
72
|
-
}],
|
|
73
|
-
};
|
|
74
|
-
});
|
|
75
|
-
}
|
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
import { FINNISH_RATES_2026, freshnessMeta } from '../compliance/rates.js';
|
|
2
|
-
/**
|
|
3
|
-
* Finnish VAT compliance rules (2026).
|
|
4
|
-
* Sourced from AVL 1501/1993, vero.fi, and docs/compliance/taxes/.
|
|
5
|
-
* Numeric rates come from src/compliance/rates.ts — do not hardcode here.
|
|
6
|
-
*/
|
|
7
|
-
const VAT_COMPLIANCE = {
|
|
8
|
-
rates: {
|
|
9
|
-
current_year: FINNISH_RATES_2026.year,
|
|
10
|
-
standard: { rate: FINNISH_RATES_2026.vat.standard, finnish: 'Yleinen verokanta', effective: '2024-09-01', basis: 'AVL 84§', examples: 'IT services, consulting, retail, construction, telecom, energy' },
|
|
11
|
-
reduced: { rate: FINNISH_RATES_2026.vat.reduced, finnish: 'Alennettu verokanta', effective: '2026-01-01', basis: 'AVL 85§, 85a§', examples: 'Food, animal feed, restaurants, transport, accommodation, books, medicine, sports, culture, performing artists, sanitary products, broadcasting' },
|
|
12
|
-
second_reduced: { rate: FINNISH_RATES_2026.vat.secondReduced, finnish: 'Toinen alennettu verokanta', basis: 'AVL 85a§', examples: 'Newspapers, magazines, digital periodicals only' },
|
|
13
|
-
zero: { rate: FINNISH_RATES_2026.vat.zero, finnish: 'Nollaverokanta', basis: 'AVL 55-72h§', examples: 'Exports outside EU, intra-community supplies, vessels (sale/rental/chartering), international transport' },
|
|
14
|
-
exempt: { rate: null, finnish: 'Verovapaat myynnit', basis: 'AVL 34-44§', examples: 'Healthcare (AVL 34-36§), education (AVL 39-40§), financial services (AVL 41-42§), insurance (AVL 44§), social services (AVL 37-38§), real estate transfers (AVL 27-28§)' },
|
|
15
|
-
},
|
|
16
|
-
rate_history: [
|
|
17
|
-
{ period: '1.1.2026 →', standard: FINNISH_RATES_2026.vat.standard, reduced: FINNISH_RATES_2026.vat.reduced, second_reduced: FINNISH_RATES_2026.vat.secondReduced, note: 'Reduced rate unified from 14% → 13.5%' },
|
|
18
|
-
{ period: '1.9.2024 → 31.12.2025', standard: 25.5, reduced: 14, second_reduced: 10, note: 'Standard raised from 24% → 25.5%' },
|
|
19
|
-
{ period: '1.1.2013 → 31.8.2024', standard: 24, reduced: 14, second_reduced: 10 },
|
|
20
|
-
],
|
|
21
|
-
deduction_rules: [
|
|
22
|
-
{ type: 'Edustuskulut (entertainment)', vat_deduction: '0%', income_tax: '50%', basis: 'AVL 114§', booking: 'Full gross to expense, NO ALV-saamiset entry' },
|
|
23
|
-
{ type: 'Neuvottelukulut (meetings)', vat_deduction: '100%', income_tax: '100%', basis: 'AVL 102§', booking: 'Normal VAT split' },
|
|
24
|
-
{ type: 'Henkilöstökulut (staff events)', vat_deduction: '100%', income_tax: '100%', basis: 'AVL 102§', booking: 'Normal VAT split' },
|
|
25
|
-
{ type: 'Insurance premiums', vat_deduction: '0% (exempt)', income_tax: '100%', basis: 'AVL 44§', booking: 'Full gross to expense, no VAT (vakuutusmaksuvero is separate)' },
|
|
26
|
-
{ type: 'Healthcare (staff)', vat_deduction: '0% (exempt)', income_tax: '100%', basis: 'AVL 34-36§' },
|
|
27
|
-
{ type: 'Financial services', vat_deduction: '0% (exempt)', income_tax: '100%', basis: 'AVL 41-42§' },
|
|
28
|
-
{ type: 'Henkilöauto (passenger car M1)', vat_deduction: '100% if exclusive business use, 0% if ANY private use (incl. commuting)', income_tax: 'Proportional', basis: 'AVL 114§ 1 mom 4 kohta', booking: 'Ajopäiväkirja (mileage logbook) required as proof' },
|
|
29
|
-
{ type: 'Pakettiauto/kuorma-auto (van N1+)', vat_deduction: 'Proportional to business use', income_tax: 'Proportional', basis: 'AVL 102§, 117§' },
|
|
30
|
-
{ type: 'Markkinointitilaisuudet (marketing events)', vat_deduction: '100%', income_tax: '100%', basis: 'AVL 102§, KHO 2013:63', booking: 'Product launches, trade shows — NOT client entertainment' },
|
|
31
|
-
],
|
|
32
|
-
reverse_charge: {
|
|
33
|
-
rule: 'Buyer self-assesses both output and input VAT. Applies to: (1) foreign B2B providers not charging Finnish VAT, (2) domestic construction services, (3) metal scrap, (4) emissions allowances, (5) investment gold',
|
|
34
|
-
basis: 'AVL 8a-8d§, 9§, 65§; EU VAT Directive Art. 196, Art. 199',
|
|
35
|
-
scenarios: [
|
|
36
|
-
{ type: 'Foreign B2B', basis: 'AVL 9§', note: 'Most common: EU and non-EU service providers' },
|
|
37
|
-
{ type: 'Construction services', basis: 'AVL 8c§', note: 'Since 1.4.2011. Buyer must itself sell/rent construction services "not just occasionally". Regular company commissioning renovation: NO reverse charge' },
|
|
38
|
-
{ type: 'Metal scrap and waste', basis: 'AVL 8d§', note: 'Since 1.1.2015. Between VAT-registered entities' },
|
|
39
|
-
{ type: 'Emissions allowances', basis: 'AVL 8b§', note: 'Greenhouse gas emission allowances' },
|
|
40
|
-
{ type: 'Investment gold', basis: 'AVL 8a§', note: 'Taxable investment gold, gold materials, gold semi-finished products' },
|
|
41
|
-
],
|
|
42
|
-
booking: '4 entries: D expense + D ALV-saamiset + C bank + C ALV-velka (käännetty)',
|
|
43
|
-
note: 'Net VAT effect is zero but BOTH must be reported on ALV-ilmoitus. Invoice must NOT show VAT rate or VAT amount',
|
|
44
|
-
omavero_fields: 'Foreign: 305 (EU goods), 306 (EU services). Domestic construction/scrap: 318 (VAT amount), 319/320 (tax bases)',
|
|
45
|
-
},
|
|
46
|
-
common_mistakes: [
|
|
47
|
-
'Food/coffee at 25.5% — should be 13.5% (AVL 85§)',
|
|
48
|
-
'Train ticket at 10% — should be 13.5% (transport = reduced rate)',
|
|
49
|
-
'Using 14% after 1.1.2026 — rate changed to 13.5%',
|
|
50
|
-
'Restaurant alcohol at 13.5% — alcohol is 25.5% even at restaurants',
|
|
51
|
-
'Newspaper/magazine at 13.5% — periodicals are 10%',
|
|
52
|
-
'Splitting VAT from insurance premium — insurance is VAT-exempt, not zero-rated',
|
|
53
|
-
'Deducting vakuutusmaksuvero as input VAT — it is not ALV',
|
|
54
|
-
],
|
|
55
|
-
};
|
|
56
|
-
/**
|
|
57
|
-
* Invoice requirements per AVL 209 e § (18 mandatory markings).
|
|
58
|
-
* Source: https://www.vero.fi/syventavat-vero-ohjeet/ohje-hakusivu/48090/laskutusvaatimukset-arvonlisaverotuksessa3/
|
|
59
|
-
*/
|
|
60
|
-
const INVOICE_REQUIREMENTS = {
|
|
61
|
-
legal_basis: 'AVL 209 e §, EU VAT Directive Art. 226',
|
|
62
|
-
source: 'https://www.vero.fi/syventavat-vero-ohjeet/ohje-hakusivu/48090/laskutusvaatimukset-arvonlisaverotuksessa3/',
|
|
63
|
-
total_markings: 18,
|
|
64
|
-
mandatory_fields: [
|
|
65
|
-
{ kohta: 1, field: 'Laskun antamispäivä', english: 'Invoice date', status: 'implemented', relevance: 'always' },
|
|
66
|
-
{ kohta: 2, field: 'Juokseva tunniste', english: 'Sequential invoice number', status: 'implemented', relevance: 'always', note: 'Gaps must be detectable per Vero requirement' },
|
|
67
|
-
{ kohta: 3, field: 'Myyjän ALV-tunniste', english: 'Seller VAT ID (FI + Y-tunnus)', status: 'implemented', relevance: 'always', note: 'Y-tunnus always required; FI-format strictly mandatory for EU cross-border' },
|
|
68
|
-
{ kohta: 4, field: 'Ostajan ALV-tunniste', english: 'Buyer VAT ID', status: 'implemented', relevance: 'conditional', note: 'Required for reverse charge and EU B2B' },
|
|
69
|
-
{ kohta: 5, field: 'Myyjän ja ostajan nimi ja osoite', english: 'Names and addresses', status: 'implemented', relevance: 'always' },
|
|
70
|
-
{ kohta: 6, field: 'Tavaroiden/palvelujen kuvaus ja määrä', english: 'Description and quantity', status: 'implemented', relevance: 'always' },
|
|
71
|
-
{ kohta: 7, field: 'Toimituspäivä / suorituspäivä', english: 'Delivery date', status: 'partial', relevance: 'conditional', note: 'NOT required when same as invoice date (Vero.fi 5.1.7). Required when different' },
|
|
72
|
-
{ kohta: 8, field: 'Veron peruste, yksikköhinta, alennukset', english: 'Tax base, unit price, discounts', status: 'implemented', relevance: 'always' },
|
|
73
|
-
{ kohta: 9, field: 'Verokanta', english: 'VAT rate', status: 'implemented', relevance: 'always' },
|
|
74
|
-
{ kohta: 10, field: 'Suoritettavan veron määrä', english: 'VAT amount', status: 'implemented', relevance: 'always' },
|
|
75
|
-
{ kohta: 11, field: 'Merkintä verottomuudesta', english: 'Tax exemption notation', status: 'gap', relevance: 'conditional', note: 'Simple "Veroton myynti" suffices. Citing AVL section is recommended but not mandatory (Vero.fi 5.1.11)' },
|
|
76
|
-
{ kohta: 12, field: 'Merkintä käännetystä verovelvollisuudesta', english: 'Reverse charge notation', status: 'gap', relevance: 'conditional', note: '"Käännetty verovelvollisuus" phrase is mandatory. Legal basis citation is recommended, not required' },
|
|
77
|
-
{ kohta: 13, field: 'Merkintä itselaskutuksesta', english: 'Self-billing notation', status: 'n/a', relevance: 'situational', note: 'When buyer issues invoice on seller behalf' },
|
|
78
|
-
{ kohta: 14, field: 'Uuden kuljetusvälineen tiedot', english: 'New means of transport details', status: 'n/a', relevance: 'situational' },
|
|
79
|
-
{ kohta: 15, field: 'Voittomarginaalijärjestelmä', english: 'Margin scheme (used goods)', status: 'n/a', relevance: 'situational' },
|
|
80
|
-
{ kohta: 16, field: 'Voittomarginaalijärjestelmä — matkatoimistot', english: 'Margin scheme (travel agents)', status: 'n/a', relevance: 'situational' },
|
|
81
|
-
{ kohta: 17, field: 'Verollisen sijoituskullan myynti', english: 'Taxable investment gold', status: 'n/a', relevance: 'situational' },
|
|
82
|
-
{ kohta: 18, field: 'Viittaus alkuperäiseen laskuun', english: 'Credit note reference (muutoslasku)', status: 'not_implemented', relevance: 'conditional', note: 'Only mandatory requirement: unambiguous reference to original invoice' },
|
|
83
|
-
],
|
|
84
|
-
simplified_invoice: {
|
|
85
|
-
threshold: 400,
|
|
86
|
-
note: 'Invoices ≤ 400 € may use simplified format (AVL 209 f §). Also: retail to consumers, restaurant/catering, passenger transport — regardless of amount',
|
|
87
|
-
},
|
|
88
|
-
muutoslasku: {
|
|
89
|
-
status: 'not_implemented',
|
|
90
|
-
mandatory: 'Unambiguous reference to original invoice (number or period)',
|
|
91
|
-
recommended: 'Hyvityslasku label, reason for credit, same numbering sequence — common practice, not legally mandated',
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
/**
|
|
95
|
-
* Filing deadlines and verokausi thresholds.
|
|
96
|
-
*/
|
|
97
|
-
const FILING_DEADLINES = {
|
|
98
|
-
vat_filing: {
|
|
99
|
-
thresholds: [
|
|
100
|
-
{ revenue: '< 30,000 €', periods: ['monthly', 'quarterly', 'annually'], note: 'Annual: deadline last day of February of following year (28.2 or 29.2 in leap year)' },
|
|
101
|
-
{ revenue: '30,000–100,000 €', periods: ['monthly', 'quarterly'], note: 'Quarterly: 12th of 2nd month after quarter' },
|
|
102
|
-
{ revenue: '> 100,000 €', periods: ['monthly'], note: 'Monthly: 12th of 2nd month after reporting month' },
|
|
103
|
-
],
|
|
104
|
-
monthly_example: 'January 2026 VAT → deadline 12.3.2026',
|
|
105
|
-
quarterly_deadlines: {
|
|
106
|
-
Q1: '12.5', Q2: '12.8', Q3: '12.11', Q4: '12.2 (next year)',
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
tyel: {
|
|
110
|
-
monthly_report: '5th of following month → pension company',
|
|
111
|
-
payment: '20th of following month → pension company',
|
|
112
|
-
},
|
|
113
|
-
tulorekisteri: {
|
|
114
|
-
palkkatietoilmoitus: '5 calendar days from payment date → Tulorekisteri',
|
|
115
|
-
tyonantajan_erillisilmoitus: '5th of following month → Tulorekisteri (sotu totals)',
|
|
116
|
-
},
|
|
117
|
-
sairausvakuutusmaksu_payment: '12th of following month → Vero (OmaVero, oma-aloitteinen)',
|
|
118
|
-
tilinpaatos: '4 months from fiscal year end (KPL 3:6)',
|
|
119
|
-
prh_filing: '8 months from fiscal year end (OYL 8:10)',
|
|
120
|
-
pienyrittaja: {
|
|
121
|
-
threshold: 20000,
|
|
122
|
-
note: 'Revenue under 20,000 €/year: no VAT registration obligation (from 1.1.2025, was 15,000 €)',
|
|
123
|
-
alarajahuojennus: 'REMOVED from 1.1.2025 — hard threshold, no progressive relief',
|
|
124
|
-
},
|
|
125
|
-
maksuperusteinen_alv: {
|
|
126
|
-
threshold: 500000,
|
|
127
|
-
note: 'Revenue under 500,000 €: may opt for cash-basis VAT (report when payment received/made, not when invoiced)',
|
|
128
|
-
application: 'Opt-in via OmaVero',
|
|
129
|
-
},
|
|
130
|
-
late_penalties: {
|
|
131
|
-
myohastymismaksu: '3 €/day for days 1-45 (max 135 €), then +2% of tax amount (max 15,000 € per tax type)',
|
|
132
|
-
viivastyskorko_2026: '9.5% for most taxes, 4.5% for inheritance tax',
|
|
133
|
-
correction_grace: 'Corrections within 45 days of original deadline: no penalty if change ≤ 500 €',
|
|
134
|
-
nil_return: 'Even when no VAT activity occurred, a nil return must be filed for each verokausi',
|
|
135
|
-
},
|
|
136
|
-
payment_deadline: 'Same as filing deadline (12th of M+2 for monthly/quarterly, last day of Feb for annual)',
|
|
137
|
-
omavero_field_codes: {
|
|
138
|
-
source: 'VSRALVKV tietuekuvaus v1.10 (27.2.2026)',
|
|
139
|
-
source_url: 'https://www.vero.fi/contentassets/ef5905e0f5b74bcba89aa9ba9c34015d/verohallinto_tietuekuvaus_vsralvkv_290824.pdf',
|
|
140
|
-
vat_amounts: {
|
|
141
|
-
301: 'Suoritettava 24%/25,5% vero kotimaan myynnistä (domestic sales standard rate)',
|
|
142
|
-
302: 'Suoritettava 14%/13,5% vero kotimaan myynnistä (domestic sales reduced rate)',
|
|
143
|
-
303: 'Suoritettava 10% vero kotimaan myynnistä (domestic sales 10%)',
|
|
144
|
-
304: 'Vero tavaroiden maahantuonnista EU:n ulkopuolelta (import VAT from outside EU)',
|
|
145
|
-
305: 'Vero tavaraostoista muista EU-maista (reverse charge VAT on EU goods)',
|
|
146
|
-
306: 'Vero palveluostoista muista EU-maista (reverse charge VAT on EU services)',
|
|
147
|
-
307: 'Verokauden vähennettävä vero (total deductible input VAT)',
|
|
148
|
-
308: 'Maksettava vero / Palautukseen oikeuttava vero (net payable/refundable). Formula: (301+302+303+304+305+306+318) - 307',
|
|
149
|
-
318: 'Vero rakentamispalvelun ja metalliromun ostoista (construction/scrap reverse charge VAT)',
|
|
150
|
-
},
|
|
151
|
-
tax_bases: {
|
|
152
|
-
309: '0-verokannan alainen liikevaihto (zero-rate turnover: exports, EU sales with deduction right)',
|
|
153
|
-
310: 'Tavaroiden maahantuonnit EU:n ulkopuolelta (goods imports from outside EU)',
|
|
154
|
-
311: 'Tavaroiden myynnit muihin EU-maihin (goods sales to EU)',
|
|
155
|
-
312: 'Palveluiden myynnit muihin EU-maihin (service sales to EU)',
|
|
156
|
-
313: 'Tavaraostot muista EU-maista (goods purchases from EU)',
|
|
157
|
-
314: 'Palveluostot muista EU-maista (service purchases from EU)',
|
|
158
|
-
319: 'Rakentamispalvelun ja metalliromun myynnit (construction/scrap sales)',
|
|
159
|
-
320: 'Rakentamispalvelun ja metalliromun ostot (construction/scrap purchases)',
|
|
160
|
-
},
|
|
161
|
-
removed_2025: '315, 316, 317 (alarajahuojennus fields) removed from 1.1.2025',
|
|
162
|
-
},
|
|
163
|
-
};
|
|
164
|
-
/**
|
|
165
|
-
* 2026 social insurance rates for salary booking.
|
|
166
|
-
*/
|
|
167
|
-
const SOCIAL_INSURANCE_2026 = {
|
|
168
|
-
year: FINNISH_RATES_2026.year,
|
|
169
|
-
sources: [
|
|
170
|
-
'https://www.etk.fi/ajankohtaista/tyoelakemaksut-vuonna-2026/',
|
|
171
|
-
'https://www.elo.fi/fi-fi/tyonantaja/tyel-vakuuttaminen/tyel-maksu/sosiaalivakuutusmaksut-2026',
|
|
172
|
-
'https://tyel-vakuutus.fi/tyonantajan-sivukulut/',
|
|
173
|
-
],
|
|
174
|
-
tyel: {
|
|
175
|
-
keskimaarainen: FINNISH_RATES_2026.social.tyelKeskimaarainen,
|
|
176
|
-
perusmaksu: FINNISH_RATES_2026.social.tyelPerusmaksu,
|
|
177
|
-
tilapaistyonantaja: 25.85,
|
|
178
|
-
employee: FINNISH_RATES_2026.social.tyelEmployee,
|
|
179
|
-
employer_average: FINNISH_RATES_2026.social.tyelEmployerAverage,
|
|
180
|
-
employer_perusmaksu: FINNISH_RATES_2026.social.tyelEmployer,
|
|
181
|
-
note: 'Perusmaksu 24.85% is what pension companies bill before company-specific adjustments. Keskimääräinen 24.40% is the actual average. Age differentiation eliminated in 2026 (ETK)',
|
|
182
|
-
},
|
|
183
|
-
sotu: {
|
|
184
|
-
rate: FINNISH_RATES_2026.social.sotu,
|
|
185
|
-
payer: 'employer',
|
|
186
|
-
note: 'Sairausvakuutusmaksu, up from 1.87% in 2025. Paid monthly to Vero',
|
|
187
|
-
},
|
|
188
|
-
ttv: {
|
|
189
|
-
employer: FINNISH_RATES_2026.social.ttvEmployer,
|
|
190
|
-
employer_large: FINNISH_RATES_2026.social.ttvEmployerLarge,
|
|
191
|
-
employer_large_threshold: FINNISH_RATES_2026.social.ttvEmployerLargeThreshold,
|
|
192
|
-
employee: FINNISH_RATES_2026.social.ttvEmployee,
|
|
193
|
-
note: 'Työttömyysvakuutus. Employer rate 0.31% for payroll ≤ 2,509,500 €; 1.23% above. Employee up from 0.59% in 2025',
|
|
194
|
-
},
|
|
195
|
-
tapaturma: {
|
|
196
|
-
average: FINNISH_RATES_2026.social.tapaturmaAverage,
|
|
197
|
-
range: '0.05–5.00%',
|
|
198
|
-
payer: 'employer',
|
|
199
|
-
note: 'Company-specific rate based on industry and claims history',
|
|
200
|
-
},
|
|
201
|
-
ryhmahenkivakuutus: {
|
|
202
|
-
rate: FINNISH_RATES_2026.social.ryhmahenki,
|
|
203
|
-
payer: 'employer',
|
|
204
|
-
note: 'Standardized via collective agreement',
|
|
205
|
-
},
|
|
206
|
-
total_employer_side_costs: '~20% of gross salary (incl. TyEL employer share)',
|
|
207
|
-
accounts: {
|
|
208
|
-
salary: '7010 Palkat ja palkkiot',
|
|
209
|
-
tyel_expense: '7020 Eläkekulut (TyEL)',
|
|
210
|
-
sotu_expense: '7030 Muut henkilösivukulut',
|
|
211
|
-
withholding_payable: '4060 Ennakonpidätysvelka',
|
|
212
|
-
tyel_payable: '4080 TyEL-velka',
|
|
213
|
-
ttv_payable: '4070 Sosiaaliturvamaksuvelka',
|
|
214
|
-
bank: '2320 Pankkitili',
|
|
215
|
-
},
|
|
216
|
-
};
|
|
217
|
-
/**
|
|
218
|
-
* KPL bookkeeping requirements.
|
|
219
|
-
*/
|
|
220
|
-
const BOOKKEEPING_RULES = {
|
|
221
|
-
legal_basis: 'Kirjanpitolaki 1336/1997',
|
|
222
|
-
core_principles: [
|
|
223
|
-
{ rule: 'Double-entry (kahdenkertainen kirjanpito)', basis: 'KPL 2:1', note: 'Every entry: total debits = total credits' },
|
|
224
|
-
{ rule: 'Voucher principle (tositeperiaate)', basis: 'KPL 2:5', note: 'Every entry must be based on a numbered voucher' },
|
|
225
|
-
{ rule: 'Audit trail (kirjausketju)', basis: 'KPL 2:6', note: 'Tosite → tositenumero → päiväkirja → pääkirja → tilinpäätös — unbroken chain' },
|
|
226
|
-
{ rule: 'Sequential numbering', basis: 'KPL 2:5', note: 'Gap detection must be possible from the numbering sequence' },
|
|
227
|
-
{ rule: 'Closed period immutability', basis: 'KPL 2:7', note: 'Corrections to closed periods must use new entries in current period' },
|
|
228
|
-
],
|
|
229
|
-
retention: [
|
|
230
|
-
{ type: 'Accounting records (kirjanpitokirjat)', years: 10, basis: 'KPL 2:10 1 mom' },
|
|
231
|
-
{ type: 'Financial statements (tilinpäätös)', years: 10, basis: 'KPL 3:9' },
|
|
232
|
-
{ type: 'Vouchers, invoices (tositteet)', years: 6, basis: 'KPL 2:10 2 mom' },
|
|
233
|
-
{ type: 'E-invoice XML originals', years: 6, basis: 'KPL 2:7' },
|
|
234
|
-
],
|
|
235
|
-
fiscal_year: {
|
|
236
|
-
normal_duration: '12 months',
|
|
237
|
-
max_duration: '18 months (first/changed period only)',
|
|
238
|
-
tilinpaatos_deadline: '4 months from fiscal year end',
|
|
239
|
-
prh_deadline: '8 months from fiscal year end (Oy, Oyj)',
|
|
240
|
-
},
|
|
241
|
-
company_sizes: [
|
|
242
|
-
{ class: 'Mikroyritys', revenue: '< 900K', balance: '< 450K', employees: '< 10', tilinpaatos: 'Simplified (PMA)' },
|
|
243
|
-
{ class: 'Pienyritys', revenue: '< 15M', balance: '< 7.5M', employees: '< 50', tilinpaatos: 'Standard (PMA)' },
|
|
244
|
-
{ class: 'Keskisuuri', revenue: '< 50M', balance: '< 25M', employees: '< 250', tilinpaatos: 'Full (KPA) + toimintakertomus for Oy/Osk' },
|
|
245
|
-
{ class: 'Suuryritys', revenue: '≥ 50M', balance: '≥ 25M', employees: '≥ 250', tilinpaatos: 'Full + toimintakertomus + rahoituslaskelma' },
|
|
246
|
-
],
|
|
247
|
-
};
|
|
248
|
-
// Topic → (description, has_dated_rates, body). Dated topics get `freshnessMeta()`
|
|
249
|
-
// stamped at read time; undated topics (invoicing, bookkeeping) get published_at
|
|
250
|
-
// from the rates module so the agent still has a single freshness axis.
|
|
251
|
-
const TOPICS = {
|
|
252
|
-
vat: {
|
|
253
|
-
description: 'Finnish VAT compliance: rates (25.5/13.5/10/0%/exempt), rate history, deduction rules (edustus/neuvottelu/insurance), reverse charge, and common mistakes. CRITICAL: 0% (zero-rated, input VAT deductible) ≠ exempt (no input VAT deduction). Legal basis: AVL 1501/1993.',
|
|
254
|
-
dated: true,
|
|
255
|
-
body: VAT_COMPLIANCE,
|
|
256
|
-
},
|
|
257
|
-
invoicing: {
|
|
258
|
-
description: 'Invoice requirements per AVL 209 e §: 18 mandatory markings (kohdat 1-18), muutoslasku/hyvityslasku rules, simplified invoice (≤400€ + retail/restaurant). Shows implementation status. Source: vero.fi',
|
|
259
|
-
dated: false,
|
|
260
|
-
body: INVOICE_REQUIREMENTS,
|
|
261
|
-
},
|
|
262
|
-
deadlines: {
|
|
263
|
-
description: 'Finnish tax filing deadlines: VAT verokausi thresholds (<30K annual, <100K quarterly, >100K monthly), TyEL reporting, tilinpäätös deadlines, pienyrittäjä 20K€ threshold, maksuperusteinen ALV.',
|
|
264
|
-
dated: true,
|
|
265
|
-
body: FILING_DEADLINES,
|
|
266
|
-
},
|
|
267
|
-
'social-insurance': {
|
|
268
|
-
description: '2026 Finnish social insurance rates: TyEL (perusmaksu 24.85%, keskimääräinen 24.40%, employee 7.30%), sotu (1.91%), TTV (0.31%/0.89%), tapaturma (~0.51%), ryhmähenki (0.06%). Sources: ETK, Elo, tyel-vakuutus.fi',
|
|
269
|
-
dated: true,
|
|
270
|
-
body: SOCIAL_INSURANCE_2026,
|
|
271
|
-
},
|
|
272
|
-
bookkeeping: {
|
|
273
|
-
description: 'KPL (Kirjanpitolaki) requirements: double-entry, audit trail, voucher principle, retention periods (10/6 years), fiscal year rules (max 18 months), company size classification (mikro/pien/keski/suuri).',
|
|
274
|
-
dated: false,
|
|
275
|
-
body: BOOKKEEPING_RULES,
|
|
276
|
-
},
|
|
277
|
-
};
|
|
278
|
-
function wrapWithMeta(body, dated) {
|
|
279
|
-
const meta = freshnessMeta();
|
|
280
|
-
return JSON.stringify({
|
|
281
|
-
_meta: {
|
|
282
|
-
published_at: meta.published_at,
|
|
283
|
-
valid_from: meta.valid_from,
|
|
284
|
-
valid_through: dated ? meta.valid_through : null,
|
|
285
|
-
sources_verified_at: meta.sources_verified_at,
|
|
286
|
-
stale: dated ? meta.stale : false,
|
|
287
|
-
days_until_expiry: dated ? meta.days_until_expiry : null,
|
|
288
|
-
dated_rates: dated,
|
|
289
|
-
},
|
|
290
|
-
...body,
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
export function registerComplianceResources(server) {
|
|
294
|
-
for (const [slug, topic] of Object.entries(TOPICS)) {
|
|
295
|
-
server.registerResource(`compliance-${slug}`, `kirjapro://compliance/${slug}`, {
|
|
296
|
-
description: topic.description,
|
|
297
|
-
mimeType: 'application/json',
|
|
298
|
-
}, async (uri) => ({
|
|
299
|
-
contents: [{
|
|
300
|
-
uri: uri.href,
|
|
301
|
-
mimeType: 'application/json',
|
|
302
|
-
text: wrapWithMeta(topic.body, topic.dated),
|
|
303
|
-
}],
|
|
304
|
-
}));
|
|
305
|
-
}
|
|
306
|
-
// Freshness index: one-stop-shop for the agent to verify at session start
|
|
307
|
-
// whether any dated chunks have expired, without having to read all five
|
|
308
|
-
// compliance resources.
|
|
309
|
-
server.registerResource('compliance-freshness', 'kirjapro://compliance/_freshness', {
|
|
310
|
-
description: 'Freshness index for every dated compliance chunk. Read this at session start. Returns valid_through/stale/days_until_expiry for each topic so the agent can detect expired rates before relying on them.',
|
|
311
|
-
mimeType: 'application/json',
|
|
312
|
-
}, async (uri) => {
|
|
313
|
-
const meta = freshnessMeta();
|
|
314
|
-
const entries = Object.entries(TOPICS).map(([slug, topic]) => ({
|
|
315
|
-
topic: slug,
|
|
316
|
-
uri: `kirjapro://compliance/${slug}`,
|
|
317
|
-
dated_rates: topic.dated,
|
|
318
|
-
valid_through: topic.dated ? meta.valid_through : null,
|
|
319
|
-
stale: topic.dated ? meta.stale : false,
|
|
320
|
-
days_until_expiry: topic.dated ? meta.days_until_expiry : null,
|
|
321
|
-
}));
|
|
322
|
-
return {
|
|
323
|
-
contents: [{
|
|
324
|
-
uri: uri.href,
|
|
325
|
-
mimeType: 'application/json',
|
|
326
|
-
text: JSON.stringify({
|
|
327
|
-
_meta: {
|
|
328
|
-
sources_verified_at: meta.sources_verified_at,
|
|
329
|
-
published_at: meta.published_at,
|
|
330
|
-
rates_module: 'src/compliance/rates.ts',
|
|
331
|
-
source: FINNISH_RATES_2026.source,
|
|
332
|
-
},
|
|
333
|
-
topics: entries,
|
|
334
|
-
}),
|
|
335
|
-
}],
|
|
336
|
-
};
|
|
337
|
-
});
|
|
338
|
-
}
|
package/dist/resources/index.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { registerSchemaResources } from './schema.js';
|
|
2
|
-
import { registerCompanyResources } from './company.js';
|
|
3
|
-
import { registerComplianceResources } from './compliance.js';
|
|
4
|
-
import { registerSecurityResources } from './security.js';
|
|
5
|
-
export function registerResources(server) {
|
|
6
|
-
registerSchemaResources(server);
|
|
7
|
-
registerCompanyResources(server);
|
|
8
|
-
registerComplianceResources(server);
|
|
9
|
-
registerSecurityResources(server);
|
|
10
|
-
}
|