continue refnum threshold rule checking

This commit is contained in:
faiztyanirh 2026-03-27 16:51:43 +07:00
parent 0f4dd0d522
commit b546db4a01
3 changed files with 98 additions and 90 deletions

View File

@ -146,6 +146,13 @@ export const refNumSchema = z
path: ['LowSign']
});
}
if (data.LowSign && !['>', '>=', '='].includes(data.LowSign)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Low sign must be =, > or >=',
path: ['LowSign'],
});
}
if (data.High && !data.HighSign) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
@ -153,6 +160,13 @@ export const refNumSchema = z
path: ['HighSign']
});
}
if (data.HighSign && !['<', '<=', '='].includes(data.HighSign)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'High sign must be =, < or <=',
path: ['HighSign'],
});
}
if (data.LowSign && data.HighSign && data.LowSign === data.HighSign) {
ctx.addIssue({
code: z.ZodIssueCode.custom,

View File

@ -432,6 +432,16 @@
refTxtState.form.TxtRefType = value;
}
}
$effect(() => {
const refType = formState.form.RefType;
if (refType === 'RANGE' || refType === 'THOLD') {
refNumState.form.NumRefType = refType;
}
if (refType === 'TEXT' || refType === 'VSET') {
refTxtState.form.TxtRefType = refType;
}
});
</script>
<FormPageContainer title="Edit Test" {primaryAction} {secondaryActions}>

View File

@ -41,7 +41,7 @@
return false;
});
// $inspect(props.refNumState.form.NumRefType)
function snapshotForm() {
const f = props.refNumState.form;
return {
@ -91,10 +91,6 @@
row.RangeType === form.RangeType
);
console.log('peers:', JSON.stringify(peers));
console.log('newLow:', form.Low, 'newLowSign:', form.LowSign);
console.log('newHigh:', form.High, 'newHighSign:', form.HighSign);
if (peers.length === 0) return null;
const newLow = form.Low !== '' && form.Low != null ? Number(form.Low) : null;
@ -102,46 +98,36 @@
const newLowSign = form.LowSign ?? '';
const newHighSign = form.HighSign ?? '';
const excludeIds = new Set();
// Cari row yang high-nya paling dekat di bawah newLow
const closestBelow = peers
.filter(r => r.High !== '' && r.High != null)
.map(r => ({ ...r, highNum: Number(r.High) }))
.filter(r => r.highNum <= newLow)
.sort((a, b) => b.highNum - a.highNum)[0];
if (closestBelow) {
excludeIds.add(closestBelow.id)
}
const closestAbove = peers
.filter(r => r.Low !== '' && r.Low != null)
.filter(r => !excludeIds.has(r.id))
.map(r => ({ ...r, lowNum: Number(r.Low) }))
.filter(r => r.lowNum <= newHigh)
.sort((a, b) => a.lowNum - b.lowNum)[0];
if (closestBelow) {
const prevHigh = closestBelow.highNum;
const prevHighSign = closestBelow.HighSign ?? '';
if (prevHigh !== newLow) {
// Ada celah
if (newLow > prevHigh + 1) {
return { field: 'Low', message: `Gap between intervals. Previous interval ends at ${prevHigh}, new interval starts at ${newLow}` };
}
}
for (const row of peers) {
const rowHigh = row.High !== '' && row.High != null ? Number(row.High) : null;
const rowLow = row.Low !== '' && row.Low != null ? Number(row.Low) : null;
const rowHighSign = row.HighSign ?? '';
const rowLowSign = row.LowSign ?? '';
console.log('rowHigh:', rowHigh, typeof rowHigh);
console.log('newLow:', newLow, typeof newLow);
console.log('equal?:', rowHigh === newLow);
// No. 4 & 6 — tanpa celah & tanpa sign kontradiktif
// Cek apakah interval baru harus dimulai tepat setelah interval sebelumnya berakhir
if (rowHigh != null && newLow != null && rowHigh === newLow) {
console.log('boundary check:', rowHighSign, newLowSign);
// rowHighSign <= 200, newLowSign harus > 200
// rowHighSign < 200, newLowSign harus >= 200
const prevEndInclusive = rowHighSign === '<=';
if (prevHigh === newLow) {
const prevEndInclusive = prevHighSign === '<=';
const newStartInclusive = newLowSign === '>=';
console.log('prevEndInclusive:', prevEndInclusive, 'newStartInclusive:', newStartInclusive);
if (prevEndInclusive && newStartInclusive) {
return { field: 'LowSign', message: 'Sign contradicts previous interval (overlap at boundary). Use > instead of >=' };
}
@ -149,68 +135,45 @@
return { field: 'LowSign', message: 'Gap between intervals. Use >= instead of >' };
}
}
if (newLow < prevHigh) {
return { field: 'Low', message: 'This interval overlaps with an existing interval' };
}
}
if (rowLow != null && newHigh != null && rowLow === newHigh) {
const prevStartInclusive = rowLowSign === '>=';
if (closestAbove) {
const nextLow = closestAbove.lowNum;
const nextLowSign = closestAbove.LowSign ?? '';
if (newHigh < nextLow - 1) {
return { field: 'High', message: `Gap between intervals. Next interval starts at ${nextLow}, new interval ends at ${newHigh}` };
}
if (nextLow === newHigh) {
const nextStartInclusive = nextLowSign === '>=';
const newEndInclusive = newHighSign === '<=';
if (prevStartInclusive && newEndInclusive) {
if (nextStartInclusive && newEndInclusive) {
return { field: 'HighSign', message: 'Sign contradicts next interval (overlap at boundary). Use < instead of <=' };
}
if (!prevStartInclusive && !newEndInclusive) {
if (!nextStartInclusive && !newEndInclusive) {
return { field: 'HighSign', message: 'Gap between intervals. Use <= instead of <' };
}
}
// No. 5 — overlap: cek apakah ada nilai yang bisa memenuhi kedua interval
// Contoh: row adalah "< 100", new adalah "> 90" → overlap di 9199
if (rowHigh != null && newLow != null) {
const prevEnd = rowHigh;
const newStart = newLow;
const prevInclusive = rowHighSign === '<=';
const newInclusive = newLowSign === '>=';
const isOverlap =
newStart < prevEnd ||
(newStart === prevEnd && prevInclusive && newInclusive);
if (isOverlap) {
return { field: 'Low', message: 'This interval overlaps with an existing interval' };
}
if (newHigh > nextLow) {
return { field: 'High', message: 'This interval overlaps with an existing interval' };
}
if (rowLow != null && newHigh != null) {
const prevStart = rowLow;
const newEnd = newHigh;
const prevInclusive = rowLowSign === '>=';
const newInclusive = newHighSign === '<=';
const isOverlap =
newEnd > prevStart ||
(newEnd === prevStart && prevInclusive && newInclusive);
if (isOverlap) {
return { field: 'High', message: 'This interval overlaps with an existing interval' };
}
}
}
return null;
}
// $inspect(tempNumeric)
function handleInsert() {
const newStart = toDays(props.refNumState.form.AgeStart);
const newEnd = toDays(props.refNumState.form.AgeEnd);
const isOverlap = tempNumeric.some((row) => {
if (row.SpcType !== props.refNumState.form.SpcType) return false;
if (row.Sex !== props.refNumState.form.Sex) return false;
if (row.NumRefType !== props.refNumState.form.NumRefType) return false;
if (row.RangeType !== props.refNumState.form.RangeType) return false;
const isOverlap = props.refNumState.form.NumRefType === 'THOLD' ? false : tempNumeric.some((row) => {
if ((row.SpcType ?? '') !== (props.refNumState.form.SpcType ?? '')) return false;
if ((row.Sex ?? '') !== (props.refNumState.form.Sex ?? '')) return false;
if ((row.NumRefType ?? '') !== (props.refNumState.form.NumRefType ?? '')) return false;
if ((row.RangeType ?? '') !== (props.refNumState.form.RangeType ?? '')) return false;
if (row.id === editingId) return false;
@ -222,12 +185,13 @@
return (newStart <= existingEnd && newEnd >= existingStart);
});
console.log(`isoverlap: ${isOverlap}`)
if (isOverlap) {
props.refNumState.errors.AgeEnd = 'Age range overlaps with existing data';
return;
}
const tholdError = validateTholdContinuity();
const tholdError = validateTholdContinuity(editingId);
if (tholdError) {
props.refNumState.errors[tholdError.field] = tholdError.message;
return;
@ -276,6 +240,37 @@
}
function handleUpdate() {
const newStart = toDays(props.refNumState.form.AgeStart);
const newEnd = toDays(props.refNumState.form.AgeEnd);
const isOverlap = props.refNumState.form.NumRefType === 'THOLD' ? false : tempNumeric.some((row) => {
if ((row.SpcType ?? '') !== (props.refNumState.form.SpcType ?? '')) return false;
if ((row.Sex ?? '') !== (props.refNumState.form.Sex ?? '')) return false;
if ((row.NumRefType ?? '') !== (props.refNumState.form.NumRefType ?? '')) return false;
if ((row.RangeType ?? '') !== (props.refNumState.form.RangeType ?? '')) return false;
if (row.id === editingId) return false;
const existingStart = toDays(row.AgeStart);
const existingEnd = toDays(row.AgeEnd);
if (existingStart == null || existingEnd == null) return false;
return (newStart <= existingEnd && newEnd >= existingStart);
});
console.log(`isoverlap: ${isOverlap}`)
if (isOverlap) {
props.refNumState.errors.AgeEnd = 'Age range overlaps with existing data';
return;
}
const tholdError = validateTholdContinuity(editingId);
if (tholdError) {
props.refNumState.errors[tholdError.field] = tholdError.message;
return;
}
tempNumeric = tempNumeric.map((row) =>
row.id === editingId ? { id: row.id, ...snapshotForm() } : row
);
@ -396,7 +391,7 @@
Cancel
</Button>
{:else}
<Button size="sm" class="cursor-pointer" onclick={handleInsert}>Insert</Button>
<Button size="sm" class="cursor-pointer" onclick={handleInsert} disabled={Object.values(props.refNumState.errors).some(error => error !== null)}>Insert</Button>
{/if}
</div>
</div>
@ -439,17 +434,6 @@
-
{/if}
</Table.Cell>
<!-- <Table.Cell class="font-medium flex justify-between">
<div>
{row.LowSign ? row.LowSign : ''}
{row.Low || 'null'} &ndash;
{row.HighSign ? row.HighSign : ''}
{row.High || 'null'}
</div>
<Badge variant="outline" class="border-dashed border-primary border-2"
>{numRefTypeBadge(row.NumRefType)}</Badge
>
</Table.Cell> -->
<Table.Cell class="font-medium flex justify-between">
<div>
{#if row.Low && row.High}