Project: doctolib-doctolib · Branch: .invalid · Mode: · Model: claude-opus-4-7 Stats: 1 user messages, 21 tool calls, tools used: Bash, Read. Conversation Turn 1 — User Review recently modified code in PR #241133 (FHIR-2224) for simplification opportunities. The PR is checked out at /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/. Changed files to analyze: 1. packages/@doctolib/dmp/src/business/DmpApi.ts — specifically the updateDocumentStatus method (~lines 346–400) 2. packages/@doctolib/national-health-records/src/business/adapters/DmpNhrAdapter.ts — specifically updateDocumentMetadata (~lines 206–262) 3. packages/@doctolib/dmp/src/types/DmpApi.ts — DocumentStatusUpdateResult interface (lines ~186–188) Simplification angles to explore: 1. DmpNhrAdapter.ts line ~234: new DmpApiError(result.error).message — instantiating a full class object just to call .message. Would a standalone getDmpApiErrorMessage(info: DmpApiErrorInfo): string helper function be simpler? Or is the full object instantiation intentional for consistency? 2. DmpApiImpl.updateDocumentStatus legacy path (~lines 385–398): The error object spread has 6 optional fields all mapped from response.. Could this be extracted into a helper like toDmpApiErrorInfo(response) to reduce repetition, especially since similar mapping likely exists elsewhere in the codebase? 3. DocumentStatusUpdateResult is structurally identical to SearchResult minus documents: Could it simply reuse Omit or should it remain independent? 4. The cast removal: } as DocumentStatusUpdateProps) was removed. Can the caller infer the type correctly now, or is it implicit? Is the type inference now explicit enough? 5. s_status ?? 'ERROR' in DmpApi.ts line ~394: This is unreachable — if s_status !== STATUS_OK is true and s_status is nullish, we'd never get here since the check already ran. Should the fallback be removed or documented? Read the actual files and suggest simplifications that are safe and don't change behavior. Report in Conventional Comments format with file:line references. Turn 1 — Assistant I'll analyze the recently modified code in PR #241133 for simplification opportunities. Let me read the files first. Tools used: - Bash: ls /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/ Tool results: → result (ok): AGENTS.md app babel.config.js backend.dependencies.codeowners.yml bin catalog-info.yaml ci CLAUDE.md ⇒ AGENTS.md CODEOWNERS CODEOWNERS.local component_backlog.yml config config.ru CONTRIBUTING.md db dev-env-setup-wsl dev-tools doc docker docker-compose.linux.yml docker-compose.specmatic.yml docker-compose.yml docs engine_metrics.yml engines fixtures frontend-metrics.config.ts frontend.dependencies.codeowners.yml Gemfile Gemfile.experimental ⇒ Gemfile Gemfile.experimental.lock Gemfile.lock Gemfil …(truncated, 559 more chars) Tools used: - Read: /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/business/DmpApi.ts (340–410) Tool results: → result (ok): 340 341 const { sessionId } = cachedSession 342 const response = await getDocumentsContent({ sessionId, ...props }) 343 return response.Documents.map(this.toDocumentContent) 344 } 345 346 async updateDocumentStatus(props: DocumentStatusUpdateProps): Promise { 347 const { ins, documentUniqueId: uniqueId, uuid, healthCareSettings, getTransactionFrames } = props 348 349 if (this.dmpService) { 350 const payload = buildUpdateDocumentStatusP …(truncated, 1932 more chars) Tools used: - Read: /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/national-health-records/src/business/adapters/DmpNhrAdapter.ts (200–270) Tool results: → result (ok): 200 const typeCode = doc.cdaContent?.code?._code 201 const facet = findFacetForCDAType(typeCode, this.documentCategorizationFacets) 202 return toNhrDocumentContentResult(doc, facet) 203 }) 204 } 205 206 async updateDocumentMetadata(props: NhrDocumentMetadataUpdateProps): Promise { 207 if (!props.patient?.nationalHealthIdentifier) { 208 throw new Error('patient_national_health_identifier_required') 209 } 210 211 const …(truncated, 2111 more chars) Tools used: - Read: /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/types/DmpApi.ts (170–200) Tool results: → result (ok): 170 171 export interface DmpApiErrorInfo { 172 apiErrorCode?: string 173 apiErrorType?: string 174 apiErrorContext?: string 175 apiErrorDescription?: string 176 apiErrorExtendedInformations?: string 177 apiErrorTypeDescription?: string 178 status: string 179 } 180 181 export interface SearchResult { 182 documents: DmpDocument[] 183 error?: DmpApiErrorInfo 184 } 185 186 export interface DocumentStatusUpdateResult { 187 error?: DmpApiErrorInfo 188 } 189 190 export type GetD …(truncated, 266 more chars) Now let me look at the broader context including DmpApiError, the search method (for comparison), and other call sites. Tools used: - Bash: grep -rn "DmpApiError" /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/ /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/ …(truncated, 40 more chars) Tool results: → result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/index.ts:83:export { DmpApiError } from './api/DmpApiError' /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/index.ts:84:export type { DmpApiErrorInfo } from './types/DmpApi' /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/types/DmpApi.ts:171:export interface DmpApiErrorInfo { /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-222 …(truncated, 5092 more chars) Tools used: - Bash: grep -n "s_status\|i_apiErrorCode\|i_apiErrorType\|s_apiErrorContext" /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/business/DmpApi.ts Tool results: → result (ok): 289: if (status.s_status !== STATUS_OK) { 290: throw new Error(Error getting patient DMP Status, status: ${status.s_status}) 385: if (response.s_status !== STATUS_OK) { 388: apiErrorCode: response.i_apiErrorCode, 389: apiErrorType: response.i_apiErrorType, 390: apiErrorContext: response.s_apiErrorContext, 394: status: response.s_status ?? 'ERROR', 421: if (authStatusResponse.s_status !== STATUS_OK) { 422: throw new Error(Error set …(truncated, 965 more chars) Tools used: - Read: /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/business/DmpApi.ts (575–600) Tool results: → result (ok): 575 documentVisibility: doc.i_document_Visibility, 576 documentFormat: doc.i_document_Format, 577 authors: this.toDmpAuthors(doc.Authors ?? []), 578 eventCodes: this.toDmpEventCodes(doc.EventCodes), 579 handle: doc.i_handle, 580 referenceIds: this.toDmpReferenceIds(doc.ReferenceIds), 581 })), 582 error: 583 response.s_status !== STATUS_OK 584 ? { 585 apiErrorCode: response.i_apiErrorCode, 586 apiErrorType: response …(truncated, 657 more chars) Tools used: - Read: /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/api/DmpApiError.ts Tool results: → result (ok): 1 import type { DmpApiErrorInfo } from '../types/DmpApi' 2 3 export class DmpApiError extends Error { 4 readonly apiErrorCode: string | undefined 5 6 readonly apiErrorType: string | undefined 7 8 readonly apiErrorContext: string | undefined 9 10 readonly apiErrorDescription: string | undefined 11 12 readonly apiErrorExtendedInformations: string | undefined 13 14 readonly apiErrorTypeDescription: string | undefined 15 16 readonly status: string 17 18 constructor(info: DmpA …(truncated, 730 more chars) Tools used: - Read: /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/business/DmpApi.ts (1–61) Tool results: → result (ok): 1 / eslint-disable @typescript-eslint/no-non-null-assertion / 2 import type { Patient } from 'monolith_DEPRECATED/types/doctor/api/Patient' 3 import type { MedicalFolder } from '@doctolib/ehr-core' 4 import { getLoincCode } from 'monolith_DEPRECATED/models/document/document' 5 import { sendMonitoringEvent } from 'monolith_DEPRECATED/services/monitoring' 6 import { isMinor } from 'monolith_DEPRECATED/models/patient/patient' 7 import { isMedicalSynthesis } from 'monolith_DEPRECATED/components/do …(truncated, 1917 more chars) Now let me check the test file for the adapter and how DmpApiError.message is used elsewhere to understand patterns. Tools used: - Read: /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/api/DmpApiError.test.ts Tool results: → result (ok): 1 import { DmpApiError } from './DmpApiError' 2 import type { DmpApiErrorInfo } from '../types/DmpApi' 3 4 describe('DmpApiError', () => { 5 describe('message assembly', () => { 6 it('combines description and extended info when both are present', () => { 7 const info: DmpApiErrorInfo = { 8 apiErrorDescription: 'Technical software error', 9 apiErrorExtendedInformations: 'Operation timed out.', 10 status: 'ERROR', 11 } 12 const error = new DmpApiErr …(truncated, 3083 more chars) Tools used: - Bash: grep -rn "DmpApiError\b" /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/ /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/app/ /Users/USER/workspace/doc …(truncated, 181 more chars) Tool results: → result (ok): (Bash completed with no output) Tools used: - Read: /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/national-health-records/src/business/adapters/DmpNhrAdapter.ts (1–51) Tool results: → result (ok): 1 import type { 2 DmpApi, 3 DmpAuthor, 4 DmpDocument, 5 DmpDocumentContent, 6 SearchProps, 7 SearchResult as DmpSearchResult, 8 } from '@doctolib/dmp' 9 import { DmpApiError, DOCUMENT_STATUS, DOCUMENT_VISIBILITY, getPatientInsForDMP } from '@doctolib/dmp' 10 import { isUnstructured } from '@doctolib/cda/types/UnstructuredBody' 11 import { isStructured } from '@doctolib/cda/types/StructuredBody' 12 import type { Patient } from 'monolith_DEPRECATED/types/doctor/api/Patient' 13 import t …(truncated, 1410 more chars) Let me also check the s_status type from the response to assess the unreachable ?? 'ERROR' claim. Tools used: - Bash: grep -n "s_status" /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/types/FrDmpTypes.ts /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/pack …(truncated, 76 more chars) Tool results: → result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/types/FrDmpTypes.ts:194: s_status?: string /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/types/FrDmpTypes.ts:208: s_status: string /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/types/FrDmpTypes.ts:231: s_status: string /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/types/FrDmpTypes.ts:67 …(truncated, 560 more chars) Tools used: - Bash: grep -n "updateDocumentStatus\b\|UpdateDocumentStatusResponse" /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/types/FrDmpTypes.ts /Users/USER/workspac …(truncated, 109 more chars) Tool results: → result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/business/FrDmpCommunication.ts:408:export const updateDocumentStatus = ({ Tools used: - Read: /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/business/FrDmpCommunication.ts (395–455) Tool results: → result (ok): 395 getPerformer, 396 getInformants, 397 getAuthors, 398 getDataEnterer, 399 getParticipants, 400 getAuthenticators, 401 getDocumentContent, 402 getTransactionFrames, 403 }), 404 } 405 return frDmpSend(payload) 406 } 407 408 export const updateDocumentStatus = ({ 409 sessionId, 410 ins, 411 uniqueId, 412 uuid, 413 healthCareSettings, 414 getTransactionFrames, 415 }: Updat …(truncated, 1676 more chars) Tools used: - Bash: grep -n "UpdateDocumentStatusByUniqueIdResponse" /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/types/FrDmpTypes.ts Tool results: → result (ok): 606:export interface UpdateDocumentStatusByUniqueIdResponse extends DmpResponse {} Tools used: - Read: /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/types/FrDmpTypes.ts (185–215) Tool results: → result (ok): 185 / Event codes to filter by. Returns documents matching at least one event code. / 186 EventCodes?: EventCode[] 187 / Enable transaction frame debugging (0 or 1). Development only. / 188 i_getTransactionFrames?: number 189 } 190 191 // DMP Responses 192 export interface DmpResponse extends DmpResult { 193 // Value "OK" for success. 194 s_status?: string 195 i_apiErrorCode?: string 196 i_apiErrorType?: string 197 s_apiErrorContext?: string 198 s_apiErrorDescription?: …(truncated, 400 more chars) Confirmed: s_status is string | undefined on DmpResponse. So when response.s_status !== STATUS_OK, s_status could still be undefined (e.g. STATUS_OK = "OK", undefined !== "OK" is true). The fallback ?? 'ERROR' is therefore reachable, not unreachable as the prompt suggests. Let me verify STATUS_OK. Tools used: - Bash: grep -rn "^export const STATUS_OK\|^export.STATUS_OK\b\|const STATUS_OK = " /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/ 2>/dev/null | head -5 Tool results: → result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/business/FrDmpConstants.ts:8:export const STATUS_OK = 'OK' Good. STATUS_OK = 'OK'. So s_status is string | undefined. If s_status is undefined, then undefined !== 'OK' is true, and the inside of the if runs, where s_status ?? 'ERROR' would correctly resolve to 'ERROR'. Therefore the fallback is actually reachable and necessary. Let me also check the test file for the adapter to see the call pattern and existing mock: Tools used: - Read: /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/national-health-records/src/business/adapters/DmpNhrAdapter.test.ts (1–61) Tool results: → result (ok): 1 import { getPatientInsForDMP } from '@doctolib/dmp' 2 import type { DmpApi, DmpDocumentContent, SearchResult as DmpSearchResult } from '@doctolib/dmp' 3 import type { NhrDocumentSearchProps } from '@doctolib/tpf-national-health-records' 4 import type { DocumentCategorizationConcept, DocumentCategorizationFacet } from '@doctolib/documents-ui/types/Document' 5 import { DmpNhrAdapter } from './DmpNhrAdapter' 6 import { findFacetForCDAType } from '../utils/findFacetForCDAType' 7 8 jest.mock('../u …(truncated, 2117 more chars) Now let me check the search method for comparison with the legacy path mapping repetition, and finally let me look at the call site (PatientCard) referenced for the cast removal: Tools used: - Read: /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/business/DmpApi.ts (540–600) Tool results: → result (ok): 540 await getAuthentificationDmpStatusForPatient(frDmpWorkflow) 541 logIfTraceEnabled('frDmpCommonFrames after getAuthentificationDmpStatusForPatient') 542 543 await setConnectionConfidentialityLevel(frDmpWorkflow, forceSecretConnection, this.props.useSecretConnection) 544 logIfTraceEnabled('frDmpCommonFrames after setConnectionConfidentialityLevel') 545 }) 546 547 frDmpWorkflow.openFrDmpTransaction = frDmpWorkflow.openFrDmpTransaction.then(async () => { 548 …(truncated, 2420 more chars) I have enough to provide a substantive review. Let me also do a quick scan for whether the toDmpApiErrorInfo helper would have other callers: Tools used: - Bash: grep -rn "i_apiErrorCode" /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/ 2>/dev/null | grep -v '\.test\.' | grep -v 'types/' Tool results: → result (ok): /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/business/FrDmpCommunication.ts:77: response: { s_status, i_apiErrorCode, i_apiErrorType } = {}, /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/business/FrDmpCommunication.ts:99: i_apiErrorCode, /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/business/DmpApi.ts:388: apiErrorCode: response.i_apiErrorCode, /Users/USER/ …(truncated, 897 more chars) Confirmed: there are exactly two identical mappings of DmpResponse → DmpApiErrorInfo at DmpApi.ts:388 (legacy updateDocumentStatus) and DmpApi.ts:585 (toSearchResult). This is a real duplication worth extracting. Now I have the full picture. Here is my review. --- Code Review — PR #241133 (FHIR-2224) Reviewed files - /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/business/DmpApi.ts - /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/national-health-records/src/business/adapters/DmpNhrAdapter.ts - /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/types/DmpApi.ts - /Users/USER/workspace/doctolib/worktrees/doctolib/FHIR-2224/packages/@doctolib/dmp/src/api/DmpApiError.ts (referenced) --- Comment 1 — DmpNhrAdapter.ts:234 — new DmpApiError(result.error).message suggestion (non-blocking): Instantiating a full Error subclass purely to read .message is mildly wasteful and obscures intent — readers wonder "is this thrown? caught? logged?" when it is none of those. Extract a tiny standalone helper colocated with DmpApiError: // packages/@doctolib/dmp/src/api/DmpApiError.ts export function getDmpApiErrorMessage(info: DmpApiErrorInfo): string { if (info.apiErrorDescription && info.apiErrorExtendedInformations) { return ${info.apiErrorDescription}: ${info.apiErrorExtendedInformations} } return info.apiErrorDescription ?? info.apiErrorExtendedInformations ?? 'DMP API error' } export class DmpApiError extends Error { // ... constructor(info: DmpApiErrorInfo) { super(getDmpApiErrorMessage(info)) // ... } } Then at DmpNhrAdapter.ts:234: message: getDmpApiErrorMessage(result.error), Benefits: removes the indirection through Error allocation/stack capture, makes the call self-describing, single source of truth for the message format (the class still uses the same helper). Also lets the test mock at DmpNhrAdapter.test.ts:24-33 shrink — it currently re-implements the message logic inline. Caveat: if a future refactor will throw the full DmpApiError here (i.e. the class shape is already the target API for the adapter), keep the instantiation — that future change matters more than the small allocation. Decide based on intent. --- Comment 2 — DmpApi.ts:385-397 and DmpApi.ts:582-593 — duplicated response→error mapping issue (blocking-ish for elegance): The block at lines 385-397 is byte-identical to the one at lines 585-591 inside toSearchResult. Both translate DmpResponse (camelCase s_ / i_) to DmpApiErrorInfo. This is the strongest simplification opportunity in the diff. suggestion: Extract a private helper colocated with the class (or as a free function in this module since it has no this): function toDmpApiErrorInfo(response: DmpResponse): DmpApiErrorInfo { return { apiErrorCode: response.i_apiErrorCode, apiErrorType: response.i_apiErrorType, apiErrorContext: response.s_apiErrorContext, apiErrorDescription: response.s_apiErrorDescription, apiErrorExtendedInformations: response.s_apiErrorExtendedInformations, apiErrorTypeDescription: response.s_apiErrorTypeDescription, status: response.s_status ?? 'ERROR', } } Then: // DmpApi.ts:385 if (response.s_status !== STATUS_OK) { return { error: toDmpApiErrorInfo(response) } } return {} // DmpApi.ts:582 error: response.s_status !== STATUS_OK ? toDmpApiErrorInfo(response) : undefined, This shrinks two 7-line blocks to one and creates a single point of truth that future fields (e.g. s_apiErrorDmpErrorCode, already used at line 811) can be added to. --- Comment 3 — DmpApi.ts:394 — "unreachable" s_status ?? 'ERROR' nitpick (correction): The prompt's claim that this fallback is unreachable is incorrect. s_status is typed string | undefined on DmpResponse (FrDmpTypes.ts:194), and STATUS_OK = 'OK'. The guard response.s_status !== STATUS_OK evaluates to true for both 'ERROR' and undefined, so the inner branch can run with s_status === undefined. The ?? 'ERROR' is correctly defending against that case. If extracted into toDmpApiErrorInfo (Comment 2), the fallback lives in exactly one place and the question stops coming up. No code change needed beyond Comment 2. --- Comment 4 — types/DmpApi.ts:181-188 — DocumentStatusUpdateResult shape nitpick: You raised the option DocumentStatusUpdateResult = Omit. I'd recommend leaving it as a standalone interface: - The two types are semantically distinct (one is the result of a query that happens to share an error envelope; the other is the result of a mutation). Coupling them via Omit would mean any future SearchResult-only field (e.g. pagination cursor, totalCount) would have to be threaded through Omit. - Omit is also less readable in IDE hovers than the explicit { error?: DmpApiErrorInfo }. - Each interface is two lines — there is no real duplication cost. A lighter alternative if the duplication does start to bite: define a base type and extend, e.g. interface DmpApiErrorEnvelope { error?: DmpApiErrorInfo } export interface SearchResult extends DmpApiErrorEnvelope { documents: DmpDocument[] } export type DocumentStatusUpdateResult = DmpApiErrorEnvelope But I would only do this once a third call site appears. --- Comment 5 — Cast removal at the call site (referenced in PR description) praise: Removing as DocumentStatusUpdateProps at the call site (DmpNhrAdapter.ts:221-226) is a clean win. The literal object passed to updateDocumentStatus now relies on TypeScript contextual typing from the parameter type at DmpApi.ts:346, which is exactly the right pattern. Casts only mask type errors; contextual typing surfaces them. Keep it as-is. --- Comment 6 — DmpNhrAdapter.ts:206-262 — overall structure of updateDocumentMetadata nitpick (optional): The async payloads.map callback is ~40 lines with three exit branches (success, error from result, skipped) plus a try/catch. Consider extracting the per-payload work to a private method processMetadataUpdate(payload, ins, healthcareSettings): Promise. This: - shrinks updateDocumentMetadata to its true responsibility (validation, fan-out, fold); - makes future support for propertyName === 'visibility' a one-line edit instead of further nesting (the current TODO at line 241 hints at this); - makes each branch independently testable. Skeleton: async updateDocumentMetadata(props: NhrDocumentMetadataUpdateProps): Promise { if (!props.patient?.nationalHealthIdentifier) { throw new Error('patient_national_health_identifier_required') } const ins = toEnvIns(props.patient.nationalHealthIdentifier) const { healthcareSettings } = props.additionalInfo const payloadResults = await Promise.all( props.params.payloads.map((payload) => this.processMetadataUpdate(payload, ins, healthcareSettings)) ) return this.buildUpdateResult(payloadResults) } Functionality is unchanged. --- Summary | # | File:line | Severity | Change | |---|---|---|---| | 1 | DmpNhrAdapter.ts:234 | suggestion | Optional: getDmpApiErrorMessage helper | | 2 | DmpApi.ts:385-397 & :582-593 | suggestion (recommended) | Extract toDmpApiErrorInfo(response) — eliminates real duplication | | 3 | DmpApi.ts:394 | nitpick | Fallback is correctly reachable; no code change | | 4 | types/DmpApi.ts:186-188 | nitpick | Keep as standalone interface | | 5 | call site cast removal | praise | Already correct | | 6 | DmpNhrAdapter.ts:206-262 | nitpick | Optional method extraction for future visibility support | The single highest-value change is Comment 2 (extract toDmpApiErrorInfo`). The …(truncated, 22 more chars)