Initial commit: HealthCarePregnant project documentation and platform

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
selfrelease
2026-06-18 09:48:05 +08:00
commit eab91174db
301 changed files with 42491 additions and 0 deletions
@@ -0,0 +1,119 @@
import { Pool } from 'pg';
import { FollowupRepository } from './followup.repository';
import { FollowUp, FollowUpOutcome, FollowUpStatus, TargetOperator } from './followup.types';
import { FieldSealer } from '../../common/crypto/field-sealer';
interface SensitiveFollowUp {
targetOperator: TargetOperator;
targetValue: number;
windowDays: number;
evaluatedObservationId: string | null;
}
interface FollowUpRow {
id: string;
disposition_id: string;
patient_id: string;
indicator: string;
status: string;
outcome: string | null;
enc: string;
due_at: Date;
created_at: Date;
evaluated_at: Date | null;
}
const iso = (v: Date | string): string => (v instanceof Date ? v.toISOString() : String(v));
const isoOrNull = (v: Date | string | null): string | null => (v == null ? null : iso(v));
/** PostgreSQL 跟进项仓储(T-D.2)。目标值/复测观测ID 加密入 encindicator/status/due 留列供匹配。 */
export class PostgresFollowupRepository extends FollowupRepository {
constructor(
private readonly pool: Pool,
private readonly sealer: FieldSealer,
) {
super();
}
async save(f: FollowUp): Promise<FollowUp> {
const sensitive: SensitiveFollowUp = {
targetOperator: f.targetOperator,
targetValue: f.targetValue,
windowDays: f.windowDays,
evaluatedObservationId: f.evaluatedObservationId ?? null,
};
await this.pool.query(
`INSERT INTO followups (id, disposition_id, patient_id, indicator, status, outcome, enc, due_at, created_at, evaluated_at)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)
ON CONFLICT (id) DO UPDATE SET
status = EXCLUDED.status, outcome = EXCLUDED.outcome,
enc = EXCLUDED.enc, evaluated_at = EXCLUDED.evaluated_at`,
[
f.id,
f.dispositionId,
f.patientId,
f.indicator,
f.status,
f.outcome ?? null,
this.sealer.seal(sensitive),
f.dueAt,
f.createdAt,
f.evaluatedAt ?? null,
],
);
return f;
}
async findById(id: string): Promise<FollowUp | null> {
const res = await this.pool.query<FollowUpRow>('SELECT * FROM followups WHERE id = $1', [id]);
return res.rows[0] ? this.toDomain(res.rows[0]) : null;
}
async findByPatient(patientId: string): Promise<FollowUp[]> {
const res = await this.pool.query<FollowUpRow>(
'SELECT * FROM followups WHERE patient_id = $1 ORDER BY created_at',
[patientId],
);
return res.rows.map((r) => this.toDomain(r));
}
async findByDisposition(dispositionId: string): Promise<FollowUp[]> {
const res = await this.pool.query<FollowUpRow>(
'SELECT * FROM followups WHERE disposition_id = $1 ORDER BY created_at',
[dispositionId],
);
return res.rows.map((r) => this.toDomain(r));
}
async findAll(): Promise<FollowUp[]> {
const res = await this.pool.query<FollowUpRow>('SELECT * FROM followups ORDER BY created_at DESC');
return res.rows.map((r) => this.toDomain(r));
}
async findPendingByIndicator(patientId: string, indicator: string): Promise<FollowUp[]> {
const res = await this.pool.query<FollowUpRow>(
`SELECT * FROM followups WHERE patient_id = $1 AND indicator = $2 AND status <> 'evaluated' ORDER BY created_at`,
[patientId, indicator],
);
return res.rows.map((r) => this.toDomain(r));
}
private toDomain(row: FollowUpRow): FollowUp {
const s = this.sealer.open<SensitiveFollowUp>(row.enc);
return {
id: row.id,
dispositionId: row.disposition_id,
patientId: row.patient_id,
indicator: row.indicator,
targetOperator: s.targetOperator,
targetValue: s.targetValue,
windowDays: s.windowDays,
dueAt: iso(row.due_at),
status: row.status as FollowUpStatus,
outcome: (row.outcome as FollowUpOutcome | null) ?? null,
evaluatedObservationId: s.evaluatedObservationId,
evaluatedAt: isoOrNull(row.evaluated_at),
createdAt: iso(row.created_at),
};
}
}