|
| 1 | +import { describe, expect, it } from "vitest"; |
| 2 | +import { parseBcnTramsFlow } from "../flow-bcn.js"; |
| 3 | +import { parseBcnTramsStations } from "../stations-bcn.js"; |
| 4 | +import type { SiteGeometry } from "../siteTable.js"; |
| 5 | +import type { SourceDescriptor } from "../types.js"; |
| 6 | + |
| 7 | +const src = { |
| 8 | + id: "es-bcn-ajuntament", |
| 9 | + attribution: "Ajuntament de Barcelona", |
| 10 | + country: "ES", |
| 11 | + license: "CC-BY-4.0", |
| 12 | +} as SourceDescriptor; |
| 13 | + |
| 14 | +const CSV = `Tram,Tram_Components,Descripció,Longitud,Latitud |
| 15 | +1,1,"Diagonal (Ronda de Dalt a Doctor Marañón)",2.11203535639414,41.3841912394771 |
| 16 | +1,2,"Diagonal (Ronda de Dalt a Doctor Marañón)",2.101502862881051,41.3816307921222 |
| 17 | +2,1,"Meridiana",2.18,41.42 |
| 18 | +2,2,"Meridiana",2.19,41.43 |
| 19 | +9,1,"Single vertex only",2.0,41.0`; |
| 20 | + |
| 21 | +describe("parseBcnTramsStations", () => { |
| 22 | + it("groups vertices per tram (ordered) into LineStrings and drops single-vertex trams", () => { |
| 23 | + const map = parseBcnTramsStations(CSV); |
| 24 | + expect(map.size).toBe(2); // tram 9 has one vertex → dropped |
| 25 | + const geom = map.get("1") as Extract<SiteGeometry, { type: "LineString" }>; |
| 26 | + expect(geom.type).toBe("LineString"); |
| 27 | + expect(geom.coordinates).toEqual([ |
| 28 | + [2.11203535639414, 41.3841912394771], |
| 29 | + [2.101502862881051, 41.3816307921222], |
| 30 | + ]); |
| 31 | + }); |
| 32 | +}); |
| 33 | + |
| 34 | +describe("parseBcnTramsFlow", () => { |
| 35 | + const siteMap = parseBcnTramsStations(CSV); |
| 36 | + |
| 37 | + it("joins status rows to geometry and maps the 0-6 scale to level-of-service", () => { |
| 38 | + const dat = ["1#20260729131557#2#2", "2#20260729131557#5#5"].join("\n"); |
| 39 | + const { flows, events } = parseBcnTramsFlow(dat, src, siteMap); |
| 40 | + expect(flows).toHaveLength(2); |
| 41 | + const t1 = flows.find((f) => f.id === "es-bcn-ajuntament:1")!; |
| 42 | + expect(t1.los).toBe("free_flow"); |
| 43 | + expect(t1.speedKph).toBeUndefined(); |
| 44 | + expect(t1.geometry.type).toBe("LineString"); |
| 45 | + expect(t1.dataUpdatedAt).toBe("2026-07-29T13:15:57"); |
| 46 | + const t2 = flows.find((f) => f.id === "es-bcn-ajuntament:2")!; |
| 47 | + expect(t2.los).toBe("stationary"); |
| 48 | + expect(t2.sourceFormat).toBe("bcn-trams"); |
| 49 | + // Only the congested (stationary) segment yields a derived congestion event. |
| 50 | + expect(events).toHaveLength(1); |
| 51 | + expect(events[0]!.type).toBe("congestion"); |
| 52 | + }); |
| 53 | + |
| 54 | + it("skips status 0 (sensor down) and segments with no known geometry", () => { |
| 55 | + const dat = ["1#20260729131557#0#0", "999#20260729131557#3#3"].join("\n"); |
| 56 | + const { flows } = parseBcnTramsFlow(dat, src, siteMap); |
| 57 | + expect(flows).toHaveLength(0); |
| 58 | + }); |
| 59 | + |
| 60 | + it("flags a hard parse failure on an empty/garbage body", () => { |
| 61 | + expect(parseBcnTramsFlow("", src, siteMap).failed).toBe(true); |
| 62 | + expect(parseBcnTramsFlow("<html>error</html>", src, siteMap).failed).toBe(true); |
| 63 | + }); |
| 64 | +}); |
0 commit comments