bangkok-train-fare 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/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # bangkok-train-fare
2
+
3
+ TypeScript utilities for Bangkok train station lookup, route building, and fare estimation.
4
+
5
+ This package is experimental. Fare rules and network data are manually maintained and may lag official updates.
6
+
7
+ ## Usage
8
+
9
+ ```bash
10
+ npm install bangkok-train-fare
11
+ ```
12
+
13
+ ```ts
14
+ import { calculateFare, searchStation } from "bangkok-train-fare";
15
+
16
+ const result = calculateFare("BL01", "BL02");
17
+ console.log(result.fare);
18
+
19
+ const stations = await searchStation("Siam");
20
+ console.log(stations);
21
+ ```
22
+
23
+ ## API
24
+
25
+ - `calculateFare(origin, destination)`
26
+ - `buildPath(origin, destination)`
27
+ - `calculateGreenFare(stations)`
28
+ - `searchStation(keyword)`
29
+ - `searchByCode(code)`
30
+
31
+ ## Publishing
32
+
33
+ The repository includes GitHub Actions workflows for CI, snapshot packages, and stable releases.
34
+
35
+ Snapshots are published with the `snapshot` npm dist-tag by manually running the `Publish` workflow with `channel=snapshot`:
36
+
37
+ ```bash
38
+ npm install bangkok-train-fare@snapshot
39
+ ```
40
+
41
+ Stable releases are published by updating `package.json` version, committing it, and pushing a matching tag:
42
+
43
+ ```bash
44
+ npm version patch
45
+ git push origin main --tags
46
+ ```
47
+
48
+ Publishing uses npm trusted publishing through GitHub Actions OIDC. Configure the package on npm with:
49
+
50
+ - Owner: `kratuwu`
51
+ - Repository: `bangkok-train-fare`
52
+ - Workflow filename: `publish.yml`
53
+ - Environment: `npm`
@@ -0,0 +1,6 @@
1
+ export { calculateGreenFare } from "./greenFareCalculator.js";
2
+ import { buildPath } from "./pathfinder.js";
3
+ export type FareResult = ReturnType<typeof buildPath> & {
4
+ fare: number;
5
+ };
6
+ export declare function calculateFare(origin: string, destination: string): FareResult;
@@ -0,0 +1,6 @@
1
+ export { calculateGreenFare } from "./greenFareCalculator.js";
2
+ import { buildPath } from "./pathfinder.js";
3
+ export function calculateFare(origin, destination) {
4
+ const { pathes, cost } = buildPath(origin, destination);
5
+ return { fare: cost, cost, pathes };
6
+ }
@@ -0,0 +1,7 @@
1
+ export declare function blueFare(distance: number): number;
2
+ export declare function purpleFare(distance: number): number;
3
+ export declare function yellowFare(distance: number): number;
4
+ export declare function pinkFare(distance: number): number;
5
+ export declare function transferDiscount(toLine: string): number;
6
+ export declare function greenFare(distance: number): number;
7
+ export declare function extendedGreenFare(distance: number): number;
@@ -0,0 +1,155 @@
1
+ export function blueFare(distance) {
2
+ if (distance === 0)
3
+ return 16;
4
+ if (distance === 1)
5
+ return 17;
6
+ if (distance === 2)
7
+ return 20;
8
+ if (distance === 3)
9
+ return 22;
10
+ if (distance === 4)
11
+ return 25;
12
+ if (distance === 5)
13
+ return 27;
14
+ if (distance === 6)
15
+ return 30;
16
+ if (distance === 7)
17
+ return 32;
18
+ if (distance === 8)
19
+ return 35;
20
+ if (distance === 9)
21
+ return 37;
22
+ if (distance === 10)
23
+ return 40;
24
+ if (distance === 11)
25
+ return 42;
26
+ else
27
+ return 45;
28
+ }
29
+ export function purpleFare(distance) {
30
+ if (distance === 0)
31
+ return 14;
32
+ if (distance === 1)
33
+ return 17;
34
+ if (distance === 2)
35
+ return 20;
36
+ if (distance === 3)
37
+ return 23;
38
+ if (distance === 4)
39
+ return 25;
40
+ if (distance === 5)
41
+ return 27;
42
+ if (distance === 6)
43
+ return 30;
44
+ if (distance === 7)
45
+ return 33;
46
+ if (distance === 8)
47
+ return 36;
48
+ if (distance === 9)
49
+ return 38;
50
+ if (distance === 10)
51
+ return 40;
52
+ else
53
+ return 42;
54
+ }
55
+ export function yellowFare(distance) {
56
+ if (distance === 0)
57
+ return 15;
58
+ if (distance === 1)
59
+ return 19;
60
+ if (distance === 2)
61
+ return 23;
62
+ if (distance === 3)
63
+ return 27;
64
+ if (distance === 4)
65
+ return 30;
66
+ if (distance === 5)
67
+ return 33;
68
+ if (distance === 6)
69
+ return 36;
70
+ if (distance === 7)
71
+ return 39;
72
+ if (distance === 8)
73
+ return 42;
74
+ else
75
+ return 45;
76
+ }
77
+ export function pinkFare(distance) {
78
+ if (distance === 0)
79
+ return 15;
80
+ if (distance === 1)
81
+ return 18;
82
+ if (distance === 2)
83
+ return 23;
84
+ if (distance === 3)
85
+ return 28;
86
+ if (distance === 4)
87
+ return 30;
88
+ if (distance === 5)
89
+ return 34;
90
+ if (distance === 6)
91
+ return 37;
92
+ if (distance === 7)
93
+ return 41;
94
+ if (distance === 8)
95
+ return 44;
96
+ else
97
+ return 45;
98
+ }
99
+ export function transferDiscount(toLine) {
100
+ if (toLine === "BL")
101
+ return -14;
102
+ if (toLine === "PP")
103
+ return -14;
104
+ if (toLine === "YL")
105
+ return -15;
106
+ if (toLine === "PU")
107
+ return -15;
108
+ return 0;
109
+ }
110
+ export function greenFare(distance) {
111
+ if (distance === 0)
112
+ return 17;
113
+ if (distance === 1)
114
+ return 17;
115
+ if (distance === 2)
116
+ return 25;
117
+ if (distance === 3)
118
+ return 28;
119
+ if (distance === 4)
120
+ return 32;
121
+ if (distance === 5)
122
+ return 35;
123
+ if (distance === 6)
124
+ return 40;
125
+ if (distance === 7)
126
+ return 43;
127
+ return 47;
128
+ }
129
+ export function extendedGreenFare(distance) {
130
+ if (distance === 0)
131
+ return 17;
132
+ if (distance === 1)
133
+ return 17;
134
+ if (distance === 2)
135
+ return 22;
136
+ if (distance === 3)
137
+ return 24;
138
+ if (distance === 4)
139
+ return 27;
140
+ if (distance === 5)
141
+ return 29;
142
+ if (distance === 6)
143
+ return 32;
144
+ if (distance === 7)
145
+ return 34;
146
+ if (distance === 7)
147
+ return 37;
148
+ if (distance === 7)
149
+ return 39;
150
+ if (distance === 7)
151
+ return 42;
152
+ if (distance === 7)
153
+ return 44;
154
+ return 45;
155
+ }
@@ -0,0 +1,12 @@
1
+ export declare const GRAPH: Record<string, string[]>;
2
+ type LineName = "blue" | "green" | "orange" | "pink" | "purple" | "yellow";
3
+ declare function normalize(code: string): string;
4
+ declare function isBlue(code: string): boolean;
5
+ declare function isPurple(code: string): boolean;
6
+ declare function isPink(code: string): boolean;
7
+ declare function isOrange(code: string): boolean;
8
+ declare function isYellow(code: string): boolean;
9
+ declare function isGreen(code: string): boolean;
10
+ export declare function getLine(code: string): LineName;
11
+ export declare function isExtendedGreen(code: string): boolean;
12
+ export { isBlue, isPurple, isPink, isYellow, isOrange, isGreen, normalize };
@@ -0,0 +1,172 @@
1
+ import network from "./network.json" with { type: "json" };
2
+ export const GRAPH = {};
3
+ const LINE_NAMES = new Set([
4
+ "blue",
5
+ "green",
6
+ "orange",
7
+ "pink",
8
+ "purple",
9
+ "yellow",
10
+ ]);
11
+ function isRecord(value) {
12
+ return typeof value === "object" && value !== null;
13
+ }
14
+ function isLineName(value) {
15
+ return typeof value === "string" && LINE_NAMES.has(value);
16
+ }
17
+ function isStringArray(value) {
18
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
19
+ }
20
+ function isStationRange(value) {
21
+ if (!isRecord(value))
22
+ return false;
23
+ return (typeof value.prefix === "string" &&
24
+ typeof value.start === "number" &&
25
+ typeof value.end === "number" &&
26
+ (value.padCode === undefined || typeof value.padCode === "boolean") &&
27
+ (value.cyclic === undefined || typeof value.cyclic === "boolean"));
28
+ }
29
+ function isNetworkLine(value) {
30
+ if (!isRecord(value))
31
+ return false;
32
+ return (isLineName(value.name) &&
33
+ (value.prefixes === undefined || isStringArray(value.prefixes)) &&
34
+ (value.stations === undefined || isStringArray(value.stations)) &&
35
+ Array.isArray(value.ranges) &&
36
+ value.ranges.every(isStationRange));
37
+ }
38
+ function isEdge(value) {
39
+ return (Array.isArray(value) &&
40
+ value.length === 2 &&
41
+ typeof value[0] === "string" &&
42
+ typeof value[1] === "string");
43
+ }
44
+ function isAliases(value) {
45
+ return (isRecord(value) &&
46
+ Object.values(value).every((item) => typeof item === "string"));
47
+ }
48
+ function parseNetworkData(value) {
49
+ if (!isRecord(value)) {
50
+ throw new Error("Invalid train fare network data");
51
+ }
52
+ const { aliases, edges, lines } = value;
53
+ if (!Array.isArray(lines) ||
54
+ !lines.every(isNetworkLine) ||
55
+ !Array.isArray(edges) ||
56
+ !edges.every(isEdge) ||
57
+ !isAliases(aliases)) {
58
+ throw new Error("Invalid train fare network data");
59
+ }
60
+ return { aliases, edges, lines };
61
+ }
62
+ const networkData = parseNetworkData(network);
63
+ const stationLines = {};
64
+ const prefixLines = {};
65
+ function stationCode(prefix, index, padCode = true) {
66
+ const codeNumber = padCode ? String(index).padStart(2, "0") : String(index);
67
+ return `${prefix}${codeNumber}`;
68
+ }
69
+ function ensureStation(code) {
70
+ GRAPH[code] ??= [];
71
+ }
72
+ function assignStationLine(code, line) {
73
+ stationLines[code] = line;
74
+ }
75
+ function assignPrefixLine(prefix, line) {
76
+ prefixLines[prefix] = line;
77
+ }
78
+ function connect(a, b) {
79
+ ensureStation(a);
80
+ ensureStation(b);
81
+ if (!GRAPH[a].includes(b))
82
+ GRAPH[a].push(b);
83
+ if (!GRAPH[b].includes(a))
84
+ GRAPH[b].push(a);
85
+ }
86
+ function createStations({ prefix, start, end, padCode = true }) {
87
+ for (let i = start; i <= end; i++) {
88
+ ensureStation(stationCode(prefix, i, padCode));
89
+ }
90
+ }
91
+ function connectStations({ prefix, start, end, padCode = true, cyclic = false, }) {
92
+ for (let i = start; i < end; i++) {
93
+ connect(stationCode(prefix, i, padCode), stationCode(prefix, i + 1, padCode));
94
+ }
95
+ if (cyclic) {
96
+ connect(stationCode(prefix, end, padCode), stationCode(prefix, start, padCode));
97
+ }
98
+ }
99
+ function createLine(range) {
100
+ createStations(range);
101
+ connectStations(range);
102
+ }
103
+ function registerLine(line) {
104
+ for (const prefix of line.prefixes ?? []) {
105
+ assignPrefixLine(prefix, line.name);
106
+ }
107
+ for (const station of line.stations ?? []) {
108
+ ensureStation(station);
109
+ assignStationLine(station, line.name);
110
+ }
111
+ for (const range of line.ranges) {
112
+ assignPrefixLine(range.prefix, line.name);
113
+ createLine(range);
114
+ for (let i = range.start; i <= range.end; i++) {
115
+ assignStationLine(stationCode(range.prefix, i, range.padCode), line.name);
116
+ }
117
+ }
118
+ }
119
+ for (const line of networkData.lines) {
120
+ registerLine(line);
121
+ }
122
+ for (const [from, to] of networkData.edges) {
123
+ connect(from, to);
124
+ }
125
+ function normalize(code) {
126
+ return networkData.aliases[code] ?? code;
127
+ }
128
+ function matchesLine(code, line) {
129
+ if (stationLines[code] === line)
130
+ return true;
131
+ return Object.entries(prefixLines).some(([prefix, prefixLine]) => prefixLine === line && code.startsWith(prefix));
132
+ }
133
+ function isBlue(code) {
134
+ return matchesLine(code, "blue");
135
+ }
136
+ function isPurple(code) {
137
+ return matchesLine(code, "purple");
138
+ }
139
+ function isPink(code) {
140
+ return matchesLine(code, "pink");
141
+ }
142
+ function isOrange(code) {
143
+ return matchesLine(code, "orange");
144
+ }
145
+ function isYellow(code) {
146
+ return matchesLine(code, "yellow");
147
+ }
148
+ function isGreen(code) {
149
+ return matchesLine(code, "green");
150
+ }
151
+ export function getLine(code) {
152
+ const line = stationLines[code];
153
+ if (line)
154
+ return line;
155
+ const prefixLine = Object.entries(prefixLines).find(([prefix]) => code.startsWith(prefix))?.[1];
156
+ if (prefixLine)
157
+ return prefixLine;
158
+ throw new Error("Unknown station code");
159
+ }
160
+ export function isExtendedGreen(code) {
161
+ if (code === "CEN" || code === "W1")
162
+ return false;
163
+ const stationNumber = Number.parseInt(code.slice(1), 10);
164
+ if (Number.isNaN(stationNumber))
165
+ return false;
166
+ if (code.startsWith("N") || code.startsWith("S"))
167
+ return stationNumber >= 9;
168
+ if (code.startsWith("E"))
169
+ return stationNumber >= 10;
170
+ return false;
171
+ }
172
+ export { isBlue, isPurple, isPink, isYellow, isOrange, isGreen, normalize };
@@ -0,0 +1 @@
1
+ export declare function calculateGreenFare(stations: string[]): number;
@@ -0,0 +1,15 @@
1
+ import { extendedGreenFare, greenFare } from "./farePolicy.js";
2
+ import { isExtendedGreen } from "./graph.js";
3
+ export function calculateGreenFare(stations) {
4
+ if (stations.length <= 1)
5
+ return greenFare(0);
6
+ const totalStops = stations.length - 1;
7
+ const extStart = stations.findIndex(isExtendedGreen);
8
+ if (extStart < 0)
9
+ return greenFare(totalStops);
10
+ if (extStart === 0)
11
+ return extendedGreenFare(totalStops);
12
+ const baseStops = extStart - 1;
13
+ const extStops = stations.length - extStart;
14
+ return Math.min(greenFare(baseStops) + extendedGreenFare(extStops), 65);
15
+ }
@@ -0,0 +1,4 @@
1
+ export { calculateFare, calculateGreenFare, type FareResult, } from "./fareEngine.js";
2
+ export { buildPath, type BuiltPathResult, type BuiltPathStep, } from "./pathfinder.js";
3
+ export { blueFare, extendedGreenFare, greenFare, pinkFare, purpleFare, transferDiscount, yellowFare, } from "./farePolicy.js";
4
+ export { GRAPH, getLine, isBlue, isExtendedGreen, isGreen, isOrange, isPink, isPurple, isYellow, normalize, } from "./graph.js";
@@ -0,0 +1,4 @@
1
+ export { calculateFare, calculateGreenFare, } from "./fareEngine.js";
2
+ export { buildPath, } from "./pathfinder.js";
3
+ export { blueFare, extendedGreenFare, greenFare, pinkFare, purpleFare, transferDiscount, yellowFare, } from "./farePolicy.js";
4
+ export { GRAPH, getLine, isBlue, isExtendedGreen, isGreen, isOrange, isPink, isPurple, isYellow, normalize, } from "./graph.js";
@@ -0,0 +1,68 @@
1
+ {
2
+ "lines": [
3
+ {
4
+ "name": "blue",
5
+ "ranges": [
6
+ { "prefix": "BL", "start": 1, "end": 32, "cyclic": true },
7
+ { "prefix": "BL", "start": 33, "end": 38 }
8
+ ]
9
+ },
10
+ {
11
+ "name": "purple",
12
+ "ranges": [{ "prefix": "PP", "start": 1, "end": 16 }]
13
+ },
14
+ {
15
+ "name": "yellow",
16
+ "ranges": [{ "prefix": "YL", "start": 1, "end": 23 }]
17
+ },
18
+ {
19
+ "name": "pink",
20
+ "ranges": [
21
+ { "prefix": "MT", "start": 1, "end": 2 },
22
+ { "prefix": "PK", "start": 1, "end": 30 }
23
+ ]
24
+ },
25
+ {
26
+ "name": "orange",
27
+ "prefixes": ["OL"],
28
+ "ranges": []
29
+ },
30
+ {
31
+ "name": "green",
32
+ "stations": ["CEN", "W1"],
33
+ "ranges": [
34
+ { "prefix": "N", "start": 1, "end": 5, "padCode": false },
35
+ { "prefix": "N", "start": 7, "end": 24, "padCode": false },
36
+ { "prefix": "E", "start": 1, "end": 23, "padCode": false },
37
+ { "prefix": "S", "start": 1, "end": 12, "padCode": false }
38
+ ]
39
+ }
40
+ ],
41
+ "edges": [
42
+ ["BL01", "BL33"],
43
+ ["BL10", "PP16"],
44
+ ["YL01", "BL15"],
45
+ ["PK10", "MT01"],
46
+ ["PP11", "PK01"],
47
+ ["N5", "N7"],
48
+ ["N1", "CEN"],
49
+ ["CEN", "E1"],
50
+ ["W1", "CEN"],
51
+ ["CEN", "S1"],
52
+ ["E4", "BL22"],
53
+ ["S2", "BL26"],
54
+ ["E15", "YL23"]
55
+ ],
56
+ "aliases": {
57
+ "BL10": "TAOPOON",
58
+ "PP16": "TAOPOON",
59
+ "BL15": "LARDPRAO",
60
+ "YL01": "LARDPRAO",
61
+ "BL22": "ASOK",
62
+ "E4": "ASOK",
63
+ "BL26": "SALDAENG",
64
+ "S2": "SALDAENG",
65
+ "E15": "SAMRONG",
66
+ "YL23": "SAMRONG"
67
+ }
68
+ }
@@ -0,0 +1,21 @@
1
+ type PathSegment = {
2
+ type: "segment";
3
+ line: string;
4
+ from: string;
5
+ to: string;
6
+ stops: number;
7
+ stations: string[];
8
+ };
9
+ type PathChange = {
10
+ type: "change";
11
+ from: string;
12
+ to: string;
13
+ toLine: string;
14
+ };
15
+ export type BuiltPathStep = PathSegment | PathChange;
16
+ export type BuiltPathResult = {
17
+ pathes: BuiltPathStep[];
18
+ cost: number;
19
+ };
20
+ export declare function buildPath(origin: string, dest: string): BuiltPathResult;
21
+ export {};
@@ -0,0 +1,149 @@
1
+ import { getLine, GRAPH } from "./graph.js";
2
+ import { blueFare, pinkFare, purpleFare, yellowFare, transferDiscount, } from "./farePolicy.js";
3
+ import { calculateGreenFare } from "./greenFareCalculator.js";
4
+ function getLineFareByStops(line, stops) {
5
+ if (line === "blue")
6
+ return blueFare(stops);
7
+ if (line === "pink")
8
+ return pinkFare(stops);
9
+ if (line === "purple")
10
+ return purpleFare(stops);
11
+ if (line === "yellow")
12
+ return yellowFare(stops);
13
+ return 0;
14
+ }
15
+ function getLineFare(line, stations) {
16
+ if (line === "green")
17
+ return calculateGreenFare(stations);
18
+ return getLineFareByStops(line, stations.length - 1);
19
+ }
20
+ function getFarePolicyLineCode(line) {
21
+ if (line === "blue")
22
+ return "BL";
23
+ if (line === "purple")
24
+ return "PP";
25
+ if (line === "yellow")
26
+ return "YL";
27
+ if (line === "pink")
28
+ return "PU";
29
+ if (line === "orange")
30
+ return "OL";
31
+ return line;
32
+ }
33
+ function getTransferDiscount(line) {
34
+ return transferDiscount(getFarePolicyLineCode(line));
35
+ }
36
+ function cheapestPath(start, end) {
37
+ const startLine = getLine(start);
38
+ if (start === end) {
39
+ return {
40
+ stations: [start],
41
+ cost: getLineFare(startLine, [start]),
42
+ };
43
+ }
44
+ const queue = [
45
+ {
46
+ station: start,
47
+ line: startLine,
48
+ stationsOnLine: [start],
49
+ completedCost: 0,
50
+ totalCost: getLineFare(startLine, [start]),
51
+ path: [start],
52
+ },
53
+ ];
54
+ const visited = new Map();
55
+ while (queue.length > 0) {
56
+ queue.sort((a, b) => a.totalCost - b.totalCost);
57
+ const state = queue.shift();
58
+ const stateKey = `${state.station}:${state.line}`;
59
+ if (visited.has(stateKey) && visited.get(stateKey) <= state.totalCost) {
60
+ continue;
61
+ }
62
+ visited.set(stateKey, state.totalCost);
63
+ if (state.station === end) {
64
+ return {
65
+ stations: state.path,
66
+ cost: state.totalCost,
67
+ };
68
+ }
69
+ for (const neighbor of GRAPH[state.station] || []) {
70
+ const neighborLine = getLine(neighbor);
71
+ let completedCost = state.completedCost;
72
+ let stationsOnLine = state.stationsOnLine;
73
+ let totalCost = state.totalCost;
74
+ if (neighborLine === state.line) {
75
+ stationsOnLine = [...state.stationsOnLine, neighbor];
76
+ totalCost = completedCost + getLineFare(state.line, stationsOnLine);
77
+ }
78
+ else {
79
+ completedCost =
80
+ state.completedCost +
81
+ getLineFare(state.line, state.stationsOnLine) +
82
+ getTransferDiscount(neighborLine);
83
+ stationsOnLine = [neighbor];
84
+ totalCost = completedCost + getLineFare(neighborLine, stationsOnLine);
85
+ }
86
+ const neighborStateKey = `${neighbor}:${neighborLine}`;
87
+ if (!visited.has(neighborStateKey) ||
88
+ totalCost < visited.get(neighborStateKey)) {
89
+ queue.push({
90
+ station: neighbor,
91
+ line: neighborLine,
92
+ stationsOnLine,
93
+ completedCost,
94
+ totalCost,
95
+ path: [...state.path, neighbor],
96
+ });
97
+ }
98
+ }
99
+ }
100
+ throw new Error("Path not found");
101
+ }
102
+ export function buildPath(origin, dest) {
103
+ const { stations: pathes, cost } = cheapestPath(origin, dest);
104
+ const result = [];
105
+ let start = pathes[0];
106
+ let line = getLine(start);
107
+ let stops = 0;
108
+ let stations = [pathes[0]];
109
+ for (let i = 1; i < pathes.length; i++) {
110
+ const station = pathes[i];
111
+ const stationLine = getLine(station);
112
+ if (stationLine === line) {
113
+ stops++;
114
+ stations.push(station);
115
+ }
116
+ else {
117
+ result.push({
118
+ type: "segment",
119
+ line,
120
+ from: start,
121
+ to: pathes[i - 1],
122
+ stops,
123
+ stations: [...stations],
124
+ });
125
+ result.push({
126
+ type: "change",
127
+ from: pathes[i - 1],
128
+ to: station,
129
+ toLine: stationLine,
130
+ });
131
+ start = station;
132
+ line = stationLine;
133
+ stops = 0;
134
+ stations = [station];
135
+ }
136
+ }
137
+ result.push({
138
+ type: "segment",
139
+ line,
140
+ from: start,
141
+ to: pathes[pathes.length - 1],
142
+ stops,
143
+ stations: [...stations],
144
+ });
145
+ return {
146
+ pathes: result,
147
+ cost,
148
+ };
149
+ }
@@ -0,0 +1,4 @@
1
+ export { calculateFare, calculateGreenFare, type FareResult, } from "./fare/fareEngine.js";
2
+ export { buildPath, type BuiltPathResult, type BuiltPathStep, } from "./fare/pathfinder.js";
3
+ export { searchByCode, searchStation } from "./station/service.js";
4
+ export type { Station } from "./station/model.js";