@oga-mcp/server 1.0.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.
Files changed (56) hide show
  1. package/README.md +137 -0
  2. package/dist/api-client.d.ts +255 -0
  3. package/dist/api-client.js +260 -0
  4. package/dist/api-client.js.map +1 -0
  5. package/dist/methodology-data.d.ts +40 -0
  6. package/dist/methodology-data.js +179 -0
  7. package/dist/methodology-data.js.map +1 -0
  8. package/dist/server.d.ts +28 -0
  9. package/dist/server.js +173 -0
  10. package/dist/server.js.map +1 -0
  11. package/dist/tools/area-brief-audiences.d.ts +40 -0
  12. package/dist/tools/area-brief-audiences.js +131 -0
  13. package/dist/tools/area-brief-audiences.js.map +1 -0
  14. package/dist/tools/area-brief-format.d.ts +15 -0
  15. package/dist/tools/area-brief-format.js +142 -0
  16. package/dist/tools/area-brief-format.js.map +1 -0
  17. package/dist/tools/area-brief.d.ts +48 -0
  18. package/dist/tools/area-brief.js +82 -0
  19. package/dist/tools/area-brief.js.map +1 -0
  20. package/dist/tools/compare-postcodes.d.ts +57 -0
  21. package/dist/tools/compare-postcodes.js +148 -0
  22. package/dist/tools/compare-postcodes.js.map +1 -0
  23. package/dist/tools/engine-version.d.ts +23 -0
  24. package/dist/tools/engine-version.js +35 -0
  25. package/dist/tools/engine-version.js.map +1 -0
  26. package/dist/tools/find-areas.d.ts +38 -0
  27. package/dist/tools/find-areas.js +58 -0
  28. package/dist/tools/find-areas.js.map +1 -0
  29. package/dist/tools/find-peers.d.ts +44 -0
  30. package/dist/tools/find-peers.js +77 -0
  31. package/dist/tools/find-peers.js.map +1 -0
  32. package/dist/tools/get-area-signals.d.ts +39 -0
  33. package/dist/tools/get-area-signals.js +60 -0
  34. package/dist/tools/get-area-signals.js.map +1 -0
  35. package/dist/tools/get-portfolio-changes.d.ts +58 -0
  36. package/dist/tools/get-portfolio-changes.js +121 -0
  37. package/dist/tools/get-portfolio-changes.js.map +1 -0
  38. package/dist/tools/get-signals-by-category.d.ts +44 -0
  39. package/dist/tools/get-signals-by-category.js +67 -0
  40. package/dist/tools/get-signals-by-category.js.map +1 -0
  41. package/dist/tools/intelligence-format.d.ts +28 -0
  42. package/dist/tools/intelligence-format.js +197 -0
  43. package/dist/tools/intelligence-format.js.map +1 -0
  44. package/dist/tools/methodology-for.d.ts +34 -0
  45. package/dist/tools/methodology-for.js +71 -0
  46. package/dist/tools/methodology-for.js.map +1 -0
  47. package/dist/tools/score-postcode.d.ts +48 -0
  48. package/dist/tools/score-postcode.js +108 -0
  49. package/dist/tools/score-postcode.js.map +1 -0
  50. package/dist/tools/signals-format.d.ts +11 -0
  51. package/dist/tools/signals-format.js +116 -0
  52. package/dist/tools/signals-format.js.map +1 -0
  53. package/dist/tools/watch-portfolio.d.ts +50 -0
  54. package/dist/tools/watch-portfolio.js +126 -0
  55. package/dist/tools/watch-portfolio.js.map +1 -0
  56. package/package.json +48 -0
@@ -0,0 +1,197 @@
1
+ /**
2
+ * AR-367: Format /v1/query responses for the find_areas MCP tool.
3
+ *
4
+ * The query plane returns a discriminated union over 7 plan ops. This
5
+ * module renders each op's results as markdown the LLM can pass to its
6
+ * user. Every value rendered is a real field from the response — no
7
+ * client-side text synthesis.
8
+ *
9
+ * Rendering bias: surface the emitted PLAN front-and-center (the
10
+ * "what did the planner decide to run" transparency play that makes
11
+ * the query plane defensible), then the op-specific results.
12
+ */
13
+ import { formatAreaProfileAsText } from "./signals-format.js";
14
+ function formatRankAreas(plan, results) {
15
+ const params = plan.params;
16
+ const sortLabel = params.sort_by
17
+ ? `${params.sort_by.signal} (${params.sort_by.mode ?? "percentile"} ${params.sort_by.direction ?? "desc"})`
18
+ : params.signal ?? "default";
19
+ const rows = (Array.isArray(results) ? results : []);
20
+ const lines = [];
21
+ lines.push(`**Ranking by:** ${sortLabel}`);
22
+ if (params.country)
23
+ lines.push(`**Scope:** ${params.country}`);
24
+ if (params.lad)
25
+ lines.push(`**LAD:** ${params.lad}`);
26
+ if (params.limit)
27
+ lines.push(`**Limit:** ${params.limit}`);
28
+ lines.push("");
29
+ if (rows.length === 0) {
30
+ lines.push("No areas matched.");
31
+ return lines.join("\n");
32
+ }
33
+ lines.push("| Rank | LSOA | Value | Percentile |");
34
+ lines.push("|---|---|---|---|");
35
+ let rank = 1;
36
+ for (const r of rows) {
37
+ const v = r.value === null || r.value === undefined ? "—" : String(r.value);
38
+ const p = r.percentile === null || r.percentile === undefined ? "—" : `${r.percentile}th`;
39
+ lines.push(`| ${rank} | ${r.geo_code} | ${v} | ${p} |`);
40
+ rank++;
41
+ }
42
+ return lines.join("\n");
43
+ }
44
+ function formatGetArea(results) {
45
+ /* results === null means the area didn't resolve. */
46
+ if (results === null)
47
+ return "Area could not be resolved.";
48
+ return formatAreaProfileAsText(results);
49
+ }
50
+ function formatScoreArea(results) {
51
+ if (results === null)
52
+ return "Area could not be resolved.";
53
+ const r = results;
54
+ const lines = [];
55
+ lines.push(`**${r.area}** · preset ${r.preset} · **${r.score}/100** · ${r.area_type} · confidence ${(r.confidence * 100).toFixed(0)}%`);
56
+ lines.push(`Engine version: ${r.engine_version}`);
57
+ lines.push("");
58
+ if (r.dimensions && r.dimensions.length > 0) {
59
+ lines.push("| Dimension | Score | Weight | Confidence |");
60
+ lines.push("|---|---|---|---|");
61
+ for (const d of r.dimensions) {
62
+ lines.push(`| ${d.label} | ${d.score}/100 | ${d.weight}% | ${(d.confidence * 100).toFixed(0)}% |`);
63
+ }
64
+ }
65
+ return lines.join("\n");
66
+ }
67
+ function formatCompareAreas(results) {
68
+ if (results === null)
69
+ return "Comparison could not be resolved.";
70
+ const r = results;
71
+ const lines = [];
72
+ lines.push(`**Scope:** ${r.meta.scope}`);
73
+ lines.push("");
74
+ for (const slot of r.areas) {
75
+ lines.push(`### ${slot.query}`);
76
+ if (slot.profile === null) {
77
+ lines.push("Could not resolve.");
78
+ }
79
+ else {
80
+ lines.push(formatAreaProfileAsText(slot.profile));
81
+ }
82
+ lines.push("");
83
+ }
84
+ return lines.join("\n").trimEnd();
85
+ }
86
+ function formatFindPeersFromQuery(results) {
87
+ if (results === null)
88
+ return "Peers could not be resolved.";
89
+ const r = results;
90
+ return renderPeersBlock(r);
91
+ }
92
+ function formatFindInsights(results) {
93
+ if (results === null)
94
+ return "Insights could not be computed.";
95
+ const r = results;
96
+ const lines = [];
97
+ lines.push(`**Signal:** ${r.signal_key}`);
98
+ lines.push(`**Scope:** ${r.meta.scope}`);
99
+ if (r.meta.threshold !== null)
100
+ lines.push(`**Threshold:** |z| >= ${r.meta.threshold}`);
101
+ lines.push("");
102
+ if (r.insights.length === 0) {
103
+ lines.push("No anomalies found.");
104
+ return lines.join("\n");
105
+ }
106
+ lines.push("| Rank | LSOA | peer-relative z | |z| |");
107
+ lines.push("|---|---|---|---|");
108
+ let rank = 1;
109
+ for (const ins of r.insights) {
110
+ lines.push(`| ${rank} | ${ins.geo_code} | ${ins.peer_relative_z.toFixed(2)} | ${ins.abs_z.toFixed(2)} |`);
111
+ rank++;
112
+ }
113
+ return lines.join("\n");
114
+ }
115
+ function formatFindForecast(results) {
116
+ if (results === null)
117
+ return "Forecast could not be computed.";
118
+ const r = results;
119
+ const lines = [];
120
+ lines.push(`**Target:** ${r.target.geo_code}`);
121
+ lines.push(`**Signal:** ${r.signal_key}`);
122
+ lines.push(`**Fit:** ${r.meta.n_observations} obs over ${r.meta.window_months}m · slope/month ${r.meta.slope_per_month.toFixed(3)}` +
123
+ (r.meta.r2 !== null ? ` · R² ${r.meta.r2.toFixed(3)}` : ""));
124
+ lines.push(`**Latest observed:** ${r.meta.latest_observed_period}`);
125
+ lines.push("");
126
+ if (r.points.length === 0) {
127
+ lines.push("No projection points returned.");
128
+ return lines.join("\n");
129
+ }
130
+ lines.push("| Period | Projected | Lower | Upper |");
131
+ lines.push("|---|---|---|---|");
132
+ for (const p of r.points) {
133
+ lines.push(`| ${p.observed_period} | ${p.projected_value.toFixed(2)} | ${p.lower_bound.toFixed(2)} | ${p.upper_bound.toFixed(2)} |`);
134
+ }
135
+ return lines.join("\n");
136
+ }
137
+ /* ── shared ───────────────────────────────────────────────────────── */
138
+ export function renderPeersBlock(r) {
139
+ const lines = [];
140
+ lines.push(`**Target LSOA:** ${r.target.geo_code}`);
141
+ lines.push(`**Scope:** ${r.meta.scope}`);
142
+ lines.push(`**Comparison signals (${r.target.signals_used.length}):** ${r.target.signals_used.join(", ")}`);
143
+ lines.push("");
144
+ if (r.peers.length === 0) {
145
+ lines.push("No peers found within the requested scope.");
146
+ return lines.join("\n");
147
+ }
148
+ lines.push("| Rank | LSOA | Distance | Signals used |");
149
+ lines.push("|---|---|---|---|");
150
+ let rank = 1;
151
+ for (const p of r.peers) {
152
+ lines.push(`| ${rank} | ${p.geo_code} | ${p.distance.toFixed(3)} | ${p.n_dims_used} |`);
153
+ rank++;
154
+ }
155
+ return lines.join("\n");
156
+ }
157
+ /* ── entry point ──────────────────────────────────────────────────── */
158
+ export function formatQueryResponseAsText(res) {
159
+ const op = res.plan.op;
160
+ const lines = [];
161
+ lines.push(`# Query result · op: ${op}`);
162
+ lines.push(`Plan source: ${res.plan_source} · Generated at: ${res.meta.generated_at}`);
163
+ lines.push("");
164
+ /* Show the emitted plan params verbatim — transparency about what
165
+ the planner decided to run. */
166
+ lines.push(`## Emitted plan`);
167
+ lines.push("```json");
168
+ lines.push(JSON.stringify(res.plan, null, 2));
169
+ lines.push("```");
170
+ lines.push("");
171
+ lines.push(`## Results`);
172
+ switch (op) {
173
+ case "rank_areas":
174
+ lines.push(formatRankAreas(res.plan, res.results));
175
+ break;
176
+ case "get_area":
177
+ lines.push(formatGetArea(res.results));
178
+ break;
179
+ case "score_area":
180
+ lines.push(formatScoreArea(res.results));
181
+ break;
182
+ case "compare_areas":
183
+ lines.push(formatCompareAreas(res.results));
184
+ break;
185
+ case "find_peers":
186
+ lines.push(formatFindPeersFromQuery(res.results));
187
+ break;
188
+ case "find_insights":
189
+ lines.push(formatFindInsights(res.results));
190
+ break;
191
+ case "find_forecast":
192
+ lines.push(formatFindForecast(res.results));
193
+ break;
194
+ }
195
+ return lines.join("\n").trimEnd();
196
+ }
197
+ //# sourceMappingURL=intelligence-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intelligence-format.js","sourceRoot":"","sources":["../../src/tools/intelligence-format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * MCP tool: methodology_for
3
+ *
4
+ * Returns the methodology explanation for a given scoring dimension. Pure
5
+ * static lookup — no network call. Source of truth in methodology-data.ts,
6
+ * which is updated when the engine methodology changes.
7
+ */
8
+ export declare const methodologyForToolName = "methodology_for";
9
+ export declare const methodologyForToolDef: {
10
+ readonly name: "methodology_for";
11
+ readonly description: string;
12
+ readonly inputSchema: {
13
+ readonly type: "object";
14
+ readonly properties: {
15
+ readonly dimension: {
16
+ readonly type: "string";
17
+ readonly description: "Dimension name. Case-insensitive, partial match supported (e.g. 'safety' matches 'Safety & Crime').";
18
+ };
19
+ };
20
+ readonly required: readonly ["dimension"];
21
+ readonly additionalProperties: false;
22
+ };
23
+ };
24
+ export interface MethodologyForArgs {
25
+ dimension: string;
26
+ }
27
+ export declare function parseMethodologyForArgs(raw: unknown): MethodologyForArgs;
28
+ export declare function executeMethodologyFor(args: MethodologyForArgs): {
29
+ content: Array<{
30
+ type: "text";
31
+ text: string;
32
+ }>;
33
+ isError?: boolean;
34
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * MCP tool: methodology_for
3
+ *
4
+ * Returns the methodology explanation for a given scoring dimension. Pure
5
+ * static lookup — no network call. Source of truth in methodology-data.ts,
6
+ * which is updated when the engine methodology changes.
7
+ */
8
+ import { findDimension, METHODOLOGY } from "../methodology-data.js";
9
+ export const methodologyForToolName = "methodology_for";
10
+ export const methodologyForToolDef = {
11
+ name: methodologyForToolName,
12
+ description: "Get the methodology explanation for a specific scoring dimension (e.g. 'Safety & Crime', 'Transport', 'Rental Yield'). " +
13
+ "Returns the data source, summary of how the score is computed, and the per-intent weight. " +
14
+ "Useful when a customer asks 'why did Safety score 80?' or for procurement teams reviewing methodology before integration. " +
15
+ `Recognised dimensions: ${METHODOLOGY.map((d) => d.dimension).join(", ")}.`,
16
+ inputSchema: {
17
+ type: "object",
18
+ properties: {
19
+ dimension: {
20
+ type: "string",
21
+ description: "Dimension name. Case-insensitive, partial match supported (e.g. 'safety' matches 'Safety & Crime').",
22
+ },
23
+ },
24
+ required: ["dimension"],
25
+ additionalProperties: false,
26
+ },
27
+ };
28
+ export function parseMethodologyForArgs(raw) {
29
+ if (typeof raw !== "object" || raw === null) {
30
+ throw new Error("methodology_for arguments must be an object");
31
+ }
32
+ const obj = raw;
33
+ const dimension = obj.dimension;
34
+ if (typeof dimension !== "string" || dimension.trim().length === 0) {
35
+ throw new Error("dimension must be a non-empty string");
36
+ }
37
+ return { dimension: dimension.trim() };
38
+ }
39
+ export function executeMethodologyFor(args) {
40
+ const match = findDimension(args.dimension);
41
+ if (!match) {
42
+ const available = METHODOLOGY.map((d) => d.dimension).join(", ");
43
+ return {
44
+ content: [
45
+ {
46
+ type: "text",
47
+ text: `No methodology found for "${args.dimension}". Available dimensions: ${available}`,
48
+ },
49
+ ],
50
+ isError: true,
51
+ };
52
+ }
53
+ const lines = [];
54
+ lines.push(`# ${match.dimension}`);
55
+ lines.push("");
56
+ lines.push(`**Used in intents:** ${match.intents.join(", ")}`);
57
+ lines.push(`**Data source:** ${match.source}`);
58
+ lines.push("");
59
+ lines.push(`## How it scores`);
60
+ lines.push(match.summary);
61
+ lines.push("");
62
+ lines.push(`## Weight per intent`);
63
+ for (const [intent, weight] of Object.entries(match.weights)) {
64
+ if (weight > 0)
65
+ lines.push(`- **${intent}**: ${weight}%`);
66
+ }
67
+ lines.push("");
68
+ lines.push(`Full methodology: https://www.onegoodarea.com/methodology`);
69
+ return { content: [{ type: "text", text: lines.join("\n") }] };
70
+ }
71
+ //# sourceMappingURL=methodology-for.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"methodology-for.js","sourceRoot":"","sources":["../../src/tools/methodology-for.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * MCP tool: score_postcode
3
+ *
4
+ * Lets a Claude / Cursor / MCP-compatible client score any UK area for a
5
+ * given decision preset. Wraps POST /v1/score?explain=true so the response
6
+ * carries server-side composed summary + per-dimension reasoning +
7
+ * recommendations + data-source attribution. The MCP NEVER synthesises
8
+ * narrative — every line of output comes from real engine state.
9
+ */
10
+ import type { OogaApiClient, Preset, OogaScoreResponse } from "../api-client.js";
11
+ export declare const scorePostcodeToolName = "score_postcode";
12
+ export declare const scorePostcodeToolDef: {
13
+ readonly name: "score_postcode";
14
+ readonly description: string;
15
+ readonly inputSchema: {
16
+ readonly type: "object";
17
+ readonly properties: {
18
+ readonly area: {
19
+ readonly type: "string";
20
+ readonly description: "UK postcode (e.g. 'SW1A 1AA') or place name (e.g. 'Manchester city centre', 'Shoreditch'). Max 100 characters.";
21
+ };
22
+ readonly preset: {
23
+ readonly type: "string";
24
+ readonly enum: readonly ["moving", "business", "investing", "research"];
25
+ readonly description: string;
26
+ };
27
+ };
28
+ readonly required: readonly ["area", "preset"];
29
+ readonly additionalProperties: false;
30
+ };
31
+ };
32
+ export interface ScorePostcodeArgs {
33
+ area: string;
34
+ preset: Preset;
35
+ }
36
+ export declare function parseScorePostcodeArgs(raw: unknown): ScorePostcodeArgs;
37
+ export declare function formatScoreResultAsText(result: OogaScoreResponse): string;
38
+ /**
39
+ * Tool execution. Calls the API, returns formatted text. Throws on auth /
40
+ * quota errors with a clear message the LLM can pass to the user.
41
+ */
42
+ export declare function executeScorePostcode(client: OogaApiClient, args: ScorePostcodeArgs): Promise<{
43
+ content: Array<{
44
+ type: "text";
45
+ text: string;
46
+ }>;
47
+ isError?: boolean;
48
+ }>;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * MCP tool: score_postcode
3
+ *
4
+ * Lets a Claude / Cursor / MCP-compatible client score any UK area for a
5
+ * given decision preset. Wraps POST /v1/score?explain=true so the response
6
+ * carries server-side composed summary + per-dimension reasoning +
7
+ * recommendations + data-source attribution. The MCP NEVER synthesises
8
+ * narrative — every line of output comes from real engine state.
9
+ */
10
+ import { OogaApiError } from "../api-client.js";
11
+ export const scorePostcodeToolName = "score_postcode";
12
+ export const scorePostcodeToolDef = {
13
+ name: scorePostcodeToolName,
14
+ description: "Score a UK postcode (or place name) for a given decision preset using the OneGoodArea engine. " +
15
+ "Returns the overall 0-100 score, five weighted dimensions with confidence + engine-grounded reasoning, " +
16
+ "a server-composed one-paragraph summary, actionable recommendations from low-scoring or low-confidence dimensions, " +
17
+ "and the list of public datasets that contributed. Every value is deterministic; the engine version is stamped on every response.",
18
+ inputSchema: {
19
+ type: "object",
20
+ properties: {
21
+ area: {
22
+ type: "string",
23
+ description: "UK postcode (e.g. 'SW1A 1AA') or place name (e.g. 'Manchester city centre', 'Shoreditch'). Max 100 characters.",
24
+ },
25
+ preset: {
26
+ type: "string",
27
+ enum: ["moving", "business", "investing", "research"],
28
+ description: "Decision context. 'moving' = origination scoring (residential mortgage suitability, demand-side risk). " +
29
+ "'business' = site selection (footfall, competition, commercial viability). " +
30
+ "'investing' = investment scoring (yield, growth, regeneration). " +
31
+ "'research' = reference scoring (neutral baseline across all five dimensions).",
32
+ },
33
+ },
34
+ required: ["area", "preset"],
35
+ additionalProperties: false,
36
+ },
37
+ };
38
+ export function parseScorePostcodeArgs(raw) {
39
+ if (typeof raw !== "object" || raw === null) {
40
+ throw new Error("score_postcode arguments must be an object");
41
+ }
42
+ const obj = raw;
43
+ const area = obj.area;
44
+ const preset = obj.preset;
45
+ if (typeof area !== "string" || area.trim().length === 0) {
46
+ throw new Error("area must be a non-empty string");
47
+ }
48
+ if (area.length > 100) {
49
+ throw new Error("area must be 100 characters or fewer");
50
+ }
51
+ const presets = ["moving", "business", "investing", "research"];
52
+ if (typeof preset !== "string" || !presets.includes(preset)) {
53
+ throw new Error(`preset must be one of: ${presets.join(", ")}`);
54
+ }
55
+ return { area: area.trim(), preset: preset };
56
+ }
57
+ export function formatScoreResultAsText(result) {
58
+ const lines = [];
59
+ lines.push(`# ${result.area} · ${result.preset} · ${result.score}/100`);
60
+ lines.push(`Engine version: ${result.engine_version}`);
61
+ lines.push(`Area type: ${result.area_type}`);
62
+ lines.push("");
63
+ if (result.summary) {
64
+ lines.push(`## Summary`);
65
+ lines.push(result.summary);
66
+ lines.push("");
67
+ }
68
+ lines.push(`## Dimensions`);
69
+ for (const d of result.dimensions) {
70
+ const conf = ` · confidence ${(d.confidence * 100).toFixed(0)}%`;
71
+ lines.push(`- **${d.label}**: ${d.score}/100 (weight ${d.weight}%${conf})`);
72
+ lines.push(` ${d.reasoning}`);
73
+ lines.push(` _${d.confidence_reason}_`);
74
+ }
75
+ lines.push("");
76
+ if (result.recommendations && result.recommendations.length > 0) {
77
+ lines.push(`## Recommendations`);
78
+ for (const r of result.recommendations)
79
+ lines.push(`- ${r}`);
80
+ lines.push("");
81
+ }
82
+ if (result.data_sources && result.data_sources.length > 0) {
83
+ lines.push(`## Data sources`);
84
+ lines.push(result.data_sources.join(" · "));
85
+ }
86
+ return lines.join("\n").trimEnd();
87
+ }
88
+ /**
89
+ * Tool execution. Calls the API, returns formatted text. Throws on auth /
90
+ * quota errors with a clear message the LLM can pass to the user.
91
+ */
92
+ export async function executeScorePostcode(client, args) {
93
+ try {
94
+ const result = await client.scoreArea(args.area, args.preset);
95
+ return {
96
+ content: [{ type: "text", text: formatScoreResultAsText(result) }],
97
+ };
98
+ }
99
+ catch (err) {
100
+ if (err instanceof OogaApiError) {
101
+ const msg = `OneGoodArea API error (HTTP ${err.status ?? "?"}): ${err.message}`;
102
+ return { content: [{ type: "text", text: msg }], isError: true };
103
+ }
104
+ const msg = err instanceof Error ? err.message : String(err);
105
+ return { content: [{ type: "text", text: `Tool error: ${msg}` }], isError: true };
106
+ }
107
+ }
108
+ //# sourceMappingURL=score-postcode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score-postcode.js","sourceRoot":"","sources":["../../src/tools/score-postcode.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,MAAM,CAAC,MAAM,qBAAqB,GAAG,gBAAgB,CAAC;AAEtD,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,IAAI,EAAE,qBAAqB;IAC3B,WAAW,EACT,gGAAgG;QAChG,yGAAyG;QACzG,qHAAqH;QACrH,kIAAkI;IACpI,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,gHAAgH;aACnH;YACD,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,CAAC;gBACrD,WAAW,EACT,yGAAyG;oBACzG,6EAA6E;oBAC7E,kEAAkE;oBAClE,+EAA+E;aAClF;SACF;QACD,QAAQ,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC;QAC5B,oBAAoB,EAAE,KAAK;KAC5B;CACO,CAAC;AAOX,MAAM,UAAU,sBAAsB,CAAC,GAAY;IACjD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,GAAG,GAAG,GAA8B,CAAC;IAC3C,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;IACtB,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IAE1B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,OAAO,GAAa,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;IAC1E,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAgB,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,0BAA0B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,MAAgB,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAAyB;IAC/D,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,IAAI,MAAM,MAAM,CAAC,MAAM,MAAM,MAAM,CAAC,KAAK,MAAM,CAAC,CAAC;IACxE,KAAK,CAAC,IAAI,CAAC,mBAAmB,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC;IACvD,KAAK,CAAC,IAAI,CAAC,cAAc,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;IAC7C,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC5B,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,iBAAiB,CAAC,CAAC,CAAC,UAAU,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;QACjE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,KAAK,gBAAgB,CAAC,CAAC,MAAM,IAAI,IAAI,GAAG,CAAC,CAAC;QAC5E,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,iBAAiB,GAAG,CAAC,CAAC;IAC3C,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,MAAM,CAAC,eAAe,IAAI,MAAM,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChE,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACjC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,eAAe;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7D,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,IAAI,MAAM,CAAC,YAAY,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1D,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAAqB,EACrB,IAAuB;IAEvB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9D,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,uBAAuB,CAAC,MAAM,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,YAAY,EAAE,CAAC;YAChC,MAAM,GAAG,GAAG,+BAA+B,GAAG,CAAC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC;YAChF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACnE,CAAC;QACD,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACpF,CAAC;AACH,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared markdown formatter for the Signals tools (AR-366).
3
+ *
4
+ * Both `get_area_signals` and `get_signals_by_category` return the same
5
+ * AreaProfile shape; this module renders it consistently. Every line is
6
+ * a real field from the response — no client-side text synthesis.
7
+ */
8
+ import type { OogaAreaProfile, SignalCategory } from "../api-client.js";
9
+ /** Format an AreaProfile as a markdown brief. If `restrictTo` is given,
10
+ only that category renders (used by get_signals_by_category). */
11
+ export declare function formatAreaProfileAsText(profile: OogaAreaProfile, restrictTo?: SignalCategory): string;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Shared markdown formatter for the Signals tools (AR-366).
3
+ *
4
+ * Both `get_area_signals` and `get_signals_by_category` return the same
5
+ * AreaProfile shape; this module renders it consistently. Every line is
6
+ * a real field from the response — no client-side text synthesis.
7
+ */
8
+ /** Format a Signal value for display. Numbers get optional comma thousands
9
+ separators when they're large; strings render verbatim; null → "—". */
10
+ function formatValue(s) {
11
+ if (s.value === null)
12
+ return "—";
13
+ if (typeof s.value === "string")
14
+ return s.value;
15
+ const n = s.value;
16
+ /* Currency: render as £42,500 with no decimals. */
17
+ if (s.unit === "GBP")
18
+ return `£${Math.round(n).toLocaleString("en-GB")}`;
19
+ /* Percent: render with one decimal + suffix. */
20
+ if (s.unit === "pct")
21
+ return `${n.toFixed(1)}%`;
22
+ /* Integer-like units. */
23
+ if (s.unit === "count" || s.unit === "rank" || s.unit === "decile") {
24
+ return Math.round(n).toLocaleString("en-GB");
25
+ }
26
+ /* Per-period. */
27
+ if (s.unit === "per_month")
28
+ return `${n.toFixed(1)} / mo`;
29
+ /* Default: number as-is. */
30
+ return n.toLocaleString("en-GB");
31
+ }
32
+ function formatPercentile(p) {
33
+ /* Use one decimal only when the value would otherwise round away. */
34
+ if (p === Math.round(p))
35
+ return `${p}th percentile`;
36
+ return `${p.toFixed(1)}th percentile`;
37
+ }
38
+ function renderSignal(s) {
39
+ const lines = [];
40
+ const unit = s.unit ? ` ${s.unit}` : "";
41
+ const valueLine = `**${s.label}**: ${formatValue(s)}${s.value !== null && s.unit && !["GBP", "pct"].includes(s.unit) ? unit : ""}`;
42
+ lines.push(`- ${valueLine}`);
43
+ /* Percentile (store-backed only). */
44
+ if (typeof s.percentile === "number") {
45
+ lines.push(` ${formatPercentile(s.percentile)} (direction: ${s.direction.replace(/_/g, " ")})`);
46
+ }
47
+ /* Confidence with engine-grounded reason. */
48
+ lines.push(` Confidence ${(s.confidence * 100).toFixed(0)}% · ${s.confidence_reason}`);
49
+ /* Provenance. */
50
+ lines.push(` Source: ${s.source} · ${s.observed_period}`);
51
+ return lines;
52
+ }
53
+ /** Format an AreaProfile as a markdown brief. If `restrictTo` is given,
54
+ only that category renders (used by get_signals_by_category). */
55
+ export function formatAreaProfileAsText(profile, restrictTo) {
56
+ const lines = [];
57
+ const geo = profile.geo;
58
+ /* Headline. */
59
+ const headlineParts = [geo.query];
60
+ if (geo.postcode && geo.postcode !== geo.query)
61
+ headlineParts.push(`(${geo.postcode})`);
62
+ if (restrictTo)
63
+ headlineParts.push(`— ${restrictTo}`);
64
+ lines.push(`# ${headlineParts.join(" ")}`);
65
+ lines.push(`Engine version: ${profile.meta.engine_version} · Fetch mode: ${profile.meta.fetch_mode}`);
66
+ lines.push("");
67
+ /* Geo summary line. */
68
+ const geoBits = [];
69
+ if (geo.admin_district)
70
+ geoBits.push(geo.admin_district);
71
+ if (geo.region)
72
+ geoBits.push(geo.region);
73
+ geoBits.push(geo.country);
74
+ geoBits.push(`${geo.area_type} area`);
75
+ if (geo.lsoa)
76
+ geoBits.push(`LSOA ${geo.lsoa}`);
77
+ lines.push(`**Area:** ${geoBits.join(" · ")}`);
78
+ lines.push("");
79
+ /* Group signals by category (preserves category order from the data
80
+ since /v1/area returns them in that order; filter() preserves order). */
81
+ const signals = restrictTo
82
+ ? profile.signals.filter((s) => s.category === restrictTo)
83
+ : profile.signals;
84
+ if (signals.length === 0) {
85
+ lines.push(restrictTo
86
+ ? `No ${restrictTo} signals returned for this area.`
87
+ : "No signals returned for this area.");
88
+ return lines.join("\n").trimEnd();
89
+ }
90
+ const byCategory = new Map();
91
+ for (const s of signals) {
92
+ const cat = s.category;
93
+ if (!byCategory.has(cat))
94
+ byCategory.set(cat, []);
95
+ byCategory.get(cat).push(s);
96
+ }
97
+ /* Category header per group when not restricted; just one flat list
98
+ when restricted (the category is already in the document title). */
99
+ for (const [category, group] of byCategory) {
100
+ if (!restrictTo) {
101
+ lines.push(`## ${category}`);
102
+ }
103
+ for (const s of group) {
104
+ for (const line of renderSignal(s))
105
+ lines.push(line);
106
+ }
107
+ lines.push("");
108
+ }
109
+ /* Sources footer (deduplicated, from the response meta). */
110
+ if (profile.meta.sources.length > 0) {
111
+ lines.push(`## Data sources`);
112
+ lines.push(profile.meta.sources.join(" · "));
113
+ }
114
+ return lines.join("\n").trimEnd();
115
+ }
116
+ //# sourceMappingURL=signals-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signals-format.js","sourceRoot":"","sources":["../../src/tools/signals-format.ts"],"names":[],"mappings":"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"}