Initial commit: HealthCarePregnant project documentation and platform
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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 加密入 enc;indicator/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),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user