@typedstandards/civic-typed-harness 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/CHANGELOG.md +57 -0
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/dist/capture/data-sources.d.ts +58 -0
- package/dist/capture/data-sources.js +119 -0
- package/dist/capture/provenance.d.ts +48 -0
- package/dist/capture/provenance.js +290 -0
- package/dist/capture/skill-metadata.d.ts +23 -0
- package/dist/capture/skill-metadata.js +45 -0
- package/dist/capture/trace.d.ts +104 -0
- package/dist/capture/trace.js +166 -0
- package/dist/format/dathere.d.ts +83 -0
- package/dist/format/dathere.js +111 -0
- package/dist/format/profiles.d.ts +13 -0
- package/dist/format/profiles.js +26 -0
- package/dist/format/sources.d.ts +54 -0
- package/dist/format/sources.js +93 -0
- package/dist/format/vocabulary.d.ts +37 -0
- package/dist/format/vocabulary.js +53 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +30 -0
- package/dist/rubric/adversarial-eval-core.d.ts +27 -0
- package/dist/rubric/adversarial-eval-core.js +128 -0
- package/package.json +67 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Civic data-source registry (FORMAT-EXTENSION group) — the source ids the
|
|
2
|
+
// civic vocabulary knows, their display names, catalog types, and endpoint
|
|
3
|
+
// coordinates, plus the default tool-name → source-id resolver and the
|
|
4
|
+
// display helpers. Relocated from civic-ai-tools-website:
|
|
5
|
+
// - endpoint constants + display helpers: `src/lib/evidence/data-sources.ts:15–16, 41–76`
|
|
6
|
+
// - agent titles/server URLs: `src/lib/evidence/provenance.ts:132–148`
|
|
7
|
+
// - static tool→source map: `src/lib/mcp/operation-types.ts:21–33` (the
|
|
8
|
+
// app's MCP registry itself stays app-side; this is the harness's civic
|
|
9
|
+
// DEFAULT — callers supply their own resolver to add or replace sources).
|
|
10
|
+
//
|
|
11
|
+
// Endpoint URLs name the demo deployment's data plane, so the registry is a
|
|
12
|
+
// typed config input (config-not-constants, S2 brief §2) with the civic
|
|
13
|
+
// values as the exported default map.
|
|
14
|
+
/** The civic default registry — the three demo sources. */
|
|
15
|
+
export const CIVIC_SOURCE_REGISTRY = {
|
|
16
|
+
socrata: {
|
|
17
|
+
displayName: 'Socrata',
|
|
18
|
+
agentTitle: 'Socrata MCP Server',
|
|
19
|
+
serverUrl: 'https://socrata-mcp.civicaitools.org',
|
|
20
|
+
catalogType: 'socrata',
|
|
21
|
+
},
|
|
22
|
+
'data-commons': {
|
|
23
|
+
displayName: 'Data Commons',
|
|
24
|
+
agentTitle: 'Google Data Commons MCP Server',
|
|
25
|
+
serverUrl: 'https://api.datacommons.org/mcp',
|
|
26
|
+
catalogType: 'data-commons',
|
|
27
|
+
aggregatePortalUrl: 'https://api.datacommons.org/mcp',
|
|
28
|
+
},
|
|
29
|
+
'boston-opencontext': {
|
|
30
|
+
displayName: 'Boston OpenContext',
|
|
31
|
+
agentTitle: 'Boston OpenContext MCP Server',
|
|
32
|
+
serverUrl: 'https://data-mcp.boston.gov/mcp',
|
|
33
|
+
catalogType: 'ckan',
|
|
34
|
+
aggregatePortalUrl: 'https://data.boston.gov',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
/** The source id untagged/pre-M9.1 captures fall back to. Socrata was the
|
|
38
|
+
* only source when those packages were written. */
|
|
39
|
+
export const FALLBACK_SOURCE_ID = 'socrata';
|
|
40
|
+
/** Is this source dataset-keyed (one `dataSources` entry per dataset) rather
|
|
41
|
+
* than aggregate? Unknown ids are neither — they contribute no entry. */
|
|
42
|
+
export function isDatasetKeyedSource(sourceId, registry = CIVIC_SOURCE_REGISTRY) {
|
|
43
|
+
const info = registry[sourceId];
|
|
44
|
+
return info !== undefined && info.aggregatePortalUrl === undefined;
|
|
45
|
+
}
|
|
46
|
+
/** Civic default tool-name → source-id map (mirrors the demo app's static
|
|
47
|
+
* MCP-layer map; used as a fallback when a trace span carries no
|
|
48
|
+
* `mcp.source` attribute). */
|
|
49
|
+
export const CIVIC_TOOL_SOURCE_MAP = {
|
|
50
|
+
get_data: 'socrata',
|
|
51
|
+
search: 'socrata',
|
|
52
|
+
fetch: 'socrata',
|
|
53
|
+
search_indicators: 'data-commons',
|
|
54
|
+
get_observations: 'data-commons',
|
|
55
|
+
ckan__search_datasets: 'boston-opencontext',
|
|
56
|
+
ckan__get_dataset: 'boston-opencontext',
|
|
57
|
+
ckan__query_data: 'boston-opencontext',
|
|
58
|
+
ckan__get_schema: 'boston-opencontext',
|
|
59
|
+
ckan__execute_sql: 'boston-opencontext',
|
|
60
|
+
ckan__aggregate_data: 'boston-opencontext',
|
|
61
|
+
};
|
|
62
|
+
/** The civic default `ToolSourceResolver`. */
|
|
63
|
+
export const civicToolSourceResolver = (toolName) => CIVIC_TOOL_SOURCE_MAP[toolName];
|
|
64
|
+
/** Human-friendly display label for a `sourceId`. Unknown ids fall back to a
|
|
65
|
+
* capitalised form of the raw id so new sources render sensibly before the
|
|
66
|
+
* registry is updated. Missing ids coerce to the fallback source (pre-M9.3
|
|
67
|
+
* packages have no `sourceId` on dataSources entries). */
|
|
68
|
+
export function displayNameForSource(sourceId, registry = CIVIC_SOURCE_REGISTRY) {
|
|
69
|
+
const id = sourceId || FALLBACK_SOURCE_ID;
|
|
70
|
+
return (registry[id]?.displayName
|
|
71
|
+
?? id
|
|
72
|
+
.split('-')
|
|
73
|
+
.filter(Boolean)
|
|
74
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
75
|
+
.join(' '));
|
|
76
|
+
}
|
|
77
|
+
/** Format a `dataSources` array as a compact, de-duplicated summary string
|
|
78
|
+
* (middle-dot separated). Returns `null` when the array is empty or missing,
|
|
79
|
+
* letting callers render a fallback. */
|
|
80
|
+
export function formatDataSourcesSummary(entries, registry = CIVIC_SOURCE_REGISTRY) {
|
|
81
|
+
if (!entries || entries.length === 0)
|
|
82
|
+
return null;
|
|
83
|
+
const seen = new Set();
|
|
84
|
+
const ordered = [];
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
const name = displayNameForSource(entry.sourceId, registry);
|
|
87
|
+
if (seen.has(name))
|
|
88
|
+
continue;
|
|
89
|
+
seen.add(name);
|
|
90
|
+
ordered.push(name);
|
|
91
|
+
}
|
|
92
|
+
return ordered.join(' · ');
|
|
93
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** The `civic:` JSON-LD namespace URI. Part of the civic vocabulary itself
|
|
2
|
+
* (it names the term set, not a deployment), so it is a constant, not a
|
|
3
|
+
* config input. */
|
|
4
|
+
export declare const CIVIC_NS = "https://civicaitools.org/ns/evidence/";
|
|
5
|
+
/** The civic evidence-package JSON-LD `@context`: prov / xsd / civic /
|
|
6
|
+
* dcterms, in the reference emission order (legacy-chain byte discipline —
|
|
7
|
+
* insertion order is the byte contract). */
|
|
8
|
+
export declare function makeCivicProvContext(): Record<string, string>;
|
|
9
|
+
/** Root of the `urn:civic-evidence:` id scheme. */
|
|
10
|
+
export declare const CIVIC_URN_PREFIX = "urn:civic-evidence";
|
|
11
|
+
/** Package-scoped node id: `urn:civic-evidence:<packageId>:<type>:<id>`. */
|
|
12
|
+
export declare function civicUrn(packageId: string, type: string, id: string): string;
|
|
13
|
+
/** Model-agent id: `urn:civic-evidence:model:<modelId>` (slashes in the
|
|
14
|
+
* model identifier collapse to dashes). */
|
|
15
|
+
export declare function civicModelUrn(model: string): string;
|
|
16
|
+
/** MCP source-agent id: `urn:civic-evidence:mcp-server:<sourceId>`. Known
|
|
17
|
+
* source ids are emitted raw (they are already URN-safe by construction);
|
|
18
|
+
* callers emitting an UNKNOWN id must encode it first (the capture-side
|
|
19
|
+
* builder uses `encodeURIComponent`, matching the reference behavior). */
|
|
20
|
+
export declare function civicSourceAgentUrn(sourceId: string): string;
|
|
21
|
+
/** Platform-agent id: `urn:civic-evidence:platform:<platformId>`. */
|
|
22
|
+
export declare function civicPlatformUrn(platformId: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Platform-agent identity — WHO published, as a PROV agent. A typed config
|
|
25
|
+
* input (config-not-constants, S2 brief §2): every deployment names its own;
|
|
26
|
+
* the civicaitools.org demo values are the exported default below.
|
|
27
|
+
*/
|
|
28
|
+
export interface PlatformAgentConfig {
|
|
29
|
+
/** Stable platform id — becomes `urn:civic-evidence:platform:<id>`. */
|
|
30
|
+
id: string;
|
|
31
|
+
/** Human-readable platform title (`dcterms:title`). */
|
|
32
|
+
title: string;
|
|
33
|
+
/** Public URL of the deployment (`civic:url`). */
|
|
34
|
+
url: string;
|
|
35
|
+
}
|
|
36
|
+
/** Demo default: the civicaitools.org reference deployment's identity. */
|
|
37
|
+
export declare const CIVICAITOOLS_PLATFORM_AGENT: PlatformAgentConfig;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Civic PROV-O vocabulary (FORMAT-EXTENSION group) — the `civic:` JSON-LD
|
|
2
|
+
// namespace, the `urn:civic-evidence:` id scheme, and the platform-agent
|
|
3
|
+
// identity shape. Relocated from civic-ai-tools-website
|
|
4
|
+
// `src/lib/evidence/provenance.ts` (`:51` urn scheme, `:179–184` platform
|
|
5
|
+
// agent, `:384–391` context) per the S2 brief §1.
|
|
6
|
+
//
|
|
7
|
+
// This module defines vocabulary; it never walks a trace. The capture-side
|
|
8
|
+
// provenance BUILDER (src/capture/provenance.ts) imports its terms from here —
|
|
9
|
+
// that direction is the package's internal module boundary.
|
|
10
|
+
import { makeProvContext, PROV_NS, XSD_NS, DCTERMS_NS } from '@typedstandards/produce-core';
|
|
11
|
+
/** The `civic:` JSON-LD namespace URI. Part of the civic vocabulary itself
|
|
12
|
+
* (it names the term set, not a deployment), so it is a constant, not a
|
|
13
|
+
* config input. */
|
|
14
|
+
export const CIVIC_NS = 'https://civicaitools.org/ns/evidence/';
|
|
15
|
+
/** The civic evidence-package JSON-LD `@context`: prov / xsd / civic /
|
|
16
|
+
* dcterms, in the reference emission order (legacy-chain byte discipline —
|
|
17
|
+
* insertion order is the byte contract). */
|
|
18
|
+
export function makeCivicProvContext() {
|
|
19
|
+
return makeProvContext({
|
|
20
|
+
prov: PROV_NS,
|
|
21
|
+
xsd: XSD_NS,
|
|
22
|
+
civic: CIVIC_NS,
|
|
23
|
+
dcterms: DCTERMS_NS,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** Root of the `urn:civic-evidence:` id scheme. */
|
|
27
|
+
export const CIVIC_URN_PREFIX = 'urn:civic-evidence';
|
|
28
|
+
/** Package-scoped node id: `urn:civic-evidence:<packageId>:<type>:<id>`. */
|
|
29
|
+
export function civicUrn(packageId, type, id) {
|
|
30
|
+
return `${CIVIC_URN_PREFIX}:${packageId}:${type}:${id}`;
|
|
31
|
+
}
|
|
32
|
+
/** Model-agent id: `urn:civic-evidence:model:<modelId>` (slashes in the
|
|
33
|
+
* model identifier collapse to dashes). */
|
|
34
|
+
export function civicModelUrn(model) {
|
|
35
|
+
return `${CIVIC_URN_PREFIX}:model:${model.replace(/\//g, '-')}`;
|
|
36
|
+
}
|
|
37
|
+
/** MCP source-agent id: `urn:civic-evidence:mcp-server:<sourceId>`. Known
|
|
38
|
+
* source ids are emitted raw (they are already URN-safe by construction);
|
|
39
|
+
* callers emitting an UNKNOWN id must encode it first (the capture-side
|
|
40
|
+
* builder uses `encodeURIComponent`, matching the reference behavior). */
|
|
41
|
+
export function civicSourceAgentUrn(sourceId) {
|
|
42
|
+
return `${CIVIC_URN_PREFIX}:mcp-server:${sourceId}`;
|
|
43
|
+
}
|
|
44
|
+
/** Platform-agent id: `urn:civic-evidence:platform:<platformId>`. */
|
|
45
|
+
export function civicPlatformUrn(platformId) {
|
|
46
|
+
return `${CIVIC_URN_PREFIX}:platform:${platformId}`;
|
|
47
|
+
}
|
|
48
|
+
/** Demo default: the civicaitools.org reference deployment's identity. */
|
|
49
|
+
export const CIVICAITOOLS_PLATFORM_AGENT = {
|
|
50
|
+
id: 'civic-ai-tools',
|
|
51
|
+
title: 'Civic AI Tools',
|
|
52
|
+
url: 'https://civicaitools.org',
|
|
53
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './format/vocabulary.ts';
|
|
2
|
+
export * from './format/sources.ts';
|
|
3
|
+
export * from './format/dathere.ts';
|
|
4
|
+
export * from './format/profiles.ts';
|
|
5
|
+
export * from './capture/trace.ts';
|
|
6
|
+
export * from './capture/skill-metadata.ts';
|
|
7
|
+
export * from './capture/data-sources.ts';
|
|
8
|
+
export * from './capture/provenance.ts';
|
|
9
|
+
export * from './rubric/adversarial-eval-core.ts';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// civic-typed-harness — the civic DOMAIN layer of ADR-0021's format/domain
|
|
2
|
+
// line, relocated from the reference app (civic-ai-tools-website) per the S2
|
|
3
|
+
// brief. The operating rule: THE HARNESS DERIVES, THE CORE ASSEMBLES — every
|
|
4
|
+
// export here produces domain-derived values (vocabulary terms, datHere
|
|
5
|
+
// policy fields, provenance graphs, capture artifacts) that feed
|
|
6
|
+
// @typedstandards/produce-core's neutral envelope assembly.
|
|
7
|
+
//
|
|
8
|
+
// Internal module boundary (structure-for-the-future; see README):
|
|
9
|
+
// - format/ — the civic FORMAT-EXTENSION: what extends the standard
|
|
10
|
+
// (civic: vocabulary, source registry, datHere policy, profile constants).
|
|
11
|
+
// - capture/ — what PRODUCES under it: TraceBuilder, skill-metadata
|
|
12
|
+
// extraction, data-source population, and the provenance BUILDER (which
|
|
13
|
+
// imports its vocabulary from format/). Clock + RNG live here only.
|
|
14
|
+
// - rubric/ — the adversarial-evaluation pure core (Q26-pinned hash).
|
|
15
|
+
//
|
|
16
|
+
// Purity contract (harness-grade): no I/O and no environment reads anywhere;
|
|
17
|
+
// clock + RNG permitted in capture modules only and injectable for tests.
|
|
18
|
+
// Enforced by eslint.config.mjs and src/purity.test.ts.
|
|
19
|
+
// Format-extension group
|
|
20
|
+
export * from "./format/vocabulary.js";
|
|
21
|
+
export * from "./format/sources.js";
|
|
22
|
+
export * from "./format/dathere.js";
|
|
23
|
+
export * from "./format/profiles.js";
|
|
24
|
+
// Capture group
|
|
25
|
+
export * from "./capture/trace.js";
|
|
26
|
+
export * from "./capture/skill-metadata.js";
|
|
27
|
+
export * from "./capture/data-sources.js";
|
|
28
|
+
export * from "./capture/provenance.js";
|
|
29
|
+
// Rubric group
|
|
30
|
+
export * from "./rubric/adversarial-eval-core.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type EvidencePackage, type EvaluationResults } from '@typedstandards/produce-core';
|
|
2
|
+
/** Rubric identifier carried as the `scoringRubric` payload field and the
|
|
3
|
+
* methodology `testSet`. Six criteria, 1-10 each, plus aggregate. */
|
|
4
|
+
export declare const RUBRIC_ID = "civicaitools-adversarial-rubric/six-criterion-v1";
|
|
5
|
+
export declare const EVALUATION_CRITERIA: readonly ["dataSourceIdentification", "quantitativeClaimSupport", "confoundersAndBias", "geographicScope", "limitationsNoted", "contradictoryConclusion"];
|
|
6
|
+
export declare const EVALUATION_RUBRIC = "You are an independent evaluator assessing an AI-generated civic data analysis.\n\nYou will receive:\n1. The original prompt (question asked)\n2. The tool calls made (MCP queries to Socrata open data portals)\n3. The AI's final output/analysis\n\nEvaluate the analysis against these 6 criteria, scoring each 1-10:\n\na) **Data Source Identification** (1-10): Does the output correctly identify the data source(s) and time period? Are dataset IDs, portal domains, and date ranges accurate?\n\nb) **Quantitative Claim Support** (1-10): Are the quantitative claims (numbers, percentages, rankings) supported by the data returned in the tool calls? Cross-check key figures against the raw data.\n\nc) **Confounders and Bias** (1-10): Does the analysis acknowledge obvious confounders, selection biases, or framing issues? Are there lurking variables or cherry-picked timeframes?\n\nd) **Geographic Scope** (1-10): Is the geographic scope appropriate for the question? Does the analysis avoid over-generalizing from one jurisdiction?\n\ne) **Limitations Noted** (1-10): Are limitations and caveats noted? Does the analysis flag data quality issues, missing fields, or incomplete coverage?\n\nf) **Contradictory Conclusion** (1-10): Could the same data reasonably support a contradictory conclusion? Does the analysis consider alternative interpretations?\n\nRespond in this exact JSON format (no markdown fences, just raw JSON):\n{\n \"dataSourceIdentification\": { \"score\": <1-10>, \"comment\": \"<1-2 sentences>\" },\n \"quantitativeClaimSupport\": { \"score\": <1-10>, \"comment\": \"<1-2 sentences>\" },\n \"confoundersAndBias\": { \"score\": <1-10>, \"comment\": \"<1-2 sentences>\" },\n \"geographicScope\": { \"score\": <1-10>, \"comment\": \"<1-2 sentences>\" },\n \"limitationsNoted\": { \"score\": <1-10>, \"comment\": \"<1-2 sentences>\" },\n \"contradictoryConclusion\": { \"score\": <1-10>, \"comment\": \"<1-2 sentences>\" },\n \"overallScore\": <average of all 6 scores, one decimal>,\n \"assessment\": \"<2-4 sentence overall assessment>\"\n}";
|
|
7
|
+
/** The methodology's `promptSetVersion`: SHA-256 of the rubric text, computed
|
|
8
|
+
* once at module load. Any wording change to the rubric produces a new
|
|
9
|
+
* version, so an attestation pins the exact prompt set that scored it. */
|
|
10
|
+
export declare const RUBRIC_VERSION_SHA256: string;
|
|
11
|
+
/** Build the user-turn evaluation content from a package's signed fields. */
|
|
12
|
+
export declare function buildEvaluationPrompt(pkg: EvidencePackage): string;
|
|
13
|
+
export type ParsedEvaluation = {
|
|
14
|
+
ok: true;
|
|
15
|
+
results: EvaluationResults;
|
|
16
|
+
} | {
|
|
17
|
+
ok: false;
|
|
18
|
+
error: string;
|
|
19
|
+
raw: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Parse + validate an evaluator response into structured results. Pure —
|
|
23
|
+
* unit-tested directly. Strips markdown fences (some models wrap despite the
|
|
24
|
+
* instruction), requires every criterion with a numeric score, and recomputes
|
|
25
|
+
* the aggregate when the model omits it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseEvaluationResponse(raw: string): ParsedEvaluation;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Adversarial evaluation — PURE core (RUBRIC group): the civic six-criterion
|
|
2
|
+
// rubric text, its pinned identifiers, the prompt builder, and the response
|
|
3
|
+
// parser. Relocated from civic-ai-tools-website
|
|
4
|
+
// `src/lib/evidence/adversarial-eval-core.ts:13, 24–55, 66–94` per the S2
|
|
5
|
+
// brief §1. The model runner and DB/blob emission (`adversarial-eval.ts`)
|
|
6
|
+
// stay app-side — this module never performs I/O.
|
|
7
|
+
//
|
|
8
|
+
// ⚠ RUBRIC_VERSION_SHA256 is a Q26-pinned version hash: attestations pin the
|
|
9
|
+
// exact prompt set that scored them by this value. The rubric text below is
|
|
10
|
+
// byte-exact with the reference implementation (the relocation changes no
|
|
11
|
+
// hashes), and the test suite asserts the literal digest. Any wording change
|
|
12
|
+
// to EVALUATION_RUBRIC is a NEW rubric version — never edit the text without
|
|
13
|
+
// versioning intent.
|
|
14
|
+
import { sha256Hex, } from '@typedstandards/produce-core';
|
|
15
|
+
/** Rubric identifier carried as the `scoringRubric` payload field and the
|
|
16
|
+
* methodology `testSet`. Six criteria, 1-10 each, plus aggregate. */
|
|
17
|
+
export const RUBRIC_ID = 'civicaitools-adversarial-rubric/six-criterion-v1';
|
|
18
|
+
export const EVALUATION_CRITERIA = [
|
|
19
|
+
'dataSourceIdentification',
|
|
20
|
+
'quantitativeClaimSupport',
|
|
21
|
+
'confoundersAndBias',
|
|
22
|
+
'geographicScope',
|
|
23
|
+
'limitationsNoted',
|
|
24
|
+
'contradictoryConclusion',
|
|
25
|
+
];
|
|
26
|
+
export const EVALUATION_RUBRIC = `You are an independent evaluator assessing an AI-generated civic data analysis.
|
|
27
|
+
|
|
28
|
+
You will receive:
|
|
29
|
+
1. The original prompt (question asked)
|
|
30
|
+
2. The tool calls made (MCP queries to Socrata open data portals)
|
|
31
|
+
3. The AI's final output/analysis
|
|
32
|
+
|
|
33
|
+
Evaluate the analysis against these 6 criteria, scoring each 1-10:
|
|
34
|
+
|
|
35
|
+
a) **Data Source Identification** (1-10): Does the output correctly identify the data source(s) and time period? Are dataset IDs, portal domains, and date ranges accurate?
|
|
36
|
+
|
|
37
|
+
b) **Quantitative Claim Support** (1-10): Are the quantitative claims (numbers, percentages, rankings) supported by the data returned in the tool calls? Cross-check key figures against the raw data.
|
|
38
|
+
|
|
39
|
+
c) **Confounders and Bias** (1-10): Does the analysis acknowledge obvious confounders, selection biases, or framing issues? Are there lurking variables or cherry-picked timeframes?
|
|
40
|
+
|
|
41
|
+
d) **Geographic Scope** (1-10): Is the geographic scope appropriate for the question? Does the analysis avoid over-generalizing from one jurisdiction?
|
|
42
|
+
|
|
43
|
+
e) **Limitations Noted** (1-10): Are limitations and caveats noted? Does the analysis flag data quality issues, missing fields, or incomplete coverage?
|
|
44
|
+
|
|
45
|
+
f) **Contradictory Conclusion** (1-10): Could the same data reasonably support a contradictory conclusion? Does the analysis consider alternative interpretations?
|
|
46
|
+
|
|
47
|
+
Respond in this exact JSON format (no markdown fences, just raw JSON):
|
|
48
|
+
{
|
|
49
|
+
"dataSourceIdentification": { "score": <1-10>, "comment": "<1-2 sentences>" },
|
|
50
|
+
"quantitativeClaimSupport": { "score": <1-10>, "comment": "<1-2 sentences>" },
|
|
51
|
+
"confoundersAndBias": { "score": <1-10>, "comment": "<1-2 sentences>" },
|
|
52
|
+
"geographicScope": { "score": <1-10>, "comment": "<1-2 sentences>" },
|
|
53
|
+
"limitationsNoted": { "score": <1-10>, "comment": "<1-2 sentences>" },
|
|
54
|
+
"contradictoryConclusion": { "score": <1-10>, "comment": "<1-2 sentences>" },
|
|
55
|
+
"overallScore": <average of all 6 scores, one decimal>,
|
|
56
|
+
"assessment": "<2-4 sentence overall assessment>"
|
|
57
|
+
}`;
|
|
58
|
+
/** The methodology's `promptSetVersion`: SHA-256 of the rubric text, computed
|
|
59
|
+
* once at module load. Any wording change to the rubric produces a new
|
|
60
|
+
* version, so an attestation pins the exact prompt set that scored it. */
|
|
61
|
+
export const RUBRIC_VERSION_SHA256 = sha256Hex(EVALUATION_RUBRIC);
|
|
62
|
+
/** Build the user-turn evaluation content from a package's signed fields. */
|
|
63
|
+
export function buildEvaluationPrompt(pkg) {
|
|
64
|
+
const toolCallSummary = pkg.queries
|
|
65
|
+
.map((q, i) => {
|
|
66
|
+
const argStr = Object.entries(q.arguments)
|
|
67
|
+
.map(([k, v]) => `${k}=${JSON.stringify(v)}`)
|
|
68
|
+
.join(', ');
|
|
69
|
+
return ` ${i + 1}. ${q.tool}(${argStr}) → ${q.resultRows ?? '?'} rows`;
|
|
70
|
+
})
|
|
71
|
+
.join('\n');
|
|
72
|
+
const dataSources = pkg.dataSources
|
|
73
|
+
.map(ds => ` - ${ds.datasetUrl} (accessed ${ds.accessTimestamp})`)
|
|
74
|
+
.join('\n');
|
|
75
|
+
return `## Original Prompt
|
|
76
|
+
${pkg.prompt.text || '[prompt text not available]'}
|
|
77
|
+
|
|
78
|
+
## Tool Calls Made (${pkg.queries.length} total)
|
|
79
|
+
${toolCallSummary || ' (none)'}
|
|
80
|
+
|
|
81
|
+
## Data Sources
|
|
82
|
+
${dataSources || ' (none)'}
|
|
83
|
+
|
|
84
|
+
## Model Used
|
|
85
|
+
${pkg.cost.model}
|
|
86
|
+
|
|
87
|
+
## AI Output
|
|
88
|
+
${pkg.output}`;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Parse + validate an evaluator response into structured results. Pure —
|
|
92
|
+
* unit-tested directly. Strips markdown fences (some models wrap despite the
|
|
93
|
+
* instruction), requires every criterion with a numeric score, and recomputes
|
|
94
|
+
* the aggregate when the model omits it.
|
|
95
|
+
*/
|
|
96
|
+
export function parseEvaluationResponse(raw) {
|
|
97
|
+
const jsonStr = raw.replace(/```json\s*/g, '').replace(/```\s*/g, '').trim();
|
|
98
|
+
let parsed;
|
|
99
|
+
try {
|
|
100
|
+
parsed = JSON.parse(jsonStr);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return { ok: false, error: 'Evaluator returned invalid JSON', raw };
|
|
104
|
+
}
|
|
105
|
+
const perCriterion = {};
|
|
106
|
+
for (const key of EVALUATION_CRITERIA) {
|
|
107
|
+
const entry = parsed[key];
|
|
108
|
+
if (!entry || typeof entry.score !== 'number') {
|
|
109
|
+
return { ok: false, error: `Missing or invalid rubric criterion: ${key}`, raw };
|
|
110
|
+
}
|
|
111
|
+
perCriterion[key] = {
|
|
112
|
+
score: entry.score,
|
|
113
|
+
comment: typeof entry.comment === 'string' ? entry.comment : '',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const overallScore = typeof parsed.overallScore === 'number'
|
|
117
|
+
? parsed.overallScore
|
|
118
|
+
: EVALUATION_CRITERIA.reduce((sum, k) => sum + perCriterion[k].score, 0) /
|
|
119
|
+
EVALUATION_CRITERIA.length;
|
|
120
|
+
return {
|
|
121
|
+
ok: true,
|
|
122
|
+
results: {
|
|
123
|
+
perCriterion,
|
|
124
|
+
overallScore,
|
|
125
|
+
assessment: typeof parsed.assessment === 'string' ? parsed.assessment : '',
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@typedstandards/civic-typed-harness",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Civic-domain harness for Typed Standards evidence packages — the DOMAIN layer of the ADR-0021 format/domain line: civic PROV-O vocabulary and provenance builder, datHere envelope-policy derivations, and the capture machinery (trace builder, skill-metadata extraction, data-source population, adversarial rubric core). Derives; @typedstandards/produce-core assembles.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Nathan Storey",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"module": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"CHANGELOG.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc -p tsconfig.json",
|
|
28
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo",
|
|
29
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
30
|
+
"lint": "eslint .",
|
|
31
|
+
"test": "node --test --experimental-strip-types \"src/**/*.test.ts\"",
|
|
32
|
+
"prepare": "npm run build",
|
|
33
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@typedstandards/produce-core": "^0.2.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@typescript-eslint/parser": "^8",
|
|
40
|
+
"eslint": "^9",
|
|
41
|
+
"typescript": "^5.9.3"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/npstorey/civic-ai-tools.git",
|
|
52
|
+
"directory": "packages/civic-typed-harness"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://github.com/npstorey/civic-ai-tools/tree/main/packages/civic-typed-harness#readme",
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/npstorey/civic-ai-tools/issues"
|
|
57
|
+
},
|
|
58
|
+
"keywords": [
|
|
59
|
+
"typed-standards",
|
|
60
|
+
"evidence",
|
|
61
|
+
"provenance",
|
|
62
|
+
"civic",
|
|
63
|
+
"harness",
|
|
64
|
+
"prov-o",
|
|
65
|
+
"otel"
|
|
66
|
+
]
|
|
67
|
+
}
|