Skip to content
Merged
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
3 changes: 3 additions & 0 deletions frontend/features/studio/components/CqlTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { formatStatusLabel } from "@/lib/status";
import type { ApiClient } from "@/lib/api/client";
import type { MeasureDetail } from "../types";
import { compileStatusClass, formatIssue, parseCompileIssue } from "../utils";
import { SqlPreviewPanel } from "./SqlPreviewPanel";

const MonacoEditor = dynamic(() => import("@monaco-editor/react"), {
ssr: false,
Expand Down Expand Up @@ -300,6 +301,8 @@ export function CqlTab({
</div>
) : null}

<SqlPreviewPanel measure={measure} />

{showDraftCqlDialog && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/50 backdrop-blur-sm">
<div className="w-full max-w-xl rounded-3xl border border-slate-200 bg-white p-6 shadow-2xl">
Expand Down
118 changes: 118 additions & 0 deletions frontend/features/studio/components/SqlPreviewPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"use client";

import { useState } from "react";
import type { MeasureDetail } from "../types";

type Props = {
measure: MeasureDetail;
};

function buildSql(measure: MeasureDetail): string {
const {
policyRef,
eligibilityCriteria,
exclusions,
complianceWindow,
requiredDataElements,
} = measure;

// Only parse as days when the string explicitly says "N days" / "N day".
// Strings like "Series of 3 doses over 6 months" must not be treated as a day count.
const windowMatch = complianceWindow?.match(/(\d+)\s*days?\b/i);
const windowDays = windowMatch ? parseInt(windowMatch[1], 10) : null;
const dueSoonDays = windowDays ? windowDays - 30 : null;

const excl = exclusions?.[0];
const exclSlug = excl?.criteriaText
? excl.criteriaText.toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_]/g, "")
: null;
const exclLine = exclSlug
? ` WHEN ep.${exclSlug} = TRUE THEN 'EXCLUDED' -- ${excl!.label}`
: ` -- (no exclusion criteria defined in Spec)`;

const dataComment =
(requiredDataElements ?? []).length > 0
? ` -- required: ${requiredDataElements.join(", ")}`
: "";

const enrollComment = eligibilityCriteria?.programEnrollmentText
? `\n -- eligibility: ${eligibilityCriteria.programEnrollmentText}`
: "";

const roleClause = eligibilityCriteria?.roleFilter
? ` AND e.role = '${eligibilityCriteria.roleFilter}'`
: ` -- role: unrestricted`;

const siteClause = eligibilityCriteria?.siteFilter
? ` AND e.site = '${eligibilityCriteria.siteFilter}'`
: ` -- site: all sites`;

const overdueExpr =
windowDays !== null
? `NOW() - MAX(exam.date) > INTERVAL '${windowDays} days'`
: `/* window: ${complianceWindow ?? "see CQL"} */`;

const dueSoonExpr =
dueSoonDays !== null
? `NOW() - MAX(exam.date) > INTERVAL '${dueSoonDays} days'`
: `/* DUE_SOON: see CQL */`;

return `-- Illustrative analogy only — CQL is the compliance source of truth
-- Policy: ${policyRef ?? "see Spec tab"}
SELECT
e.id,
e.name,
e.role,
e.site,
MAX(exam.date) AS last_exam_date${dataComment ? ",\n" + dataComment : ""}
CASE
${exclLine}
WHEN MAX(exam.date) IS NULL THEN 'MISSING_DATA'
WHEN ${overdueExpr} THEN 'OVERDUE'
WHEN ${dueSoonExpr} THEN 'DUE_SOON'
-- DUE_SOON threshold approximate; see CQL for exact window
ELSE 'COMPLIANT'
END AS outcome_status
FROM employees e
JOIN employee_programs ep ON ep.employee_id = e.id${enrollComment}
LEFT JOIN exams exam ON exam.employee_id = e.id
WHERE e.active = TRUE
${roleClause}
${siteClause}
GROUP BY e.id, e.name, e.role, e.site, ep.exclusion_flag`;
}

export function SqlPreviewPanel({ measure }: Props) {
const [open, setOpen] = useState(false);
const sql = buildSql(measure);

return (
<div className="rounded border border-slate-200 bg-white">
<button
type="button"
onClick={() => setOpen((prev) => !prev)}
className="flex w-full items-center gap-2 px-4 py-2 text-left text-xs font-semibold text-slate-700 hover:bg-slate-50"
aria-expanded={open}
>
<span className="select-none text-[10px]">{open ? "▼" : "▶"}</span>
SQL Analogy
<span className="ml-1 text-[10px] font-normal text-slate-400">(derived from Spec — illustrative only)</span>
</button>

{open && (
<div className="border-t border-slate-200">
<div className="flex items-start gap-2 rounded-none border-b border-amber-200 bg-amber-50 px-4 py-2 text-xs text-amber-800">
<span className="mt-0.5 shrink-0 font-semibold uppercase tracking-wider text-amber-700">Illustrative only</span>
<span>Not executed. CQL is the compliance source of truth. Column names and table structure are analogical.</span>
</div>
<pre
className="overflow-x-auto rounded-b bg-slate-950 p-4 font-mono text-xs leading-5 text-slate-200"
data-testid="sql-preview-block"
>
{sql}
</pre>
</div>
)}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { SqlPreviewPanel } from "../SqlPreviewPanel";
import type { MeasureDetail } from "../../types";

const sampleMeasure: MeasureDetail = {
id: "m1",
name: "Annual Audiogram Completed",
policyRef: "OSHA 29 CFR 1910.95",
oshaReferenceId: null,
version: "1.0",
status: "Active",
owner: "Safety",
description: "Annual audiogram for employees in hearing conservation.",
eligibilityCriteria: {
roleFilter: "Safety Technician",
siteFilter: "Plant A",
programEnrollmentText: "In Hearing Conservation Program",
},
exclusions: [{ label: "Active Waiver", criteriaText: "Has Active Waiver" }],
complianceWindow: "365 days",
requiredDataElements: ["Audiogram Date", "Waiver Status"],
cqlText: "",
compileStatus: "COMPILED",
valueSets: [],
testFixtures: [],
};

describe("SqlPreviewPanel", () => {
it("renders a collapsed toggle button by default", () => {
render(<SqlPreviewPanel measure={sampleMeasure} />);
expect(screen.getByRole("button", { name: /SQL Analogy/i })).toBeInTheDocument();
expect(screen.queryByTestId("sql-preview-block")).toBeNull();
});

it("expands and shows the illustrative-only banner on click", () => {
render(<SqlPreviewPanel measure={sampleMeasure} />);
fireEvent.click(screen.getByRole("button", { name: /SQL Analogy/i }));
// The amber banner span uses exact text "Illustrative only"
expect(screen.getAllByText(/Illustrative only/i).length).toBeGreaterThanOrEqual(1);
expect(screen.getByText(/Not executed\. CQL is the compliance source of truth/i)).toBeInTheDocument();
});

it("shows policy ref and compliance window in the SQL block", () => {
render(<SqlPreviewPanel measure={sampleMeasure} />);
fireEvent.click(screen.getByRole("button", { name: /SQL Analogy/i }));
const block = screen.getByTestId("sql-preview-block");
expect(block.textContent).toContain("OSHA 29 CFR 1910.95");
expect(block.textContent).toContain("365 days");
expect(block.textContent).toContain("335 days");
});

it("includes role and site filters from spec", () => {
render(<SqlPreviewPanel measure={sampleMeasure} />);
fireEvent.click(screen.getByRole("button", { name: /SQL Analogy/i }));
const block = screen.getByTestId("sql-preview-block");
expect(block.textContent).toContain("Safety Technician");
expect(block.textContent).toContain("Plant A");
});

it("includes the approximate DUE_SOON comment", () => {
render(<SqlPreviewPanel measure={sampleMeasure} />);
fireEvent.click(screen.getByRole("button", { name: /SQL Analogy/i }));
const block = screen.getByTestId("sql-preview-block");
expect(block.textContent).toContain("DUE_SOON threshold approximate; see CQL for exact window");
});

it("collapses again on second click", () => {
render(<SqlPreviewPanel measure={sampleMeasure} />);
fireEvent.click(screen.getByRole("button", { name: /SQL Analogy/i }));
fireEvent.click(screen.getByRole("button", { name: /SQL Analogy/i }));
expect(screen.queryByTestId("sql-preview-block")).toBeNull();
});

it("renders fallback text when compliance window has no numeric value", () => {
const noWindow: MeasureDetail = { ...sampleMeasure, complianceWindow: "see policy" };
render(<SqlPreviewPanel measure={noWindow} />);
fireEvent.click(screen.getByRole("button", { name: /SQL Analogy/i }));
const block = screen.getByTestId("sql-preview-block");
expect(block.textContent).toContain("see policy");
});

it("uses raw-string fallback for multi-number non-day windows like 'Series of 3 doses over 6 months'", () => {
const doseWindow: MeasureDetail = { ...sampleMeasure, complianceWindow: "Series of 3 doses over 6 months" };
render(<SqlPreviewPanel measure={doseWindow} />);
fireEvent.click(screen.getByRole("button", { name: /SQL Analogy/i }));
const block = screen.getByTestId("sql-preview-block");
// Should pass the window through as a comment, not parse '3' as a day count
expect(block.textContent).toContain("Series of 3 doses over 6 months");
expect(block.textContent).not.toContain("INTERVAL '3 days'");
});
});
Loading