Files
AIclinicalresearch/frontend-v2/src/modules/asl/components/charting/BaselineTable.tsx
HaHafeng 205932bb3f feat(asl): Complete Tool 4 SR Chart Generator and Tool 5 Meta Analysis Engine
Tool 4 - SR Chart Generator:
- PRISMA 2020 flow diagram with Chinese/English toggle (SVG)
- Baseline characteristics table (Table 1)
- Dual data source: project pipeline API + Excel upload
- SVG/PNG export support
- Backend: ChartingService with Prisma aggregation
- Frontend: PrismaFlowDiagram, BaselineTable, DataSourceSelector

Tool 5 - Meta Analysis Engine:
- 3 data types: HR (metagen), dichotomous (metabin), continuous (metacont)
- Random and fixed effects models
- Multiple effect measures: HR / OR / RR
- Forest plot + funnel plot (base64 PNG from R)
- Heterogeneity statistics: I2, Q, p-value, Tau2
- Data input via Excel upload or project pipeline
- R Docker image updated with meta package (13 tools total)
- E2E test: 36/36 passed
- Key fix: exp() back-transformation for log-scale ratio measures

Also includes:
- IIT CRA Agent V3.0 routing and AI chat page integration
- Updated ASL module status guide (v2.3)
- Updated system status guide (v6.3)
- Updated R statistics engine guide (v1.4)

Tested: Frontend renders correctly, backend APIs functional, E2E tests passed
Made-with: Cursor
2026-02-26 21:51:02 +08:00

111 lines
2.8 KiB
TypeScript

import React, { useMemo } from 'react';
import { Table, Typography, Empty } from 'antd';
import type { BaselineRow } from '../../utils/chartingExcelUtils';
const { Title } = Typography;
interface Props {
data: BaselineRow[];
}
const KNOWN_FIELD_LABELS: Record<string, string> = {
Study_ID: 'Study ID',
study_id: 'Study ID',
Intervention_Name: 'Intervention',
intervention_name: 'Intervention',
Control_Name: 'Control',
control_name: 'Control',
Intervention_N: 'Intervention N',
intervention_n: 'Intervention N',
Control_N: 'Control N',
control_n: 'Control N',
Age_Mean_SD: 'Age (Mean ± SD)',
age_mean_sd: 'Age (Mean ± SD)',
Male_Percent: 'Male %',
male_percent: 'Male %',
study_design: 'Study Design',
Study_Design: 'Study Design',
};
function humanize(key: string): string {
if (KNOWN_FIELD_LABELS[key]) return KNOWN_FIELD_LABELS[key];
return key
.replace(/_/g, ' ')
.replace(/\b\w/g, (c) => c.toUpperCase());
}
const BaselineTable: React.FC<Props> = ({ data }) => {
const columns = useMemo(() => {
if (data.length === 0) return [];
const allKeys = new Set<string>();
data.forEach((row) => {
Object.keys(row).forEach((k) => allKeys.add(k));
});
const priorityOrder = [
'Study_ID', 'study_id',
'Intervention_Name', 'intervention_name',
'Control_Name', 'control_name',
'Intervention_N', 'intervention_n',
'Control_N', 'control_n',
'Age_Mean_SD', 'age_mean_sd',
'Male_Percent', 'male_percent',
];
const orderedKeys: string[] = [];
const seen = new Set<string>();
for (const pk of priorityOrder) {
if (allKeys.has(pk) && !seen.has(pk)) {
orderedKeys.push(pk);
seen.add(pk);
}
}
for (const k of allKeys) {
if (!seen.has(k)) {
orderedKeys.push(k);
seen.add(k);
}
}
return orderedKeys.map((key) => ({
title: humanize(key),
dataIndex: key,
key,
ellipsis: true,
render: (val: any) => {
if (val === null || val === undefined) return '-';
if (typeof val === 'object') return JSON.stringify(val);
return String(val);
},
}));
}, [data]);
if (data.length === 0) {
return <Empty description="暂无基线数据" />;
}
const dataSource = data.map((row, i) => ({ ...row, _rowKey: `row-${i}` }));
return (
<div className="w-full">
<Title level={5} className="text-center mb-4">
Table 1. 线 (Baseline Characteristics of Included Studies)
</Title>
<Table
columns={columns}
dataSource={dataSource}
rowKey="_rowKey"
pagination={false}
bordered
size="small"
scroll={{ x: 'max-content' }}
className="baseline-academic-table"
/>
</div>
);
};
export default BaselineTable;