Backup
This commit is contained in:
2
fix2.md
2
fix2.md
@@ -39,4 +39,4 @@ Truncate ReasonEntry (kills 99% of the insanity).
|
|||||||
Fix "Ayer" to be calendar-based.
|
Fix "Ayer" to be calendar-based.
|
||||||
Fix KPI row label to reflect selected range.
|
Fix KPI row label to reflect selected range.
|
||||||
If no OrgShift rows exist, show a toast or disable "Turno actual" button instead of silently falling back.
|
If no OrgShift rows exist, show a toast or disable "Turno actual" button instead of silently falling back.
|
||||||
Improve dual-banner priority (offline > mold-change).
|
Improve dual-banner priority (offline > mold-change)
|
||||||
54
fix3.md
Normal file
54
fix3.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
The fix created the production segment, but the mold-change active event never got a matching resolved event in CT. Two checks will tell us which.
|
||||||
|
|
||||||
|
On CT Postgres
|
||||||
|
SELECT ts, data->>'status' AS status,
|
||||||
|
data->>'incidentKey' AS ikey,
|
||||||
|
data->>'is_update' AS is_update
|
||||||
|
FROM "MachineEvent"
|
||||||
|
WHERE "machineId" = '<uuid-M4-5>'
|
||||||
|
AND "eventType" = 'mold-change'
|
||||||
|
ORDER BY ts DESC LIMIT 10;
|
||||||
|
Expected when working: active + resolved rows with same incidentKey.
|
||||||
|
|
||||||
|
If only active exists → resolved event never reached CT. Likely causes:
|
||||||
|
|
||||||
|
Flow wasn't redeployed after the edit (Node-RED still running old version — check if node.warn in auto-close is firing in debug sidebar).
|
||||||
|
state.moldChange persisted stale from before (cleared active manually somewhere).
|
||||||
|
User hit COMENZAR before deploying the updated flow → no close event ever emitted for that episode.
|
||||||
|
If both exist but incidentKey differs → my close event and start event used different startMs. Send me both rows and I'll trace.
|
||||||
|
|
||||||
|
Manual cleanup for the stuck episode
|
||||||
|
Until a new resolved event arrives, the banner won't clear. Force it:
|
||||||
|
|
||||||
|
-- Insert a synthetic resolved event matching the stuck active one
|
||||||
|
INSERT INTO "MachineEvent" (id, "orgId", "machineId", ts, topic, "eventType", severity, "requiresAck", title, description, data, "createdAt")
|
||||||
|
SELECT gen_random_uuid(), "orgId", "machineId", NOW(), 'mold-change', 'mold-change', 'info', false,
|
||||||
|
'Cambio de molde cerrado manualmente', 'cierre manual',
|
||||||
|
jsonb_build_object(
|
||||||
|
'status','resolved',
|
||||||
|
'incidentKey', data->>'incidentKey',
|
||||||
|
'start_ms', (data->>'start_ms')::bigint,
|
||||||
|
'end_ms', extract(epoch from NOW())*1000
|
||||||
|
),
|
||||||
|
NOW()
|
||||||
|
FROM "MachineEvent"
|
||||||
|
WHERE "machineId" = '<uuid-M4-5>' AND "eventType" = 'mold-change' AND data->>'status' = 'active'
|
||||||
|
ORDER BY ts DESC LIMIT 1;
|
||||||
|
Banner disappears on next recap refresh (cache 60s).
|
||||||
|
|
||||||
|
Permanent safeguard (CT)
|
||||||
|
In lib/recap/getRecapData.ts ~line 817, add a freshness cap: an "active" mold-change older than 12h is almost always stuck data. Treat as resolved:
|
||||||
|
|
||||||
|
const STALE_ACTIVE_MS = 12 * 60 * 60 * 1000;
|
||||||
|
for (const event of machineMoldEvents) {
|
||||||
|
const key = eventIncidentKey(event.data, "mold-change", event.ts);
|
||||||
|
const status = eventStatus(event.data);
|
||||||
|
if (status === "resolved") { moldActiveByIncident.delete(key); continue; }
|
||||||
|
if (status === "active" || !status) {
|
||||||
|
// ignore if too old to be real
|
||||||
|
if (params.end.getTime() - event.ts.getTime() > STALE_ACTIVE_MS) continue;
|
||||||
|
moldActiveByIncident.set(key, moldStartMs(event.data, event.ts));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Same for the timeline extension logic in lib/recap/timeline.ts line 662 — cap isFreshActive at the same threshold.
|
||||||
|
|
||||||
1
flows (63).json
Normal file
1
flows (63).json
Normal file
File diff suppressed because one or more lines are too long
@@ -27,6 +27,7 @@ const STOP_TYPES = new Set(["microstop", "macrostop"]);
|
|||||||
const STOP_STATUS = new Set(["STOP", "DOWN", "OFFLINE"]);
|
const STOP_STATUS = new Set(["STOP", "DOWN", "OFFLINE"]);
|
||||||
const CACHE_TTL_SEC = 60;
|
const CACHE_TTL_SEC = 60;
|
||||||
const MOLD_LOOKBACK_MS = 14 * 24 * 60 * 60 * 1000;
|
const MOLD_LOOKBACK_MS = 14 * 24 * 60 * 60 * 1000;
|
||||||
|
const MOLD_ACTIVE_STALE_MS = 12 * 60 * 60 * 1000;
|
||||||
|
|
||||||
function safeNum(value: unknown) {
|
function safeNum(value: unknown) {
|
||||||
if (typeof value === "number" && Number.isFinite(value)) return value;
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
||||||
@@ -822,6 +823,7 @@ async function computeRecap(params: Required<Pick<RecapQuery, "orgId">> & {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (status === "active" || !status) {
|
if (status === "active" || !status) {
|
||||||
|
if (params.end.getTime() - event.ts.getTime() > MOLD_ACTIVE_STALE_MS) continue;
|
||||||
moldActiveByIncident.set(key, moldStartMs(event.data, event.ts));
|
moldActiveByIncident.set(key, moldStartMs(event.data, event.ts));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { RecapTimelineSegment } from "@/lib/recap/types";
|
import type { RecapTimelineSegment } from "@/lib/recap/types";
|
||||||
|
|
||||||
const ACTIVE_STALE_MS = 2 * 60 * 1000;
|
const ACTIVE_STALE_MS = 2 * 60 * 1000;
|
||||||
|
const MOLD_ACTIVE_STALE_MS = 12 * 60 * 60 * 1000;
|
||||||
const MERGE_GAP_MS = 30 * 1000;
|
const MERGE_GAP_MS = 30 * 1000;
|
||||||
const MICRO_CLUSTER_GAP_MS = 60 * 1000;
|
const MICRO_CLUSTER_GAP_MS = 60 * 1000;
|
||||||
const ABSORB_SHORT_SEGMENT_MS = 30 * 1000;
|
const ABSORB_SHORT_SEGMENT_MS = 30 * 1000;
|
||||||
@@ -584,6 +585,21 @@ export function buildTimelineSegments(input: {
|
|||||||
}
|
}
|
||||||
if (currentProduction) rawSegments.push(currentProduction);
|
if (currentProduction) rawSegments.push(currentProduction);
|
||||||
|
|
||||||
|
// If production evidence appears after a mold-change "active" event, we cap that
|
||||||
|
// mold-change segment at the first production timestamp to avoid stale overwrite.
|
||||||
|
const productionWindows = rawSegments
|
||||||
|
.filter((segment): segment is Extract<RawSegment, { type: "production" }> => segment.type === "production")
|
||||||
|
.map((segment) => ({ startMs: segment.startMs, endMs: segment.endMs }))
|
||||||
|
.sort((a, b) => a.startMs - b.startMs || a.endMs - b.endMs);
|
||||||
|
|
||||||
|
const firstProductionMsAfter = (startMs: number) => {
|
||||||
|
for (const window of productionWindows) {
|
||||||
|
if (window.endMs <= startMs) continue;
|
||||||
|
return Math.max(startMs, window.startMs);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
const eventEpisodes = new Map<
|
const eventEpisodes = new Map<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
@@ -661,8 +677,16 @@ export function buildTimelineSegments(input: {
|
|||||||
let endMs = Math.trunc(episode.endMs ?? episode.lastTsMs);
|
let endMs = Math.trunc(episode.endMs ?? episode.lastTsMs);
|
||||||
|
|
||||||
if (episode.statusActive && !episode.statusResolved) {
|
if (episode.statusActive && !episode.statusResolved) {
|
||||||
const isFreshActive = rangeEndMs - episode.lastTsMs <= ACTIVE_STALE_MS;
|
const activeStaleMs = episode.type === "mold-change" ? MOLD_ACTIVE_STALE_MS : ACTIVE_STALE_MS;
|
||||||
|
const isFreshActive = rangeEndMs - episode.lastTsMs <= activeStaleMs;
|
||||||
endMs = isFreshActive ? rangeEndMs : episode.lastTsMs;
|
endMs = isFreshActive ? rangeEndMs : episode.lastTsMs;
|
||||||
|
|
||||||
|
if (episode.type === "mold-change") {
|
||||||
|
const productionResumeMs = firstProductionMsAfter(startMs);
|
||||||
|
if (productionResumeMs != null) {
|
||||||
|
endMs = Math.min(endMs, productionResumeMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (endMs <= startMs && episode.durationSec != null && episode.durationSec > 0) {
|
} else if (endMs <= startMs && episode.durationSec != null && episode.durationSec > 0) {
|
||||||
endMs = startMs + episode.durationSec * 1000;
|
endMs = startMs + episode.durationSec * 1000;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user