feat(claim-drawer): validation rule opens peek with rule catalog
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
interface Props {
|
||||
/** The rule code (e.g. "R050_diagnosis_present" or just "R050"). */
|
||||
rule: string;
|
||||
}
|
||||
|
||||
interface RuleDoc {
|
||||
/** Short human title (e.g. "Diagnosis pointer present"). */
|
||||
title: string;
|
||||
/** Plain-English description of what the rule checks. */
|
||||
description: string;
|
||||
/** Why the rule matters — operator-facing rationale. */
|
||||
whyItMatters: string;
|
||||
/** How to fix — short, actionable. */
|
||||
howToFix: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* SP21 Phase 5 Task 5.9: rule catalog used by ValidationRulePeekContent.
|
||||
*
|
||||
* The catalog is intentionally small — it covers the rules we actually
|
||||
* emit today (R050_diagnosis_present, R200_units_recommended). For
|
||||
* anything not in the catalog the peek still renders (with an "Unknown
|
||||
* rule" note) — operators should still be able to open the peek for
|
||||
* any rule code so they can see the originating message verbatim.
|
||||
*
|
||||
* Adding a new entry here is the source-of-truth for the rule's
|
||||
* documentation. The ValidationPanel wires the peek by rule code; if
|
||||
* we add new rules later (Phase 6+), add a new entry here.
|
||||
*/
|
||||
const RULE_CATALOG: Record<string, RuleDoc> = {
|
||||
R050_diagnosis_present: {
|
||||
title: "Diagnosis pointer present",
|
||||
description:
|
||||
"Each service line must point to at least one diagnosis code in the claim header (the HL segment's HI element). A missing pointer makes the line unprocessable on the payer side.",
|
||||
whyItMatters:
|
||||
"Payers reject claims with missing diagnosis pointers at the 999 stage, which would otherwise re-trigger the 999 rejection loop. Catching it here gives the operator a chance to attach the dx before submission.",
|
||||
howToFix:
|
||||
"Open the claim's Service Lines table and attach the relevant diagnosis code (e.g. E11.9) to the line. The pointer is the line's diagnosis pointer list.",
|
||||
},
|
||||
R200_units_recommended: {
|
||||
title: "Service line units recommended",
|
||||
description:
|
||||
"Service lines that represent timed procedures (anesthesia, critical care, psychotherapy time-based codes) should carry an explicit units value. Defaulting to 1 is acceptable for most codes but flagged here for review.",
|
||||
whyItMatters:
|
||||
"Timed codes without units get under-reimbursed — payers default to 1 unit when the field is blank, even when the procedure took 45 minutes. The warning exists so an operator can verify the units are correct before submission.",
|
||||
howToFix:
|
||||
"Confirm the units value on the service line matches the documented encounter time. If the code is not time-based, no action is required.",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Peek body for a validation rule — opens on top of the ClaimDrawer
|
||||
* via PeekModal when the operator clicks a rule code in the
|
||||
* ValidationPanel. The body shows the rule's title, description, why
|
||||
* it matters, and how to fix it.
|
||||
*
|
||||
* Unknown rules (codes not in the catalog) render a small "Unknown
|
||||
* rule — see originating message" note rather than blowing up. The
|
||||
* peek still renders so the operator can correlate the rule code to
|
||||
* whatever they were just looking at.
|
||||
*
|
||||
* No fetch — the catalog is static and bundled. (A future phase
|
||||
* could swap this for a backend-served catalog if rules become
|
||||
* user-extensible.)
|
||||
*/
|
||||
export function ValidationRulePeekContent({ rule }: Props) {
|
||||
// Normalize: the rule code in the validation payload is the full
|
||||
// form (`R050_diagnosis_present`), but a future backend response
|
||||
// might use the short form (`R050`). Look up both.
|
||||
const doc =
|
||||
RULE_CATALOG[rule] ??
|
||||
RULE_CATALOG[rule.split("_")[0] ?? ""] ??
|
||||
null;
|
||||
|
||||
if (!doc) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="display text-[14px] text-foreground">
|
||||
{rule}
|
||||
</div>
|
||||
<div
|
||||
className="text-[12.5px]"
|
||||
style={{ color: "hsl(var(--muted-foreground))" }}
|
||||
>
|
||||
Unknown rule. The originating message is the authoritative
|
||||
description — this peek is a no-op for undocumented rule codes.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="display text-[15px] text-foreground">
|
||||
{doc.title}
|
||||
</div>
|
||||
<div
|
||||
className="mono text-[11px]"
|
||||
style={{ color: "hsl(var(--muted-foreground))" }}
|
||||
>
|
||||
{rule}
|
||||
</div>
|
||||
<div className="text-[13px] leading-relaxed text-[color:var(--m-ink-primary)]">
|
||||
{doc.description}
|
||||
</div>
|
||||
<div className="space-y-1.5 pt-1">
|
||||
<Section heading="Why it matters">{doc.whyItMatters}</Section>
|
||||
<Section heading="How to fix">{doc.howToFix}</Section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Section({
|
||||
heading,
|
||||
children,
|
||||
}: {
|
||||
heading: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="mono text-[10px] uppercase tracking-[0.18em] font-semibold mb-0.5"
|
||||
style={{ color: "hsl(var(--muted-foreground))" }}
|
||||
>
|
||||
{heading}
|
||||
</div>
|
||||
<div className="text-[12.5px] leading-relaxed text-[color:var(--m-ink-secondary)]">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user