Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions knowledge-graph-measurement-harmonization-guard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Knowledge Graph Measurement Harmonization Guard

This self-contained slice reviews scientific measurement edges before the knowledge graph treats them as comparable.
It normalizes units, checks biological context, compares statistical endpoints, and blocks low-provenance recommendations.

## Run locally

```bash
node knowledge-graph-measurement-harmonization-guard/test.js
node knowledge-graph-measurement-harmonization-guard/demo.js
```

Demo outputs are written to `knowledge-graph-measurement-harmonization-guard/demo-output/`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Acceptance Notes

- The module is dependency-free and uses synthetic graph data only.
- Tests cover blocked graph edges, deterministic unit normalization, JSON-LD review output, and a ready graph path.
- Demo artifacts include JSON, Markdown, SVG, and MP4 output.
- The slice is distinct from prior #17 submissions around broad extractors, link audit, ontology drift, relationship conflict, author-affiliation disambiguation, artifact lineage, evidence freshness, reproducibility routes, visibility guards, and negative-result replication signals.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Measurement harmonization curator actions

Graph: kg-cardio-measurement-review
Status: curation_required

- edge-rat-human-pressure: Split the graph edge or add a cross-context translation model before recommending reuse.
- edge-low-provenance: Attach DOI, protocol, or dataset provenance before surfacing this recommendation.
- edge-mean-vs-median: Normalize the statistical endpoint or require curator approval for the comparison.
- edge-glucose-rfu: Add an ontology-backed conversion or mark the relationship as non-comparable.

Audit digest: 6b18c9efd5c2126643baa9b6c35532fd305fc889aca712635386ff4dcc70a58b
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
{
"packetType": "knowledge-graph-measurement-harmonization-guard",
"graphId": "kg-cardio-measurement-review",
"overallStatus": "curation_required",
"edgeDecisions": [
{
"edgeId": "edge-rat-pressure-pa",
"sourceMeasurementId": "rat-pressure-mmhg",
"targetMeasurementId": "rat-pressure-pa",
"decision": "allow_comparison",
"normalizedSource": {
"normalizedValue": 15998.64,
"normalizedUnit": "Pa",
"conversion": "mmHg_to_Pa"
},
"normalizedTarget": {
"normalizedValue": 15999,
"normalizedUnit": "Pa",
"conversion": "identity"
}
},
{
"edgeId": "edge-rat-human-pressure",
"sourceMeasurementId": "rat-pressure-mmhg",
"targetMeasurementId": "human-pressure-pa",
"decision": "suppress_recommendation",
"normalizedSource": {
"normalizedValue": 15998.64,
"normalizedUnit": "Pa",
"conversion": "mmHg_to_Pa"
},
"normalizedTarget": {
"normalizedValue": 15999,
"normalizedUnit": "Pa",
"conversion": "identity"
}
},
{
"edgeId": "edge-glucose-rfu",
"sourceMeasurementId": "glucose-mgdl",
"targetMeasurementId": "glucose-rfu",
"decision": "suppress_recommendation",
"normalizedSource": {
"normalizedValue": 5.27,
"normalizedUnit": "mmol/L",
"conversion": "mg_dL_to_mmol_L"
},
"normalizedTarget": {
"normalizedValue": null,
"normalizedUnit": null,
"conversion": "unsupported"
}
},
{
"edgeId": "edge-mean-vs-median",
"sourceMeasurementId": "yield-mean",
"targetMeasurementId": "yield-median",
"decision": "suppress_recommendation",
"normalizedSource": {
"normalizedValue": 81,
"normalizedUnit": "percent",
"conversion": "identity"
},
"normalizedTarget": {
"normalizedValue": 79,
"normalizedUnit": "percent",
"conversion": "identity"
}
},
{
"edgeId": "edge-low-provenance",
"sourceMeasurementId": "rat-pressure-mmhg",
"targetMeasurementId": "rat-pressure-pa",
"decision": "suppress_recommendation",
"normalizedSource": {
"normalizedValue": 15998.64,
"normalizedUnit": "Pa",
"conversion": "mmHg_to_Pa"
},
"normalizedTarget": {
"normalizedValue": 15999,
"normalizedUnit": "Pa",
"conversion": "identity"
}
}
],
"findings": [
{
"edgeId": "edge-rat-human-pressure",
"code": "biological_context_mismatch",
"severity": "blocker",
"evidence": "Context mismatch in species.",
"curatorAction": "Split the graph edge or add a cross-context translation model before recommending reuse."
},
{
"edgeId": "edge-low-provenance",
"code": "provenance_confidence_low",
"severity": "blocker",
"evidence": "Evidence confidence 0.42 is below 0.75.",
"curatorAction": "Attach DOI, protocol, or dataset provenance before surfacing this recommendation."
},
{
"edgeId": "edge-mean-vs-median",
"code": "statistical_endpoint_mismatch",
"severity": "blocker",
"evidence": "mean cannot be compared directly with median.",
"curatorAction": "Normalize the statistical endpoint or require curator approval for the comparison."
},
{
"edgeId": "edge-glucose-rfu",
"code": "unit_not_convertible",
"severity": "blocker",
"evidence": "mg/dL cannot be safely compared with RFU for glucose_concentration.",
"curatorAction": "Add an ontology-backed conversion or mark the relationship as non-comparable."
}
],
"curatorActions": [
{
"edgeId": "edge-rat-human-pressure",
"action": "Split the graph edge or add a cross-context translation model before recommending reuse.",
"evidence": "Context mismatch in species."
},
{
"edgeId": "edge-low-provenance",
"action": "Attach DOI, protocol, or dataset provenance before surfacing this recommendation.",
"evidence": "Evidence confidence 0.42 is below 0.75."
},
{
"edgeId": "edge-mean-vs-median",
"action": "Normalize the statistical endpoint or require curator approval for the comparison.",
"evidence": "mean cannot be compared directly with median."
},
{
"edgeId": "edge-glucose-rfu",
"action": "Add an ontology-backed conversion or mark the relationship as non-comparable.",
"evidence": "mg/dL cannot be safely compared with RFU for glucose_concentration."
}
],
"jsonLd": {
"@context": "https://schema.org",
"@type": "MeasurementHarmonizationReview",
"identifier": "kg-cardio-measurement-review",
"reviewStatus": "curation_required",
"measurementCount": 7,
"edgeCount": 5
},
"auditDigest": "6b18c9efd5c2126643baa9b6c35532fd305fc889aca712635386ff4dcc70a58b"
}
72 changes: 72 additions & 0 deletions knowledge-graph-measurement-harmonization-guard/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const { buildGraphHarmonizationPacket } = require('./index');
const { sampleGraph } = require('./sample-data');

const outputDir = path.join(__dirname, 'demo-output');
fs.mkdirSync(outputDir, { recursive: true });

const packet = buildGraphHarmonizationPacket(sampleGraph);

fs.writeFileSync(path.join(outputDir, 'harmonization-packet.json'), `${JSON.stringify(packet, null, 2)}\n`);

const rows = packet.findings
.map((finding, index) => `<text x="120" y="${315 + index * 48}" font-family="Arial" font-size="23" fill="#1f2937">${index + 1}. ${finding.edgeId}: ${finding.code}</text>`)
.join('\n ');

fs.writeFileSync(path.join(outputDir, 'demo.svg'), `<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720">
<rect width="1280" height="720" fill="#0f172a"/>
<rect x="70" y="70" width="1140" height="580" rx="18" fill="#f8fafc"/>
<text x="110" y="145" font-family="Arial" font-size="43" font-weight="700" fill="#111827">Knowledge Graph Measurement Harmonization</text>
<text x="110" y="205" font-family="Arial" font-size="25" fill="#475569">${packet.graphId}</text>
<text x="110" y="275" font-family="Arial" font-size="33" font-weight="700" fill="#b91c1c">${packet.findings.length} comparable-edge blockers</text>
${rows}
<text x="110" y="620" font-family="Arial" font-size="22" fill="#475569">audit digest: ${packet.auditDigest.slice(0, 24)}...</text>
</svg>
`);

fs.writeFileSync(path.join(outputDir, 'curator-actions.md'), [
'# Measurement harmonization curator actions',
'',
`Graph: ${packet.graphId}`,
`Status: ${packet.overallStatus}`,
'',
...packet.curatorActions.map((item) => `- ${item.edgeId}: ${item.action}`),
'',
`Audit digest: ${packet.auditDigest}`,
'',
].join('\n'));

function renderMp4() {
const videoPath = path.join(outputDir, 'demo.mp4');
const font = 'C\\:/Windows/Fonts/arial.ttf';
const escapeText = (value) => String(value).replace(/\\/g, '\\\\').replace(/:/g, '\\:').replace(/'/g, "\\'");
const filters = [
`drawtext=fontfile='${font}':text='${escapeText('KG Measurement Harmonization Guard')}':x=70:y=80:fontsize=42:fontcolor=white`,
`drawtext=fontfile='${font}':text='${escapeText(`${packet.findings.length} graph edge blockers found`)}':x=70:y=155:fontsize=32:fontcolor=0xffd166`,
...packet.findings.map((finding, index) =>
`drawtext=fontfile='${font}':text='${escapeText(`${finding.edgeId}: ${finding.code}`)}':x=90:y=${235 + index * 58}:fontsize=25:fontcolor=white`,
),
`drawtext=fontfile='${font}':text='${escapeText(`audit ${packet.auditDigest.slice(0, 20)}...`)}':x=70:y=630:fontsize=24:fontcolor=0x93c5fd`,
].join(',');

execFileSync('ffmpeg', [
'-y',
'-f',
'lavfi',
'-i',
'color=c=0x0f172a:s=1280x720:d=7',
'-vf',
filters,
'-c:v',
'libx264',
'-pix_fmt',
'yuv420p',
videoPath,
], { stdio: 'inherit' });
}

renderMp4();

console.log(`Wrote demo artifacts to ${outputDir}`);
Loading