Co-Study4Grid / frontend /src /components /ComputedPairsTable.tsx
github-actions[bot]
Deploy 7688ef1
13d4e44
Raw
History Blame Contribute Delete
8.76 kB
// Copyright (c) 2025-2026, RTE (https://www.rte-france.com)
// This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
// If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
// you can obtain one at http://mozilla.org/MPL/2.0/.
// SPDX-License-Identifier: MPL-2.0
// This file is part of Co-Study4Grid a Power Grid Study tool Assistant Interface to help solve contigencies for a grid state under study.
import React from 'react';
import type { ActionDetail } from '../types';
import { colors } from '../styles/tokens';
interface SimulationFeedback {
max_rho: number | null;
max_rho_line: string;
is_rho_reduction: boolean;
is_islanded?: boolean;
disconnected_mw?: number;
non_convergence?: string | null;
}
export interface ComputedPairEntry {
id: string;
action1: string;
action2: string;
betas?: number[];
estimated_max_rho?: number | null;
estimated_max_rho_line?: string;
target_max_rho?: number | null;
target_max_rho_line?: string;
is_suspect: boolean;
isSimulated: boolean;
simulated_max_rho: number | null;
simulated_max_rho_line: string | null;
simData: ActionDetail | SimulationFeedback | null;
}
interface ComputedPairsTableProps {
computedPairsList: ComputedPairEntry[];
monitoringFactor: number;
simulating: boolean;
onSimulate: (actionId: string) => void;
displayName?: (id: string) => string;
}
const ComputedPairsTable: React.FC<ComputedPairsTableProps> = ({
computedPairsList,
monitoringFactor,
simulating,
onSimulate,
displayName = (id: string) => id,
}) => {
return (
<div style={{ maxHeight: '500px', overflowY: 'auto' }}>
<table className="action-table">
<thead style={{ position: 'sticky', top: 0, zIndex: 1 }}>
<tr>
<th>Action 1</th>
<th>Action 2</th>
<th>Betas</th>
<th>Max Loading (Est.)</th>
<th>Line (Est.)</th>
<th>Simulated Max Rho</th>
<th>Simulated Line</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{computedPairsList.length === 0 ? (
<tr>
<td colSpan={8} style={{ padding: '40px', textAlign: 'center', color: colors.textTertiary, fontStyle: 'italic' }}>
No computed combinations found.<br />Go to <strong>Explore Pairs</strong> to create new ones.
</td>
</tr>
) : computedPairsList.map(p => {
const estMaxRho = p.estimated_max_rho;
const isSimulated = p.isSimulated;
const simMaxRho = p.simulated_max_rho;
return (
<tr key={p.id}>
<td style={{ fontWeight: 'bold', fontSize: '12px' }}>{p.action1}</td>
<td style={{ fontWeight: 'bold', fontSize: '12px' }}>{p.action2}</td>
<td style={{ fontSize: '11px', fontFamily: 'monospace', color: colors.textTertiary }}>
{p.betas ? p.betas.map(b => b.toFixed(2)).join(', ') : '-'}
</td>
<td>
{estMaxRho != null && !isNaN(estMaxRho) ? (
<span className="metric-badge metric-rho" style={{
background: estMaxRho > monitoringFactor ? colors.dangerSoft : estMaxRho > (monitoringFactor - 0.05) ? colors.warningSoft : colors.successSoft,
color: estMaxRho > monitoringFactor ? colors.dangerText : estMaxRho > (monitoringFactor - 0.05) ? colors.warningText : colors.successStrong,
border: estMaxRho > monitoringFactor ? `1px solid ${colors.dangerText}` : estMaxRho > (monitoringFactor - 0.05) ? `1px solid ${colors.warningText}` : `1px dashed ${colors.successStrong}`
}}>
{(estMaxRho * 100).toFixed(1)}%
{p.is_suspect && (
<span style={{ marginLeft: '4px' }} title="Estimation suspect due to islanding">⚠️</span>
)}
</span>
) : '—'}
</td>
<td style={{ fontSize: '11px', color: colors.textTertiary, fontStyle: 'italic' }}>
{p.estimated_max_rho_line ? displayName(p.estimated_max_rho_line) : 'N/A'}
{p.target_max_rho != null && p.target_max_rho_line && p.target_max_rho_line !== 'N/A' && p.target_max_rho_line !== p.estimated_max_rho_line && (
<div style={{ fontSize: '10px', color: colors.textTertiary, marginTop: '2px', fontStyle: 'normal' }} title="Max on the lines this pair was selected to resolve">
target: {(p.target_max_rho * 100).toFixed(1)}% on {displayName(p.target_max_rho_line)}
</div>
)}
</td>
<td style={{ textAlign: 'center' }}>
{isSimulated && simMaxRho != null ? (
<span className="metric-badge metric-rho" style={{
background: simMaxRho > monitoringFactor ? colors.dangerSoft : simMaxRho > (monitoringFactor - 0.05) ? colors.warningSoft : colors.successSoft,
color: simMaxRho > monitoringFactor ? colors.dangerText : simMaxRho > (monitoringFactor - 0.05) ? colors.warningText : colors.successStrong
}}>
{(simMaxRho * 100).toFixed(1)}%
{(p.simData as ActionDetail | SimulationFeedback)?.is_islanded && (
<span style={{ fontSize: '10px', marginLeft: '4px' }} title="Islanding detected">🏝️</span>
)}
</span>
) : (
<span style={{ color: colors.textTertiary, fontSize: '11px' }}>Not simulated</span>
)}
</td>
<td style={{ fontSize: '11px', color: colors.textPrimary, fontWeight: isSimulated ? 'bold' : 'normal' }}>
{isSimulated && p.simulated_max_rho_line ? displayName(p.simulated_max_rho_line) : '-'}
</td>
<td>
<button
onClick={() => onSimulate(p.id)}
disabled={simulating}
style={{
padding: '6px 14px',
background: isSimulated ? colors.brand : colors.success,
color: colors.textOnBrand,
border: 'none',
borderRadius: '4px',
cursor: simulating ? 'not-allowed' : 'pointer',
fontSize: '11px',
fontWeight: 'bold',
opacity: simulating ? 0.6 : 1,
minWidth: '100px'
}}
>
{simulating ? '⌛' : (isSimulated ? 'Re-Simulate' : 'Simulate')}
</button>
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
};
export default ComputedPairsTable;