osmassist 0.0.0-stage → 1.0.0-alpha.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andreas Neumann
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,146 @@
1
- # Temporary Holding Version
1
+ # osmAssist
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Suggests [OpenStreetMap](https://www.openstreetmap.org) tag changes from a
4
+ feature's own website with the help of an AI model: opening hours, phone and
5
+ fax numbers, e-mail addresses, a new website address and linked profiles on
6
+ social networks, plus signals that the business has closed, moved, or that
7
+ the domain is parked or belongs to someone else.
8
+
9
+ The library only makes suggestions. Every suggestion comes with a verbatim
10
+ quote and the page it is from, and must be checked and approved by a human
11
+ before anything is changed in OpenStreetMap. It is used by
12
+ [osmCorrection](https://github.com/cyper85/osmCorrection).
13
+
14
+ ## Principles
15
+
16
+ - **One feature at a time, on request.** No bulk edits.
17
+ - **The user's own API key.** The caller pays for the tokens.
18
+ - **Only the feature's website** (the domain from the OSM tag) is fetched.
19
+ Closed or paid networks (Facebook, Instagram, Google …) are never fetched;
20
+ links to profiles are taken from the website only.
21
+ - **No knowledge of the model.** A suggestion is rejected unless its quote is
22
+ found on a fetched page of the site; links and e-mail addresses must
23
+ appear on the page as well.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install osmassist
29
+ ```
30
+
31
+ ES module with TypeScript types, for Node.js ≥ 22 and browsers (via a
32
+ bundler). The Claude provider is a separate entry point
33
+ (`osmassist/claude`), so a web app loads the Anthropic SDK only where it is
34
+ used. The JSON Schema of the answer files is part of the package
35
+ (`osmassist/schema/answer-file.schema.json`).
36
+
37
+ ## Usage
38
+
39
+ ```ts
40
+ import { aiAssistedTag } from 'osmassist';
41
+ // separate entry point, so a web app can load the SDK only when it is needed
42
+ import { createClaudeProvider } from 'osmassist/claude';
43
+
44
+ const provider = createClaudeProvider({ apiKey: userKey }); // Claude Sonnet 5.5 by default
45
+
46
+ const result = await provider.analyze(
47
+ {
48
+ element: { type: 'node', id: 123, tags: { shop: 'bakery', website: 'https://example.org/' } },
49
+ url: 'https://example.org/',
50
+ today: '2026-11-01',
51
+ country: 'DE',
52
+ },
53
+ {
54
+ // format check of the caller, e.g. with osmValidation; returns the preferred form
55
+ validate: (key, value) => ({ valid: true, value }),
56
+ },
57
+ );
58
+
59
+ result.proposals; // [{ key: 'opening_hours', value: 'Mo-Fr 06:00-18:00', oldValue?, quote, sourceUrl }]
60
+ result.signals; // [{ kind: 'closed' | 'moved' | 'parked' | 'new_owner', quote, sourceUrl }]
61
+ result.rejected; // suggestions that failed a check, with reason
62
+ result.pages; // fetched pages, with an error code where fetching failed
63
+ result.usage; // tokens and estimated cost in USD
64
+ aiAssistedTag([result.model]); // 'Anthropic Claude Sonnet 5.5', for the changeset
65
+ ```
66
+
67
+ `analyze` is `read` and `assess` in one step. `read` costs tokens and does
68
+ not depend on the current tags: the model reports everything the site says.
69
+ `assess(reading, request, validate)` compares it with the tags of one
70
+ element and costs nothing, so a reading can be reused for other elements
71
+ with the same website (and the same address, if the site lists several
72
+ locations):
73
+
74
+ ```ts
75
+ import { assess } from 'osmassist';
76
+
77
+ const reading = await provider.read(request);
78
+ assess(reading, request, validate);
79
+ assess(reading, { ...request, element: otherElement }, validate);
80
+ ```
81
+
82
+ Keys follow the form the feature already uses (`phone` or `contact:phone`);
83
+ new contact tags get `contact:*`.
84
+
85
+ Errors are thrown as `AssistError` with a `code` (`auth`, `rate_limit`,
86
+ `refused`, `incomplete`, `invalid_response`, `api`, `aborted`). Messages are
87
+ in English; callers translate them by code.
88
+
89
+ ## Analysis by an AI agent (e.g. Claude Code)
90
+
91
+ To use an AI plan instead of API credits, the analysis can run in an AI
92
+ agent on the user's computer, e.g. Claude Code with a Claude plan, Kiro or
93
+ Codex CLI: `jobFile` writes the instructions with the element IDs
94
+ (`auftrag.md`), the agent saves the pages (headless browser or `curl`) and
95
+ writes one answer file per job, and the app reads them back:
96
+
97
+ ```ts
98
+ import { assess, parseAnswerFile, readingFromAnswer } from 'osmassist';
99
+
100
+ const answer = parseAnswerFile(json); // antworten/a1.json
101
+ answer.ai; // { vendor, model, tool }, as the agent states it
102
+ const reading = readingFromAnswer(answer, pages, vendor); // pages: path → saved HTML; vendor: confirmed by the user
103
+ assess(reading, { element, url: answer.url, today }, validate);
104
+ ```
105
+
106
+ The format of the answer files is described in
107
+ [docs/answer-file.md](docs/answer-file.md), with the JSON Schema in
108
+ [schema/answer-file.schema.json](schema/answer-file.schema.json) (also
109
+ exported as `ANSWER_FILE_SCHEMA` and as the package file
110
+ `osmassist/schema/answer-file.schema.json`).
111
+
112
+ `htmlToText` turns the saved HTML into text without a DOM; links keep their
113
+ address, so profile links and e-mail addresses can be checked. `decodeHtml`
114
+ reads saved files in UTF-8, UTF-16 or the charset the page declares.
115
+
116
+ ## Claude
117
+
118
+ - Web fetch tool (`web_fetch_20250910`) restricted to the site's domain, at
119
+ most 4 pages of at most 20,000 tokens each. Anthropic fetches the pages, so
120
+ the library also works in the browser without a proxy.
121
+ - Structured output with a fixed JSON schema (`ANSWER_SCHEMA`).
122
+ - Server-side fallback on refusals (`fallbacks: "default"`); the model that
123
+ actually answered is reported in `result.model`.
124
+ - Calls from the browser are enabled (`dangerouslyAllowBrowser`): the key is
125
+ the user's own and only goes to `api.anthropic.com`. Recommend a key with a
126
+ short expiry that is used for nothing else.
127
+
128
+ ## Development
129
+
130
+ ```bash
131
+ npm install
132
+ npm test # vitest, with recorded API responses (no real API calls)
133
+ npm run build # dist/
134
+ npm run schema # schema/answer-file.schema.json from the code
135
+ ```
136
+
137
+ Releases: a GitHub release with a tag like `v1.0.0-alpha.1` (matching `version` in
138
+ `package.json`) publishes the package to npm (`.github/workflows/publish.yaml`,
139
+ npm trusted publishing, no token). Versions with a suffix such as
140
+ `1.0.0-alpha.1` get the npm tag `next`, others `latest`; as long as `latest`
141
+ is only npm's placeholder from reserving the name (`0.0.0-stage`), a
142
+ pre-release becomes `latest` as well.
143
+
144
+ ## License
145
+
146
+ MIT
@@ -0,0 +1,185 @@
1
+ import { type RawAnswer, type SiteReading } from './types.js';
2
+ export declare const ANSWER_FORMAT = "osmcorrection-assist-answer";
3
+ /** Where the schema file is published (schema/answer-file.schema.json in this repository). */
4
+ export declare const ANSWER_FILE_SCHEMA_ID = "https://raw.githubusercontent.com/cyper85/osmAssist/master/schema/answer-file.schema.json";
5
+ export interface ElementId {
6
+ type: 'node' | 'way' | 'relation';
7
+ id: number;
8
+ }
9
+ export interface AnswerPage {
10
+ url: string;
11
+ /** the saved HTML, relative to the working folder */
12
+ file?: string;
13
+ error?: string;
14
+ }
15
+ /** The AI that wrote the answer, as it states itself. */
16
+ export interface AnswerAi {
17
+ /** maker of the model, e.g. "Anthropic", "OpenAI", "Google" (not the maker of the tool) */
18
+ vendor: string;
19
+ /** e.g. "claude-opus-5-5" */
20
+ model: string;
21
+ /** the agent it ran in, e.g. "Claude Code", "Kiro", "Codex CLI" */
22
+ tool: string;
23
+ }
24
+ export interface AnswerFile extends RawAnswer {
25
+ format: typeof ANSWER_FORMAT;
26
+ version: 1;
27
+ /** required by the schema; older answers (first run, 2026-11-02) do not have it */
28
+ ai?: AnswerAi;
29
+ job: string;
30
+ elements: ElementId[];
31
+ url: string;
32
+ fetched_with?: string;
33
+ pages: AnswerPage[];
34
+ }
35
+ /**
36
+ * JSON Schema of an answer file, given to the model in the job file. The
37
+ * entries of `proposals` and `signals` are the ones of the API (ANSWER_SCHEMA).
38
+ */
39
+ export declare const ANSWER_FILE_SCHEMA: {
40
+ readonly $schema: 'https://json-schema.org/draft/2020-12/schema';
41
+ readonly $id: "https://raw.githubusercontent.com/cyper85/osmAssist/master/schema/answer-file.schema.json";
42
+ readonly title: 'osmAssist answer file';
43
+ readonly description: 'What an AI agent found on the website of one job (one website, one or more OpenStreetMap elements). See docs/answer-file.md.';
44
+ readonly type: 'object';
45
+ readonly additionalProperties: false;
46
+ readonly required: readonly ['format', 'version', 'ai', 'job', 'elements', 'url', 'fetched_with', 'pages', 'proposals', 'signals'];
47
+ readonly properties: {
48
+ readonly format: {
49
+ readonly const: "osmcorrection-assist-answer";
50
+ };
51
+ readonly version: {
52
+ readonly const: 1;
53
+ };
54
+ readonly ai: {
55
+ readonly type: 'object';
56
+ readonly description: 'The AI that wrote this answer. Used for the changeset tag ai_assisted, after the user confirmed it.';
57
+ readonly additionalProperties: false;
58
+ readonly required: readonly ['vendor', 'model', 'tool'];
59
+ readonly properties: {
60
+ readonly vendor: {
61
+ readonly type: 'string';
62
+ readonly minLength: 1;
63
+ readonly description: 'Maker of the model (not of the tool), e.g. "Anthropic", "OpenAI", "Google", "Amazon".';
64
+ };
65
+ readonly model: {
66
+ readonly type: 'string';
67
+ readonly minLength: 1;
68
+ readonly description: 'The model, e.g. "claude-opus-5-5"; "unknown" if not known.';
69
+ };
70
+ readonly tool: {
71
+ readonly type: 'string';
72
+ readonly minLength: 1;
73
+ readonly description: 'The agent it ran in, e.g. "Claude Code", "Kiro", "Codex CLI".';
74
+ };
75
+ };
76
+ };
77
+ readonly job: {
78
+ readonly type: 'string';
79
+ readonly description: 'ID of the job, e.g. "a1".';
80
+ };
81
+ readonly elements: {
82
+ readonly type: 'array';
83
+ readonly minItems: 1;
84
+ readonly items: {
85
+ readonly type: 'string';
86
+ readonly pattern: '^(node|way|relation)/[0-9]+$';
87
+ };
88
+ readonly description: 'The elements of the job, as in the job list.';
89
+ };
90
+ readonly url: {
91
+ readonly type: 'string';
92
+ readonly description: 'The website from the OpenStreetMap data.';
93
+ };
94
+ readonly fetched_with: {
95
+ readonly enum: readonly ['browser', 'curl'];
96
+ readonly description: 'How the pages were saved.';
97
+ };
98
+ readonly pages: {
99
+ readonly type: 'array';
100
+ readonly description: 'Every page fetched, with "file" (saved) or "error" (could not be fetched).';
101
+ readonly items: {
102
+ readonly type: 'object';
103
+ readonly additionalProperties: false;
104
+ readonly required: readonly ['url'];
105
+ readonly properties: {
106
+ readonly url: {
107
+ readonly type: 'string';
108
+ };
109
+ readonly file: {
110
+ readonly type: 'string';
111
+ readonly pattern: '^seiten/[^/]+\\.html$';
112
+ };
113
+ readonly error: {
114
+ readonly type: 'string';
115
+ };
116
+ };
117
+ readonly oneOf: readonly [{
118
+ readonly required: readonly ['file'];
119
+ }, {
120
+ readonly required: readonly ['error'];
121
+ }];
122
+ };
123
+ };
124
+ readonly proposals: {
125
+ readonly type: 'array';
126
+ readonly items: {
127
+ readonly type: 'object';
128
+ readonly additionalProperties: false;
129
+ readonly required: readonly ['field', 'network', 'value', 'quote', 'source_url'];
130
+ readonly properties: {
131
+ readonly field: {
132
+ readonly type: 'string';
133
+ readonly enum: readonly ["opening_hours", "phone", "fax", "email", "website", "social"];
134
+ };
135
+ readonly network: {
136
+ readonly type: 'string';
137
+ readonly enum: readonly ["none", "facebook", "twitter", "instagram", "tiktok", "youtube", "linkedin", "xing", "pinterest", "vimeo", "flickr", "vk", "mastodon", "bluesky", "mobilizon", "diaspora", "gnusocial", "telegram", "whatsapp", "matrix", "tripadvisor"];
138
+ };
139
+ readonly value: {
140
+ readonly type: 'string';
141
+ };
142
+ readonly quote: {
143
+ readonly type: 'string';
144
+ };
145
+ readonly source_url: {
146
+ readonly type: 'string';
147
+ };
148
+ };
149
+ };
150
+ };
151
+ readonly signals: {
152
+ readonly type: 'array';
153
+ readonly items: {
154
+ readonly type: 'object';
155
+ readonly additionalProperties: false;
156
+ readonly required: readonly ['kind', 'quote', 'source_url'];
157
+ readonly properties: {
158
+ readonly kind: {
159
+ readonly type: 'string';
160
+ readonly enum: readonly ["closed", "moved", "parked", "new_owner"];
161
+ };
162
+ readonly quote: {
163
+ readonly type: 'string';
164
+ };
165
+ readonly source_url: {
166
+ readonly type: 'string';
167
+ };
168
+ };
169
+ };
170
+ };
171
+ };
172
+ };
173
+ /** "node/42" → { type: 'node', id: 42 } */
174
+ export declare function parseElementId(text: string): ElementId | undefined;
175
+ /** Reads an answer file; throws with a reason if it does not have the format. */
176
+ export declare function parseAnswerFile(json: string): AnswerFile;
177
+ /** Normalizes a path inside the working folder ("./seiten\\a1.html" → "seiten/a1.html"). */
178
+ export declare function folderPath(path: string): string;
179
+ /**
180
+ * The reading of one answer file, with the text of its saved pages. A page
181
+ * whose file is missing counts as not fetched, so its quotes are rejected.
182
+ * `vendor` is the maker of the model as confirmed by the user; without it,
183
+ * the one the answer states.
184
+ */
185
+ export declare function readingFromAnswer(answer: AnswerFile, files: Map<string, string>, vendor?: string): SiteReading;
@@ -0,0 +1,215 @@
1
+ import { isRawAnswer } from './check.js';
2
+ import { htmlToText } from './html.js';
3
+ import { ANSWER_SCHEMA } from './prompt.js';
4
+ import { FIELDS, NETWORKS, SIGNAL_KINDS } from './types.js';
5
+ /*
6
+ * Analysis outside the app by an AI agent on the user's computer, e.g. Claude
7
+ * Code with the user's Claude plan (docs/answer-file.md): the app exports a
8
+ * job file with the element IDs, the agent saves the pages and writes one
9
+ * answer file per job, and the app imports them. The checks are the same as
10
+ * for the API.
11
+ */
12
+ export const ANSWER_FORMAT = 'osmcorrection-assist-answer';
13
+ /** Where the schema file is published (schema/answer-file.schema.json in this repository). */
14
+ export const ANSWER_FILE_SCHEMA_ID = 'https://raw.githubusercontent.com/cyper85/osmAssist/master/schema/answer-file.schema.json';
15
+ /**
16
+ * JSON Schema of an answer file, given to the model in the job file. The
17
+ * entries of `proposals` and `signals` are the ones of the API (ANSWER_SCHEMA).
18
+ */
19
+ export const ANSWER_FILE_SCHEMA = {
20
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
21
+ $id: ANSWER_FILE_SCHEMA_ID,
22
+ title: 'osmAssist answer file',
23
+ description: 'What an AI agent found on the website of one job (one website, one or more OpenStreetMap elements). See docs/answer-file.md.',
24
+ type: 'object',
25
+ additionalProperties: false,
26
+ required: ['format', 'version', 'ai', 'job', 'elements', 'url', 'fetched_with', 'pages', 'proposals', 'signals'],
27
+ properties: {
28
+ format: { const: ANSWER_FORMAT },
29
+ version: { const: 1 },
30
+ ai: {
31
+ type: 'object',
32
+ description: 'The AI that wrote this answer. Used for the changeset tag ai_assisted, after the user confirmed it.',
33
+ additionalProperties: false,
34
+ required: ['vendor', 'model', 'tool'],
35
+ properties: {
36
+ vendor: {
37
+ type: 'string',
38
+ minLength: 1,
39
+ description: 'Maker of the model (not of the tool), e.g. "Anthropic", "OpenAI", "Google", "Amazon".',
40
+ },
41
+ model: { type: 'string', minLength: 1, description: 'The model, e.g. "claude-opus-5-5"; "unknown" if not known.' },
42
+ tool: { type: 'string', minLength: 1, description: 'The agent it ran in, e.g. "Claude Code", "Kiro", "Codex CLI".' },
43
+ },
44
+ },
45
+ job: { type: 'string', description: 'ID of the job, e.g. "a1".' },
46
+ elements: {
47
+ type: 'array',
48
+ minItems: 1,
49
+ items: { type: 'string', pattern: '^(node|way|relation)/[0-9]+$' },
50
+ description: 'The elements of the job, as in the job list.',
51
+ },
52
+ url: { type: 'string', description: 'The website from the OpenStreetMap data.' },
53
+ fetched_with: { enum: ['browser', 'curl'], description: 'How the pages were saved.' },
54
+ pages: {
55
+ type: 'array',
56
+ description: 'Every page fetched, with "file" (saved) or "error" (could not be fetched).',
57
+ items: {
58
+ type: 'object',
59
+ additionalProperties: false,
60
+ required: ['url'],
61
+ properties: {
62
+ url: { type: 'string' },
63
+ file: { type: 'string', pattern: '^seiten/[^/]+\\.html$' },
64
+ error: { type: 'string' },
65
+ },
66
+ oneOf: [{ required: ['file'] }, { required: ['error'] }],
67
+ },
68
+ },
69
+ proposals: ANSWER_SCHEMA.properties.proposals,
70
+ signals: ANSWER_SCHEMA.properties.signals,
71
+ },
72
+ };
73
+ const isObject = (value) => typeof value === 'object' && value !== null;
74
+ /** "node/42" → { type: 'node', id: 42 } */
75
+ export function parseElementId(text) {
76
+ const match = /^(node|way|relation)\/(\d+)$/.exec(text.trim());
77
+ return match === null ? undefined : { type: match[1], id: Number(match[2]) };
78
+ }
79
+ /** Reads an answer file; throws with a reason if it does not have the format. */
80
+ export function parseAnswerFile(json) {
81
+ let data;
82
+ try {
83
+ data = JSON.parse(json);
84
+ }
85
+ catch {
86
+ throw new Error('not valid JSON');
87
+ }
88
+ if (!isObject(data) || data.format !== ANSWER_FORMAT || data.version !== 1) {
89
+ throw new Error(`not an answer file (format "${ANSWER_FORMAT}", version 1)`);
90
+ }
91
+ if (typeof data.job !== 'string' || typeof data.url !== 'string') {
92
+ throw new Error('"job" or "url" missing');
93
+ }
94
+ const elements = Array.isArray(data.elements)
95
+ ? data.elements.map((e) => (typeof e === 'string' ? parseElementId(e) : undefined))
96
+ : [];
97
+ if (elements.length === 0 || elements.some((e) => e === undefined)) {
98
+ throw new Error('"elements" must list IDs like "node/42"');
99
+ }
100
+ const pages = Array.isArray(data.pages) ? data.pages : [];
101
+ const pageOk = (p) => isObject(p) &&
102
+ typeof p.url === 'string' &&
103
+ (p.file === undefined || typeof p.file === 'string') &&
104
+ (p.error === undefined || typeof p.error === 'string');
105
+ if (!pages.every(pageOk)) {
106
+ throw new Error('"pages" must have "url" and "file" or "error"');
107
+ }
108
+ // models sometimes name the kind of a signal "signal" or "type" (first run, 2026-11-02)
109
+ const signals = Array.isArray(data.signals)
110
+ ? data.signals.map((s) => (isObject(s) && s.kind === undefined ? { ...s, kind: s.signal ?? s.type } : s))
111
+ : data.signals;
112
+ const answer = {
113
+ proposals: data.proposals,
114
+ signals: Array.isArray(signals)
115
+ ? signals.map((s) => (isObject(s) ? { kind: s.kind, quote: s.quote, source_url: s.source_url } : s))
116
+ : signals,
117
+ };
118
+ if (!isRawAnswer(answer)) {
119
+ throw new Error(schemaProblem(answer.proposals, answer.signals));
120
+ }
121
+ const ai = isObject(data.ai) && typeof data.ai.vendor === 'string' && data.ai.vendor.trim() !== '' ? data.ai : undefined;
122
+ return {
123
+ format: ANSWER_FORMAT,
124
+ version: 1,
125
+ ...(ai === undefined
126
+ ? {}
127
+ : {
128
+ ai: {
129
+ vendor: String(ai.vendor).trim(),
130
+ model: typeof ai.model === 'string' ? ai.model : 'unknown',
131
+ tool: typeof ai.tool === 'string' ? ai.tool : 'unknown',
132
+ },
133
+ }),
134
+ job: data.job,
135
+ elements: elements,
136
+ url: data.url,
137
+ ...(typeof data.fetched_with === 'string' ? { fetched_with: data.fetched_with } : {}),
138
+ pages: pages,
139
+ ...answer,
140
+ };
141
+ }
142
+ /** Names the first entry that does not match the schema, e.g. 'signals[0]: "kind" …'. */
143
+ function schemaProblem(proposals, signals) {
144
+ const fields = (entry, rules) => rules.find(([name, ok]) => !ok(entry[name]));
145
+ const text = (v) => typeof v === 'string';
146
+ const listCheck = (name, list, rules) => {
147
+ if (!Array.isArray(list)) {
148
+ return `"${name}" must be a list`;
149
+ }
150
+ for (const [i, entry] of list.entries()) {
151
+ if (!isObject(entry)) {
152
+ return `${name}[${i}] is not an object`;
153
+ }
154
+ const wrong = fields(entry, rules);
155
+ if (wrong !== undefined) {
156
+ return `${name}[${i}]: "${wrong[0]}" ${wrong[2]}`;
157
+ }
158
+ }
159
+ return undefined;
160
+ };
161
+ const oneOf = (list) => (v) => typeof v === 'string' && list.includes(v);
162
+ return (listCheck('proposals', proposals, [
163
+ ['field', oneOf(FIELDS), `must be one of ${FIELDS.join(', ')}`],
164
+ ['network', oneOf(['none', ...NETWORKS]), 'must be "none" or a network'],
165
+ ['value', text, 'must be a text'],
166
+ ['quote', text, 'must be a text'],
167
+ ['source_url', text, 'must be a text'],
168
+ ]) ??
169
+ listCheck('signals', signals, [
170
+ ['kind', oneOf(SIGNAL_KINDS), `must be one of ${SIGNAL_KINDS.join(', ')}`],
171
+ ['quote', text, 'must be a text'],
172
+ ['source_url', text, 'must be a text'],
173
+ ]) ??
174
+ '"proposals" or "signals" do not match the schema');
175
+ }
176
+ /** Normalizes a path inside the working folder ("./seiten\\a1.html" → "seiten/a1.html"). */
177
+ export function folderPath(path) {
178
+ return path.replace(/\\/g, '/').replace(/^(\.\/)+/, '');
179
+ }
180
+ /**
181
+ * The reading of one answer file, with the text of its saved pages. A page
182
+ * whose file is missing counts as not fetched, so its quotes are rejected.
183
+ * `vendor` is the maker of the model as confirmed by the user; without it,
184
+ * the one the answer states.
185
+ */
186
+ export function readingFromAnswer(answer, files, vendor) {
187
+ const texts = {};
188
+ const pages = answer.pages.map((page) => {
189
+ if (page.file === undefined) {
190
+ return { url: page.url, error: page.error ?? 'not fetched' };
191
+ }
192
+ const html = files.get(folderPath(page.file));
193
+ if (html === undefined) {
194
+ return { url: page.url, error: `file missing: ${page.file}` };
195
+ }
196
+ if (html.trim() === '') {
197
+ return { url: page.url, error: `file empty: ${page.file}` };
198
+ }
199
+ texts[page.url] = htmlToText(html, page.url);
200
+ return { url: page.url };
201
+ });
202
+ return {
203
+ url: answer.url,
204
+ answer: { proposals: answer.proposals, signals: answer.signals },
205
+ texts,
206
+ pages,
207
+ model: {
208
+ vendor: vendor ?? answer.ai?.vendor ?? 'unknown',
209
+ name: answer.ai === undefined ? 'unknown' : `${answer.ai.model} (${answer.ai.tool})`,
210
+ id: answer.ai?.model ?? 'unknown',
211
+ },
212
+ usage: { inputTokens: 0, outputTokens: 0 },
213
+ };
214
+ }
215
+ //# sourceMappingURL=answerFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"answerFile.js","sourceRoot":"","sources":["../src/answerFile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { type AssistRequest, type AssistResult, type Proposal, type RawAnswer, type Rejected, type Signal, type SiteReading, type Compare, type Validate } from './types.js';
2
+ /** Checks the structure of the answer; the schema should already guarantee it. */
3
+ export declare function isRawAnswer(value: unknown): value is RawAnswer;
4
+ /**
5
+ * The key for a suggestion in the form the element already uses: an existing
6
+ * `contact:<name>`, else an existing `<name>`, else `contact:<name>`. The
7
+ * website stays `website` if it is new, opening hours are always
8
+ * `opening_hours`.
9
+ */
10
+ export declare function targetKey(base: string, tags: Record<string, string>): string;
11
+ /**
12
+ * Turns the answer into suggestions. A suggestion is rejected if its quote is
13
+ * not on a fetched page of the site, if a link or address is not on the page,
14
+ * if the value is invalid, or if it is the current value.
15
+ */
16
+ export declare function checkAnswer(answer: RawAnswer, pages: Map<string, string>, request: AssistRequest, validate?: Validate, compare?: Compare): {
17
+ proposals: Proposal[];
18
+ signals: Signal[];
19
+ rejected: Rejected[];
20
+ };
21
+ /** Compares what was read on the website with the tags of one element. Costs nothing. */
22
+ export declare function assess(reading: SiteReading, request: AssistRequest, validate?: Validate, compare?: Compare): AssistResult;