compare-laermwerte: Testprogramm zum Vergleich von Laermwerten zweier API-Instanzen

Ruft dieselben Parameter (sensorid/data/span/datetime/peak) gegen zwei
"getsensordata"-Endpunkte ab und diffed die Werte zeitstempelweise, generisch
über alle Felder (funktioniert für live genauso wie für havg/davg/daynight/lden).
Diente als Verifikation der VictoriaMetrics-Anbindung: live-Werte stimmen exakt,
havg-Werte liegen weit innerhalb der Toleranz gegenüber der Influx-Produktion.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 10:59:56 +02:00
parent 23fe7d2ed2
commit 21050f51c9
3 changed files with 630 additions and 0 deletions
+249
View File
@@ -0,0 +1,249 @@
// compare.js - vergleicht die Laermwerte zweier "noise"-API-Instanzen
// (z.B. lokaler VictoriaMetrics-Test-Stack vs. Produktion) fuer denselben
// Sensor/Zeitraum/Auswertungstyp. Siehe Laerm_API.md fuer die API-Parameter.
//
// Aufruf: node compare.js [-s sensorid] [-d live|havg|davg|daynight|lden] ...
// node compare.js -h fuer alle Optionen
import axios from 'axios'
import { DateTime } from 'luxon'
import mod_getopt from 'posix-getopt'
const DEFAULT_URL_A = 'http://localhost:3003/api/getsensordata'
const DEFAULT_URL_B = 'https://noise.citysensor.de/api/getsensordata'
const DEFAULT_SENSORID = '37833'
const DEFAULT_DATA = 'live'
const DEFAULT_SPAN = '1'
const DEFAULT_PEAK = '70'
const DEFAULT_TOLERANCE = 0.05
const MAX_EXAMPLES = 20
function parseArgs(argv) {
let opts = {
urlA: DEFAULT_URL_A,
urlB: DEFAULT_URL_B,
sensorid: DEFAULT_SENSORID,
data: DEFAULT_DATA,
span: DEFAULT_SPAN,
datetime: null,
peak: DEFAULT_PEAK,
tolerance: DEFAULT_TOLERANCE,
}
let parser = new mod_getopt.BasicParser(
'a:(urlA)b:(urlB)s:(sensorid)d:(data)p:(span)t:(datetime)k:(peak)e:(tolerance)h(help)v(version)',
argv
)
let option
while ((option = parser.getopt()) !== undefined) {
switch (option.option) {
case 'a': opts.urlA = option.optarg; break
case 'b': opts.urlB = option.optarg; break
case 's': opts.sensorid = option.optarg; break
case 'd': opts.data = option.optarg; break
case 'p': opts.span = option.optarg; break
case 't': opts.datetime = option.optarg; break
case 'k': opts.peak = option.optarg; break
case 'e': opts.tolerance = parseFloat(option.optarg); break
case 'v':
console.log('compare-laermwerte 1.0.0')
process.exit()
break
case 'h':
console.log('Usage: node compare.js [options]')
console.log('Options:')
console.log(` -a urlA Basis-URL Implementierung A (default: ${DEFAULT_URL_A})`)
console.log(` -b urlB Basis-URL Implementierung B (default: ${DEFAULT_URL_B})`)
console.log(` -s sensorid Sensor-ID (default: ${DEFAULT_SENSORID})`)
console.log(` -d data live|havg|davg|daynight|lden (default: ${DEFAULT_DATA})`)
console.log(` -p span Zeitspanne in Tagen (default: ${DEFAULT_SPAN})`)
console.log(' -t datetime Start-Zeitpunkt ISO8601 (default: unbelegt -> jetzt - span)')
console.log(` -k peak dB-Schwelle fuer peakcount, nur havg/davg (default: ${DEFAULT_PEAK})`)
console.log(` -e tolerance Toleranz fuer Zahlenvergleich, absolut (default: ${DEFAULT_TOLERANCE})`)
console.log(' -v Version anzeigen')
console.log(' -h diese Hilfe')
process.exit()
break
default:
break
}
}
return opts
}
function buildURL(base, opts) {
let params = {
sensorid: opts.sensorid,
data: opts.data,
span: opts.span,
peak: opts.peak,
}
if (opts.datetime) {
params.datetime = opts.datetime
}
let qs = new URLSearchParams(params).toString()
return `${base}?${qs}`
}
async function fetchSide(label, url) {
try {
let ret = await axios.get(url, { timeout: 15000 })
if (ret.data && ret.data.err) {
return { ok: false, err: `${label}: API-Fehler: ${ret.data.err}` }
}
return { ok: true, values: ret.data.values || [], options: ret.data.options }
} catch (e) {
let msg = e.response ? `HTTP ${e.response.status}` : e.message
return { ok: false, err: `${label}: Request fehlgeschlagen: ${msg}` }
}
}
function toMap(values) {
let map = new Map()
for (let row of values) {
let ms = DateTime.fromISO(row.datetime, { zone: 'utc' }).toMillis()
if (Number.isNaN(ms)) {
continue
}
map.set(ms, row)
}
return map
}
function diffRows(mapA, mapB, tolerance) {
let onlyA = []
let onlyB = []
let commonCount = 0
let fieldStats = new Map() // field -> {count, sumDiff, maxDiff, mismatchCount}
let examples = []
for (let ts of mapA.keys()) {
if (!mapB.has(ts)) {
onlyA.push(ts)
}
}
for (let ts of mapB.keys()) {
if (!mapA.has(ts)) {
onlyB.push(ts)
}
}
for (let [ts, rowA] of mapA) {
let rowB = mapB.get(ts)
if (!rowB) {
continue
}
commonCount++
let fields = new Set([...Object.keys(rowA), ...Object.keys(rowB)])
fields.delete('datetime')
for (let field of fields) {
let a = (field in rowA) ? rowA[field] : null
let b = (field in rowB) ? rowB[field] : null
if (a === null && b === null) {
continue
}
let stat = fieldStats.get(field)
if (!stat) {
stat = { count: 0, sumDiff: 0, maxDiff: 0, mismatchCount: 0 }
fieldStats.set(field, stat)
}
stat.count++
if (typeof a !== 'number' || typeof b !== 'number') {
stat.mismatchCount++
if (examples.length < MAX_EXAMPLES) {
examples.push({ ts, field, a, b, reason: 'null-mismatch' })
}
continue
}
let diff = Math.abs(a - b)
stat.sumDiff += diff
if (diff > stat.maxDiff) {
stat.maxDiff = diff
}
if (diff > tolerance) {
stat.mismatchCount++
if (examples.length < MAX_EXAMPLES) {
examples.push({ ts, field, a, b, diff })
}
}
}
}
return { onlyA, onlyB, commonCount, fieldStats, examples }
}
function fmtTs(ms) {
return DateTime.fromMillis(ms, { zone: 'utc' }).toISO()
}
function printSummary(opts, sideA, sideB, diff) {
console.log('=== Vergleich Laermwerte ===')
console.log(`Sensor: ${opts.sensorid} data: ${opts.data} span: ${opts.span}${opts.datetime ? ` datetime: ${opts.datetime}` : ''}`)
console.log(`A: ${sideA.count} Werte`)
console.log(`B: ${sideB.count} Werte`)
console.log(`Gemeinsame Zeitstempel: ${diff.commonCount}`)
console.log(`Nur in A: ${diff.onlyA.length}${diff.onlyA.length ? ` (z.B. ${diff.onlyA.slice(0, 3).map(fmtTs).join(', ')})` : ''}`)
console.log(`Nur in B: ${diff.onlyB.length}${diff.onlyB.length ? ` (z.B. ${diff.onlyB.slice(0, 3).map(fmtTs).join(', ')})` : ''}`)
console.log('')
console.log('Felder (bei gemeinsamen Zeitstempeln):')
let totalMismatches = 0
for (let [field, stat] of diff.fieldStats) {
let avg = stat.count ? stat.sumDiff / stat.count : 0
totalMismatches += stat.mismatchCount
console.log(` ${field.padEnd(14)} verglichen: ${String(stat.count).padStart(5)} max diff: ${stat.maxDiff.toFixed(4).padStart(10)} avg diff: ${avg.toFixed(4).padStart(10)} mismatches (>${opts.tolerance}): ${stat.mismatchCount}`)
}
if (diff.examples.length > 0) {
console.log('')
console.log(`Beispiel-Mismatches (max ${MAX_EXAMPLES}):`)
for (let ex of diff.examples) {
if (ex.reason === 'null-mismatch') {
console.log(` ${fmtTs(ex.ts)} ${ex.field}: A=${ex.a} B=${ex.b}`)
} else {
console.log(` ${fmtTs(ex.ts)} ${ex.field}: A=${ex.a} B=${ex.b} diff=${ex.diff.toFixed(4)}`)
}
}
}
console.log('')
if (totalMismatches > 0) {
console.log(`FEHLGESCHLAGEN: ${totalMismatches} Feld-Mismatches ausserhalb der Toleranz gefunden.`)
} else {
console.log('OK: Keine Mismatches ausserhalb der Toleranz bei gemeinsamen Zeitstempeln.')
}
return totalMismatches
}
async function main() {
let opts = parseArgs(process.argv)
let urlA = buildURL(opts.urlA, opts)
let urlB = buildURL(opts.urlB, opts)
console.log(`A: ${urlA}`)
console.log(`B: ${urlB}`)
console.log('')
let [sideA, sideB] = await Promise.all([
fetchSide('A', urlA),
fetchSide('B', urlB),
])
if (!sideA.ok || !sideB.ok) {
if (!sideA.ok) console.error(sideA.err)
if (!sideB.ok) console.error(sideB.err)
process.exitCode = 2
return
}
let mapA = toMap(sideA.values)
let mapB = toMap(sideB.values)
let diff = diffRows(mapA, mapB, opts.tolerance)
let mismatches = printSummary(
opts,
{ count: sideA.values.length },
{ count: sideB.values.length },
diff
)
process.exitCode = mismatches > 0 ? 1 : 0
}
main().catch((e) => {
console.error(e)
process.exitCode = 2
})
+366
View File
@@ -0,0 +1,366 @@
{
"name": "compare-laermwerte",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "compare-laermwerte",
"version": "1.0.0",
"dependencies": {
"axios": "^1.12.0",
"luxon": "^3.3.0",
"posix-getopt": "^1.2.1"
}
},
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz",
"integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.16.0",
"form-data": "^4.0.6",
"https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/follow-redirects": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.4",
"mime-types": "^2.1.35"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/luxon": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
"integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/posix-getopt": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/posix-getopt/-/posix-getopt-1.2.1.tgz",
"integrity": "sha512-BbGTiH8MOWAuc6h5yITkSn9k3HP4+QOCV9t6I5F62OrH7zqTHRo08QNsgELRreTBxcvRhbSpMoUnAx77Dz4yUA==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/proxy-from-env": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
}
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"name": "compare-laermwerte",
"version": "1.0.0",
"description": "Vergleicht Laermwerte zweier API-Instanzen (z.B. lokaler Victoria-Test-Stack vs. Produktion)",
"type": "module",
"main": "compare.js",
"scripts": {
"start": "node compare.js"
},
"dependencies": {
"axios": "^1.12.0",
"luxon": "^3.3.0",
"posix-getopt": "^1.2.1"
}
}