diff --git a/scripts/health/assets/health-assets/dashboard-v5-record.js b/scripts/health/assets/health-assets/dashboard-v5-record.js index b33263e..04ee31a 100644 --- a/scripts/health/assets/health-assets/dashboard-v5-record.js +++ b/scripts/health/assets/health-assets/dashboard-v5-record.js @@ -58,6 +58,8 @@ const text = value => String(value ?? '—'); const sourceLabel = value => ({document:'Dokument',doctor_report:'Arztbericht',manual:'Manuell dokumentiert',capture:'Mobile Erfassung',imported:'Importierte Dokumentation',medication_plan:'Medikationsplan'})[String(value||'')] || 'Dokumentierte Quelle'; + const medicationRouteLabel = value => ({unknown:'Unbekannt',oral:'Oral',subcutaneous:'Subkutan',intravenous:'Intravenös',intramuscular:'Intramuskulär',topical:'Äusserlich',inhaled:'Inhalativ',other:'Andere dokumentierte Route'})[String(value||'')] || 'Unbekannt'; + const medicationSideLabel = value => ({left:'Links',right:'Rechts',unspecified:'Seite nicht angegeben'})[String(value||'')] || ''; const element = (tag, value, className) => { const node = document.createElement(tag); if (className) node.className = className; @@ -816,22 +818,25 @@ function administrationCaptureDialog(prescription, eventItem = null, captureMode = 'historical') { const trigger=document.activeElement; const plannedMode=captureMode==='planned'; + const correctionMode=captureMode==='correction'; if(plannedMode&&!eventItem)return; - const baseline=plannedMode?eventItem?.administration_preset:prescription.administration_preset; + if(correctionMode&&(!eventItem?.capture_entry_ref||!eventItem?.correction_administration_preset))return; + const baseline=correctionMode?eventItem.correction_administration_preset:(plannedMode?eventItem?.administration_preset:prescription.administration_preset); const dialog=document.createElement('dialog');dialog.className='medication-action-dialog';dialog.setAttribute('aria-labelledby','medication-administration-title'); const form=document.createElement('form');form.method='dialog'; - const title=element('h2',plannedMode?'Geplanten Termin dokumentieren':'Historische Einnahme/Gabe erfassen');title.id='medication-administration-title'; - form.append(title,element('p','Manuelle Dokumentation ohne Änderung des Verordnungsstatus.','v5-meta')); + const title=element('h2',correctionMode?'Medikationseintrag korrigieren':(plannedMode?'Geplanten Termin dokumentieren':'Historische Einnahme/Gabe erfassen'));title.id='medication-administration-title'; + form.append(title,element('p',correctionMode?'Append-only Korrektur ohne Änderung der Verordnung.':'Manuelle Dokumentation ohne Änderung des Verordnungsstatus.','v5-meta')); if(prescription.status!=='active')form.append(element('p',`Verordnungsstatus bleibt: ${prescription.status==='unknown'?'unbekannt':prescription.status}.`,'v5-meta')); - const when=document.createElement('input');when.type='datetime-local';when.required=true; - const quantity=document.createElement('input');quantity.maxLength=40;quantity.required=true;quantity.inputMode='decimal';quantity.value=baseline?.quantity_value||''; - const dosageForm=document.createElement('input');dosageForm.maxLength=40;dosageForm.required=true;dosageForm.value=baseline?.dosage_form||''; + const when=document.createElement('input');when.type='datetime-local';when.required=true;when.value=correctionMode?(eventItem?.occurred_at||''):'';when.readOnly=correctionMode; + const quantity=document.createElement('input');quantity.type='number';quantity.min='0.0001';quantity.max='100000';quantity.step='any';quantity.required=true;quantity.inputMode='decimal';quantity.value=baseline?.quantity_value||''; + const dosageForm=document.createElement('select');[['','Bitte wählen'],['Spritze','Spritze'],['Tablette','Tablette']].forEach(([value,label])=>{const option=element('option',label);option.value=value;dosageForm.append(option);});dosageForm.required=true;dosageForm.value=baseline?.dosage_form||''; const strength=document.createElement('input');strength.maxLength=80;strength.required=true;strength.value=baseline?.strength||''; const route=document.createElement('select');[['unknown','Nicht belegt'],['oral','Oral'],['subcutaneous','Subkutan'],['intravenous','Intravenös'],['intramuscular','Intramuskulär'],['topical','Äußerlich'],['inhaled','Inhalativ'],['other','Andere dokumentierte Route']].forEach(([value,label])=>{const option=element('option',label);option.value=value;route.append(option);});route.value=baseline?.route_normalized||'unknown'; const routeOriginal=document.createElement('input');routeOriginal.maxLength=60;routeOriginal.value=baseline?.route_original||''; - const region=document.createElement('input');region.maxLength=80;const side=document.createElement('select');[['','Nicht angegeben'],['left','Links'],['right','Rechts'],['unspecified','Seite nicht angegeben']].forEach(([value,label])=>{const option=element('option',label);option.value=value;side.append(option);});const injectionDetail=document.createElement('input');injectionDetail.maxLength=120; - const note=document.createElement('textarea');note.maxLength=300;const deviation=document.createElement('input');deviation.type='checkbox';const duplicate=document.createElement('input');duplicate.type='checkbox'; + const region=document.createElement('input');region.maxLength=80;region.value=correctionMode?(eventItem?.injection_region||''):'';const side=document.createElement('select');[['','Nicht angegeben'],['left','Links'],['right','Rechts'],['unspecified','Seite nicht angegeben']].forEach(([value,label])=>{const option=element('option',label);option.value=value;side.append(option);});side.value=correctionMode?(eventItem?.injection_side||''):'';const injectionDetail=document.createElement('input');injectionDetail.maxLength=120;injectionDetail.value=correctionMode?(eventItem?.injection_detail||''):''; + const note=document.createElement('textarea');note.maxLength=300;note.value=correctionMode?(eventItem?.note||''):'';const correctionReason=document.createElement('textarea');correctionReason.maxLength=300;correctionReason.required=correctionMode;const deviation=document.createElement('input');deviation.type='checkbox';const duplicate=document.createElement('input');duplicate.type='checkbox'; form.append(formRow('Medikament',element('strong',prescription.name)),formRow('Zeitpunkt (Europe/Zurich)',when),formRow('Menge',quantity),formRow('Darreichungsform',dosageForm),formRow('Wirkstoffstärke',strength),formRow('Applikationsweg',route),formRow('Applikationsweg – dokumentierte Originalangabe',routeOriginal),formRow('Injektionsregion (optional)',region),formRow('Seite (optional)',side),formRow('Injektionsstelle (optional)',injectionDetail),formRow('Notiz (optional)',note)); + if(correctionMode)form.append(formRow('Korrekturbegründung',correctionReason)); const deviationRow=formRow('Abweichung von der verifizierten Vorauswahl bewusst bestätigen',deviation);deviationRow.hidden=true;form.append(deviationRow,formRow('Möglichen identischen Doppeleintrag nach Prüfung zulassen',duplicate)); const fieldError=element('p','', 'capture-inline-status');fieldError.setAttribute('role','alert');form.append(fieldError); const preview=element('section',undefined,'medication-action-preview');preview.hidden=true;preview.setAttribute('aria-live','polite');form.append(preview); @@ -845,14 +850,14 @@ form.addEventListener('input',()=>{fieldError.textContent='';if(frozen){frozen=null;frozenCsrf='';preview.hidden=true;submit.textContent='Vorschau prüfen';}}); form.addEventListener('submit',async event=>{event.preventDefault();fieldError.textContent='';if(!frozen){ if(differs()&&!deviation.checked){fieldError.textContent='Die Angaben weichen von der verifizierten Vorauswahl ab. Bitte Abweichung bestätigen.';deviation.focus();return;} - const data={contract:'health.medication_action.v2',mode:captureMode,status:'administered',medication_ref:prescription.id,planned_event_ref:plannedMode?eventItem.id:'',name:prescription.name,quantity_value:quantity.value.trim(),dosage_form:dosageForm.value.trim(),strength:strength.value.trim(),route_original:routeOriginal.value.trim(),route_normalized:route.value,injection_region:region.value.trim(),injection_side:side.value,injection_detail:injectionDetail.value.trim(),note:note.value.trim(),preset_revision:baseline?.preset_revision||'',preview_revision:'',deviation_confirmed:deviation.checked,duplicate_confirmed:duplicate.checked}; - const draft={version:1,action:'capture_entry',capture_type:'medication',request_version:1,idempotency_key:[...crypto.getRandomValues(new Uint8Array(16))].map(value=>value.toString(16).padStart(2,'0')).join(''),occurred_at:when.value,ended_at:null,data,attachments:[],corrects_entry_id:null,withdraws_entry_id:null}; + const data={contract:'health.medication_action.v2',mode:captureMode,status:correctionMode?'corrected':'administered',medication_ref:prescription.id,planned_event_ref:plannedMode?eventItem.id:'',name:prescription.name,quantity_value:quantity.value.trim(),dosage_form:dosageForm.value.trim(),strength:strength.value.trim(),route_original:routeOriginal.value.trim(),route_normalized:route.value,injection_region:region.value.trim(),injection_side:side.value,injection_detail:injectionDetail.value.trim(),note:note.value.trim(),preset_revision:baseline?.preset_revision||'',preview_revision:'',deviation_confirmed:deviation.checked,duplicate_confirmed:duplicate.checked,...(correctionMode?{correction_target_ref:eventItem.id,correction_reason:correctionReason.value.trim(),bind_verified_preset:false}:{})}; + const draft={version:1,action:'capture_entry',capture_type:'medication',request_version:1,idempotency_key:[...crypto.getRandomValues(new Uint8Array(16))].map(value=>value.toString(16).padStart(2,'0')).join(''),occurred_at:when.value,ended_at:null,data,attachments:[],corrects_entry_id:correctionMode?eventItem.capture_entry_ref:null,withdraws_entry_id:null}; submit.disabled=true; try{ const csrfResponse=await fetch('/api/v1/capture/csrf',{credentials:'same-origin',headers:{'Accept':'application/json'}});if(!csrfResponse.ok)throw new Error('csrf');const csrfResult=await csrfResponse.json();const captureCsrf=String(csrfResult.csrf_token||'');if(!/^[A-Za-z0-9_-]{24,128}$/.test(captureCsrf))throw new Error('csrf'); - const body=new URLSearchParams({csrf_token:captureCsrf,return_to:'v5',payload:JSON.stringify(draft)});const response=await fetch('/health-actions/medication-preview',{method:'POST',credentials:'same-origin',headers:{'Accept':'application/json','Content-Type':'application/x-www-form-urlencoded;charset=UTF-8'},body:body.toString()});const result=await response.json().catch(()=>({}));if(!response.ok){fieldError.textContent=result?.error?.message||'Die Angaben sind ungültig. Bitte markierte Felder prüfen.';const field=result?.error?.field;const fieldInputs={medication:quantity,quantity_value:quantity,dosage_form:dosageForm,strength,route_original:routeOriginal,route_normalized:route,injection_region:region,injection_side:side,injection_detail:injectionDetail,note,capture_mode:when,deviation_confirmed:deviation,duplicate_confirmed:duplicate,preset:quantity,preview:submit};fieldInputs[field]?.focus();return;}if(!/^[a-f0-9]{64}$/.test(String(result.preview_revision||'')))throw new Error('preview');draft.data.preview_revision=result.preview_revision;frozen=draft;frozenCsrf=captureCsrf; + const body=new URLSearchParams({csrf_token:captureCsrf,return_to:'v5',payload:JSON.stringify(draft)});const response=await fetch('/health-actions/medication-preview',{method:'POST',credentials:'same-origin',headers:{'Accept':'application/json','Content-Type':'application/x-www-form-urlencoded;charset=UTF-8'},body:body.toString()});const result=await response.json().catch(()=>({}));if(!response.ok){fieldError.textContent=result?.error?.message||'Die Angaben sind ungültig. Bitte markierte Felder prüfen.';const field=result?.error?.field;const fieldInputs={medication:quantity,quantity_value:quantity,dosage_form:dosageForm,strength,route_original:routeOriginal,route_normalized:route,injection_region:region,injection_side:side,injection_detail:injectionDetail,note,capture_mode:when,correction_target:submit,correction_reason:correctionReason,deviation_confirmed:deviation,duplicate_confirmed:duplicate,preset:quantity,preview:submit};fieldInputs[field]?.focus();return;}if(!/^[a-f0-9]{64}$/.test(String(result.preview_revision||'')))throw new Error('preview');draft.data.preview_revision=result.preview_revision;frozen=draft;frozenCsrf=captureCsrf; }catch(_error){fieldError.textContent='Die Erfassung ist technisch nicht verfügbar. Es wurde nichts vorgemerkt.';return;}finally{submit.disabled=false;} - preview.replaceChildren(element('h3','Vollständige Vorschau'),element('p',`${when.value} · ${prescription.name}`),element('p',`${quantity.value} ${dosageForm.value} · ${strength.value}`),element('p',`Applikationsweg: ${route.options[route.selectedIndex].text}${region.value||side.value||injectionDetail.value?` · ${[region.value,side.options[side.selectedIndex]?.text,injectionDetail.value].filter(Boolean).join(' · ')}`:''}`),element('p',note.value?`Notiz: ${note.value}`:'Keine Notiz.','v5-meta'),element('p',plannedMode?'Mit vorhandenem Plantermin verknüpft.':'Historische manuelle Gabe ohne Plantermin.','v5-meta'));preview.hidden=false;submit.textContent='Verbindlich vormerken';return; + preview.replaceChildren(element('h3','Vollständige Vorschau'),element('p',`${when.value} · ${prescription.name}`),element('p',`${quantity.value} ${dosageForm.value} · ${strength.value}`),element('p',`Applikationsweg: ${route.options[route.selectedIndex].text}${region.value||side.value||injectionDetail.value?` · ${[region.value,side.options[side.selectedIndex]?.text,injectionDetail.value].filter(Boolean).join(' · ')}`:''}`),element('p',correctionMode?`Korrekturbegründung: ${correctionReason.value}`:(note.value?`Notiz: ${note.value}`:'Keine Notiz.'),'v5-meta'),element('p',correctionMode?'Append-only Korrektur des gewählten Ereignisses.':(plannedMode?'Mit vorhandenem Plantermin verknüpft.':'Historische manuelle Gabe ohne Plantermin.'),'v5-meta'));preview.hidden=false;submit.textContent='Verbindlich vormerken';return; } submit.disabled=true; try{ @@ -882,7 +887,7 @@ const section = element('section'); section.append(element('h5', ({ planned: 'Geplant', administered: 'Tatsächlich verabreicht / eingenommen', missed: 'Ausgelassen', corrected: 'Korrigiert',unknown:'Status unbekannt' })[kind])); const rows = data[kind] || []; if(!rows.length){section.append(element('p','Keine dokumentierten Angaben.'));node.append(section);return;} - const list=element('div',undefined,'medication-history-list');rows.forEach(item=>{const article=element('article',undefined,'medication-history-row');article.append(element('div',`${item.date} · ${item.name} · ${medicationDose(item)} · ${sourceLabel(item.source)}`));if(item.superseded_by_correction)article.append(element('span','Durch Korrektur ersetzt','status-chip'));const details=document.createElement('details');details.append(element('summary','Details'));const facts=element('dl');[['Status',item.status],['Dokumentierter Folgetermin',item.scheduled_next_date||'Nicht angegeben'],['Geplante Menge',item.planned_quantity_value||'Nicht strukturiert dokumentiert'],['Geplante Darreichungsform',item.planned_dosage_form||'Nicht strukturiert dokumentiert'],['Geplante Wirkstoffstärke',item.planned_strength||'Nicht strukturiert dokumentiert'],['Tatsächliche Menge',item.actual_quantity_value||'Nicht strukturiert dokumentiert'],['Tatsächliche Darreichungsform',item.actual_dosage_form||'Nicht strukturiert dokumentiert'],['Tatsächliche Wirkstoffstärke',item.actual_strength||'Nicht strukturiert dokumentiert'],['Legacy-Dosis Wert/Einheit (uninterpretiert)',[item.actual_dose_value,item.actual_dose_unit].filter(Boolean).join(' ')||[item.planned_dose_value,item.planned_dose_unit].filter(Boolean).join(' ')||'Nicht vorhanden'],['Legacy-Dosisfreitext (uninterpretiert)',item.legacy_dose||'Nicht vorhanden'],['Applikationsweg (Original)',item.route_original||'Nicht angegeben'],['Applikationsweg (Anzeige)',item.route_normalized||'Unbekannt'],['Injektionsstelle',[item.injection_region,item.injection_side,item.injection_detail].filter(Boolean).join(' · ')||'Nicht angegeben'],['Charge',item.lot_number||'Nicht angegeben'],['Notiz',item.note||'Nicht angegeben'],['Korrekturbegründung',item.correction_reason||'Nicht angegeben'],['Quelle',sourceLabel(item.source)]].forEach(([label,value])=>facts.append(element('dt',label),element('dd',value)));details.append(facts);article.append(details);const prescription=prescriptionsById.get(item.prescription_id);if(prescription&&kind==='planned'&&!item.plan_consumed){if(item.administration_preset)article.append(button('Geplanten Termin dokumentieren',()=>administrationCaptureDialog(prescription,item,'planned')));article.append(button('Auslassung dokumentieren',()=>medicationActionDialog(prescription,item,'missed')));}if(prescription&&!item.superseded_by_correction&&item.correction_preview_revision)article.append(button('Eintrag korrigieren',()=>medicationActionDialog(prescription,item,'corrected')));list.append(article);});section.append(list);node.append(section); + const list=element('div',undefined,'medication-history-list');rows.forEach(item=>{const article=element('article',undefined,'medication-history-row');article.append(element('div',`${item.date} · ${item.name} · ${medicationDose(item)} · ${sourceLabel(item.source)}`));if(item.superseded_by_correction)article.append(element('span','Durch Korrektur ersetzt','status-chip'));const details=document.createElement('details');details.append(element('summary','Details'));const facts=element('dl');[['Status',item.status],['Dokumentierter Folgetermin',item.scheduled_next_date||'Nicht angegeben'],['Geplante Menge',item.planned_quantity_value||'Nicht strukturiert dokumentiert'],['Geplante Darreichungsform',item.planned_dosage_form||'Nicht strukturiert dokumentiert'],['Geplante Wirkstoffstärke',item.planned_strength||'Nicht strukturiert dokumentiert'],['Tatsächliche Menge',item.actual_quantity_value||'Nicht strukturiert dokumentiert'],['Tatsächliche Darreichungsform',item.actual_dosage_form||'Nicht strukturiert dokumentiert'],['Tatsächliche Wirkstoffstärke',item.actual_strength||'Nicht strukturiert dokumentiert'],['Legacy-Dosis Wert/Einheit (uninterpretiert)',[item.actual_dose_value,item.actual_dose_unit].filter(Boolean).join(' ')||[item.planned_dose_value,item.planned_dose_unit].filter(Boolean).join(' ')||'Nicht vorhanden'],['Legacy-Dosisfreitext (uninterpretiert)',item.legacy_dose||'Nicht vorhanden'],['Applikationsweg (Original)',item.route_original||'Nicht angegeben'],['Applikationsweg (Anzeige)',medicationRouteLabel(item.route_normalized)],['Injektionsstelle',[item.injection_region,medicationSideLabel(item.injection_side),item.injection_detail].filter(Boolean).join(' · ')||'Nicht angegeben'],['Charge',item.lot_number||'Nicht angegeben'],['Notiz',item.note||'Nicht angegeben'],['Korrekturbegründung',item.correction_reason||'Nicht angegeben'],['Quelle',sourceLabel(item.source)]].forEach(([label,value])=>facts.append(element('dt',label),element('dd',value)));details.append(facts);article.append(details);const prescription=prescriptionsById.get(item.prescription_id);if(prescription&&kind==='planned'&&!item.plan_consumed){if(item.administration_preset)article.append(button('Geplanten Termin dokumentieren',()=>administrationCaptureDialog(prescription,item,'planned')));article.append(button('Auslassung dokumentieren',()=>medicationActionDialog(prescription,item,'missed')));}if(prescription&&!item.superseded_by_correction&&item.correction_preview_revision){if(item.correction_administration_preset&&item.capture_entry_ref)article.append(button('Eintrag korrigieren',()=>administrationCaptureDialog(prescription,item,'correction')));else article.append(button('Legacy-Eintrag korrigieren',()=>medicationActionDialog(prescription,item,'corrected')));}list.append(article);});section.append(list);node.append(section); }); node.append(element('p', `Datenvollständigkeit: ${data.truncated ? `gekürzt (${data.truncated_sections.join(', ')})` : 'vollständige Seite'} · Fehlende Legacy-Felder bleiben unbekannt.`, 'v5-meta')); return node; @@ -958,7 +963,7 @@ symptoms:{title:'Beschwerden und Aphten',unit:'Dokumentierter Score',context:'Persönlicher Vergleich; keine generische medizinische Bewertung',series:[{name:'Symptomscore',points:(data.symptoms||[]).map(item=>[item.date,item.symptoms?.total??null])}]}, histamine:{title:'Histamin-Zuordnungsindex',unit:'Index',context:'Dokumentierte Zuordnung; keine Verträglichkeits- oder Entzündungsbewertung',series:[{name:'Index',points:(data.nutrition||[]).map(item=>[item.date,item.histamine?.score??null])}]}, nutrition_factors:{title:'Zucker, Ballaststoffe und gesättigte Fettsäuren',unit:'g',context:'Ernährungsorientierung; YAZIO bleibt Primärquelle',series:[['Zucker','nutrient.sugar'],['Ballaststoffe','nutrient.dietaryfiber'],['Gesättigte Fettsäuren','nutrient.saturated']].map(([name,key])=>({name,points:(data.nutrition||[]).map(item=>[item.date,item.nutrients?.[key]?.total_value??item.nutrients?.[key]?.value??null])}))} - };sessionChartSpecs.forEach((stored,index)=>{specs[`session_${index}`]={title:String(stored.title||'Explorer-Grafik'),unit:String(stored.unit||''),context:String(stored.context||'Zeitliche Beobachtung ohne Kausalitätsaussage'),period:stored.period||null,coverage:stored.coverage||null,markers:Array.isArray(stored.markers)?stored.markers:[],series:Array.isArray(stored.series)?stored.series.map(item=>({name:String(item.name||'Dokumentierter Wert'),unit:String(item.unit||stored.unit||''),points:Array.isArray(item.points)?item.points:[],baseline:Array.isArray(item.baseline)?item.baseline:[]})):[]};});const markers=[...(data.medications?.administered||[]).map(item=>({date:item.date,label:'Medikament'})),...(data.supplements?.administered||[]).map(item=>({date:item.date,label:'Supplement'}))];const draws=[];selectedChartKeys.forEach(key=>{const spec=specs[key];if(!spec)return;const article=element('article',undefined,'doctor-summary-chart');const title=element('h4',spec.title);const period=spec.period?.from&&spec.period?.to?spec.period:{from:data.from,to:data.to};const documentedPoints=spec.series.flatMap(item=>item.points).filter(point=>point[1]!==null&&point[1]!==undefined);const pointCount=documentedPoints.length;const documentedDays=spec.coverage?.observed_days??new Set(documentedPoints.map(point=>point[0])).size;const expectedDays=spec.coverage?.expected_days??Math.max(1,Math.round((new Date(`${period.to}T12:00:00Z`)-new Date(`${period.from}T12:00:00Z`))/86400000)+1);const gapCount=Math.max(0,expectedDays-documentedDays);article.dataset.reportChartKey=key;article.append(title,element('p',`Einheit: ${spec.unit||'Nicht dokumentiert'} · Zeitraum: ${period.from} bis ${period.to} · Punkte: ${pointCount} · Abdeckung: ${documentedDays}/${expectedDays} Tage · Datenlücken: ${gapCount} Tage · Kontext: ${spec.context}`,'v5-meta'),element('p','Zeitliche Beobachtung; keine Kausalitätsaussage.','doctor-summary-disclaimer'));const chart=element('div',undefined,'doctor-summary-echart');chart.setAttribute('role','img');chart.setAttribute('aria-label',`${spec.title}, ${period.from} bis ${period.to}`);article.append(chart);chartSection.append(article);draws.push({chart,spec,period});});setTimeout(()=>draws.forEach(({chart,spec,period})=>{if(!window.echarts||!chart.isConnected)return;const instance=window.echarts.init(chart,null,{renderer:'canvas'});const units=[...new Set(spec.series.map(item=>item.unit||spec.unit||''))];instance.setOption({animation:!window.matchMedia('(prefers-reduced-motion: reduce)').matches,animationDuration:window.matchMedia('(prefers-reduced-motion: reduce)').matches?0:280,animationDurationUpdate:window.matchMedia('(prefers-reduced-motion: reduce)').matches?0:240,aria:{enabled:true,description:`${spec.title}; dokumentierter Verlauf; keine Kausalitätsaussage.`},title:{text:spec.title,subtext:`${period.from} bis ${period.to}`},legend:{show:true,top:42},tooltip:{trigger:'axis'},grid:{left:64,right:36,top:84,bottom:48,containLabel:true},xAxis:{type:'time',name:'Datum'},yAxis:units.map((unit,index)=>({type:'value',name:unit||spec.unit||'',position:index?'right':'left',scale:true})),series:spec.series.flatMap((item,index)=>{const axis=Math.max(0,units.indexOf(item.unit||spec.unit||''));const main={name:item.name,type:'line',connectNulls:false,showSymbol:true,yAxisIndex:axis,data:item.points,markLine:{silent:true,symbol:'none',lineStyle:{type:'dashed',color:'#6b7280'},data:(spec.markers?.length?spec.markers:markers).map(marker=>({xAxis:marker.date,name:'Dokumentiertes Ereignis'}))}};const baseline=Array.isArray(item.baseline)&&item.baseline.length?{name:`${item.name} · persönliche Baseline`,type:'line',connectNulls:false,showSymbol:false,silent:true,yAxisIndex:axis,data:item.baseline,lineStyle:{type:'dashed',color:'#5c5484'}}:null;return baseline?[main,baseline]:[main];})});window.addEventListener('beforeprint',()=>instance.resize(),{once:true});}),0);} + };sessionChartSpecs.forEach((stored,index)=>{specs[`session_${index}`]={title:String(stored.title||'Explorer-Grafik'),unit:String(stored.unit||''),context:String(stored.context||'Zeitliche Beobachtung ohne Kausalitätsaussage'),period:stored.period||null,coverage:stored.coverage||null,markers:Array.isArray(stored.markers)?stored.markers:[],series:Array.isArray(stored.series)?stored.series.map(item=>({name:String(item.name||'Dokumentierter Wert'),unit:String(item.unit||stored.unit||''),points:Array.isArray(item.points)?item.points:[],baseline:Array.isArray(item.baseline)?item.baseline:[]})):[]};});const effectiveMedicationMarkers=[...(data.medications?.administered||[]),...(data.medications?.corrected||[])].filter(item=>!item.superseded_by_correction&&(item.status==='administered'||(item.status==='corrected'&&item.effective_status==='administered'))).map(item=>({date:item.date,label:'Medikament'}));const markers=[...effectiveMedicationMarkers,...(data.supplements?.administered||[]).map(item=>({date:item.date,label:'Supplement'}))];const draws=[];selectedChartKeys.forEach(key=>{const spec=specs[key];if(!spec)return;const article=element('article',undefined,'doctor-summary-chart');const title=element('h4',spec.title);const period=spec.period?.from&&spec.period?.to?spec.period:{from:data.from,to:data.to};const documentedPoints=spec.series.flatMap(item=>item.points).filter(point=>point[1]!==null&&point[1]!==undefined);const pointCount=documentedPoints.length;const documentedDays=spec.coverage?.observed_days??new Set(documentedPoints.map(point=>point[0])).size;const expectedDays=spec.coverage?.expected_days??Math.max(1,Math.round((new Date(`${period.to}T12:00:00Z`)-new Date(`${period.from}T12:00:00Z`))/86400000)+1);const gapCount=Math.max(0,expectedDays-documentedDays);article.dataset.reportChartKey=key;article.append(title,element('p',`Einheit: ${spec.unit||'Nicht dokumentiert'} · Zeitraum: ${period.from} bis ${period.to} · Punkte: ${pointCount} · Abdeckung: ${documentedDays}/${expectedDays} Tage · Datenlücken: ${gapCount} Tage · Kontext: ${spec.context}`,'v5-meta'),element('p','Zeitliche Beobachtung; keine Kausalitätsaussage.','doctor-summary-disclaimer'));const chart=element('div',undefined,'doctor-summary-echart');chart.setAttribute('role','img');chart.setAttribute('aria-label',`${spec.title}, ${period.from} bis ${period.to}`);article.append(chart);chartSection.append(article);draws.push({chart,spec,period});});setTimeout(()=>draws.forEach(({chart,spec,period})=>{if(!window.echarts||!chart.isConnected)return;const instance=window.echarts.init(chart,null,{renderer:'canvas'});const units=[...new Set(spec.series.map(item=>item.unit||spec.unit||''))];instance.setOption({animation:!window.matchMedia('(prefers-reduced-motion: reduce)').matches,animationDuration:window.matchMedia('(prefers-reduced-motion: reduce)').matches?0:280,animationDurationUpdate:window.matchMedia('(prefers-reduced-motion: reduce)').matches?0:240,aria:{enabled:true,description:`${spec.title}; dokumentierter Verlauf; keine Kausalitätsaussage.`},title:{text:spec.title,subtext:`${period.from} bis ${period.to}`},legend:{show:true,top:42},tooltip:{trigger:'axis'},grid:{left:64,right:36,top:84,bottom:48,containLabel:true},xAxis:{type:'time',name:'Datum'},yAxis:units.map((unit,index)=>({type:'value',name:unit||spec.unit||'',position:index?'right':'left',scale:true})),series:spec.series.flatMap((item,index)=>{const axis=Math.max(0,units.indexOf(item.unit||spec.unit||''));const main={name:item.name,type:'line',connectNulls:false,showSymbol:true,yAxisIndex:axis,data:item.points,markLine:{silent:true,symbol:'none',lineStyle:{type:'dashed',color:'#6b7280'},data:(spec.markers?.length?spec.markers:markers).map(marker=>({xAxis:marker.date,name:'Dokumentiertes Ereignis'}))}};const baseline=Array.isArray(item.baseline)&&item.baseline.length?{name:`${item.name} · persönliche Baseline`,type:'line',connectNulls:false,showSymbol:false,silent:true,yAxisIndex:axis,data:item.baseline,lineStyle:{type:'dashed',color:'#5c5484'}}:null;return baseline?[main,baseline]:[main];})});window.addEventListener('beforeprint',()=>instance.resize(),{once:true});}),0);} const print=button('Drucken / als PDF speichern',()=>window.print());print.className='doctor-summary-print';node.append(printable,print);return node; } __HERMES_CWD_8d46a20096ed__/home/agent/.hermes/repos/HealthManager__HERMES_CWD_8d46a20096ed__