@@ -44,6 +44,8 @@ import type { MapContainer } from './MapContainer';
4444import { dedupeHeadlines } from './CountryDeepDivePanel-news-utils' ;
4545import { renderFollowButton } from '@/utils/follow-button' ;
4646import { renderNotifyCountryLink } from '@/utils/notify-country-link' ;
47+ import { exportCountryEvidenceMarkdown } from '@/utils/export' ;
48+ import type { CountryEvidenceBundleInput } from '@/utils/export' ;
4749
4850const DEPENDENCY_FLAG_LABELS : Record < string , { text : string ; cls : string } > = {
4951 DEPENDENCY_FLAG_SINGLE_SOURCE_CRITICAL : { text : 'Single Source' , cls : 'cdp-dep-critical' } ,
@@ -96,6 +98,12 @@ export class CountryDeepDivePanel implements CountryBriefPanel {
9698 private closeButton : HTMLButtonElement ;
9799 private currentCode : string | null = null ;
98100 private currentName : string | null = null ;
101+ private currentScore : CountryScore | null = null ;
102+ private currentSignals : CountryBriefSignals | null = null ;
103+ private currentBrief : string | null = null ;
104+ private currentBriefGeneratedAt : string | null = null ;
105+ private currentBriefCached : boolean | null = null ;
106+ private currentHeadlines : NewsItem [ ] = [ ] ;
99107 private isMaximizedState = false ;
100108 private onCloseCallback ?: ( ) => void ;
101109 private onStateChangeCallback ?: ( state : { visible : boolean ; maximized : boolean } ) => void ;
@@ -261,6 +269,13 @@ export class CountryDeepDivePanel implements CountryBriefPanel {
261269 this . abortController = new AbortController ( ) ;
262270 this . currentCode = code ;
263271 this . currentName = country ;
272+ this . currentScore = score ;
273+ this . currentSignals = signals ;
274+ this . currentBrief = null ;
275+ this . currentBriefGeneratedAt = null ;
276+ this . currentBriefCached = null ;
277+ this . currentHeadlines = [ ] ;
278+ this . currentHeadlineCount = 0 ;
264279 this . economicIndicators = [ ] ;
265280 this . infrastructureByType . clear ( ) ;
266281 this . renderSkeleton ( country , code , score , signals ) ;
@@ -280,6 +295,12 @@ export class CountryDeepDivePanel implements CountryBriefPanel {
280295 this . close ( ) ;
281296 this . currentCode = null ;
282297 this . currentName = null ;
298+ this . currentScore = null ;
299+ this . currentSignals = null ;
300+ this . currentBrief = null ;
301+ this . currentBriefGeneratedAt = null ;
302+ this . currentBriefCached = null ;
303+ this . currentHeadlines = [ ] ;
283304 this . onCloseCallback ?.( ) ;
284305 this . onStateChangeCallback ?.( { visible : false , maximized : false } ) ;
285306 }
@@ -337,6 +358,7 @@ export class CountryDeepDivePanel implements CountryBriefPanel {
337358 public updateNews ( headlines : NewsItem [ ] ) : void {
338359 if ( ! this . newsBody ) return ;
339360 this . newsBody . replaceChildren ( ) ;
361+ this . currentHeadlines = [ ] ;
340362
341363 const compare = ( a : NewsItem , b : NewsItem ) => {
342364 const sa = SEVERITY_ORDER [ this . toThreatLevel ( a . threat ?. level ) ] ;
@@ -352,6 +374,7 @@ export class CountryDeepDivePanel implements CountryBriefPanel {
352374 . slice ( 0 , 10 ) ;
353375
354376 this . currentHeadlineCount = deduped . length ;
377+ this . currentHeadlines = deduped . map ( ( { item } ) => item ) ;
355378
356379 if ( deduped . length === 0 ) {
357380 this . newsBody . append ( this . makeEmpty ( t ( 'countryBrief.noNews' ) ) ) ;
@@ -2309,10 +2332,17 @@ export class CountryDeepDivePanel implements CountryBriefPanel {
23092332 this . briefBody . replaceChildren ( ) ;
23102333
23112334 if ( data . error || data . skipped || ! data . brief ) {
2335+ this . currentBrief = null ;
2336+ this . currentBriefGeneratedAt = null ;
2337+ this . currentBriefCached = null ;
23122338 this . briefBody . append ( this . makeEmpty ( data . error || data . reason || t ( 'countryBrief.assessmentUnavailable' ) ) ) ;
23132339 return ;
23142340 }
23152341
2342+ this . currentBrief = data . brief ;
2343+ this . currentBriefGeneratedAt = data . generatedAt ?? null ;
2344+ this . currentBriefCached = data . cached === true ;
2345+
23162346 const summaryHtml = this . formatBrief ( this . summarizeBrief ( data . brief ) , 0 ) ;
23172347 const text = this . el ( 'div' , 'cdp-assessment-text cdp-summary-only' ) ;
23182348 setTrustedHtml ( text , trustedHtml ( summaryHtml , "legacy direct innerHTML migration" ) ) ;
@@ -2424,7 +2454,13 @@ export class CountryDeepDivePanel implements CountryBriefPanel {
24242454 this . onExportImage ( this . currentCode , this . currentName ) ;
24252455 }
24262456 } ) ;
2427- right . append ( shareBtn , maxBtn , storyButton , exportButton ) ;
2457+ const evidenceButton = this . el ( 'button' , 'cdp-action-btn cdp-evidence-export-btn' , 'Evidence' ) as HTMLButtonElement ;
2458+ evidenceButton . setAttribute ( 'type' , 'button' ) ;
2459+ evidenceButton . setAttribute ( 'title' , 'Export evidence bundle as Markdown' ) ;
2460+ evidenceButton . addEventListener ( 'click' , ( ) => {
2461+ this . exportEvidenceBundle ( ) ;
2462+ } ) ;
2463+ right . append ( shareBtn , maxBtn , storyButton , exportButton , evidenceButton ) ;
24282464 header . append ( left , right ) ;
24292465
24302466 const scoreCard = this . el ( 'section' , 'cdp-card cdp-score-card' ) ;
@@ -2918,6 +2954,67 @@ export class CountryDeepDivePanel implements CountryBriefPanel {
29182954 return formatIntelBrief ( text , headlineCount > 0 ? { count : headlineCount , hrefPrefix : '#cdp-news-' } : undefined ) ;
29192955 }
29202956
2957+ private exportEvidenceBundle ( ) : void {
2958+ if ( ! this . currentCode || ! this . currentName ) return ;
2959+ const exportedAt = new Date ( ) . toISOString ( ) ;
2960+ const data : CountryEvidenceBundleInput = {
2961+ country : this . currentName ,
2962+ code : this . currentCode ,
2963+ context : 'Country dossier' ,
2964+ generatedAt : exportedAt ,
2965+ exportedAt,
2966+ } ;
2967+
2968+ if ( this . currentScore ) {
2969+ data . score = this . currentScore . score ;
2970+ data . level = this . currentScore . level ;
2971+ data . trend = this . currentScore . trend ;
2972+ data . components = this . currentScore . components ;
2973+ }
2974+ if ( this . currentSignals ) {
2975+ data . signals = {
2976+ criticalNews : this . currentSignals . criticalNews ,
2977+ protests : this . currentSignals . protests ,
2978+ militaryFlights : this . currentSignals . militaryFlights ,
2979+ militaryVessels : this . currentSignals . militaryVessels ,
2980+ militaryFlightsInCountry : this . currentSignals . militaryFlightsInCountry ,
2981+ militaryVesselsInCountry : this . currentSignals . militaryVesselsInCountry ,
2982+ outages : this . currentSignals . outages ,
2983+ aisDisruptions : this . currentSignals . aisDisruptions ,
2984+ satelliteFires : this . currentSignals . satelliteFires ,
2985+ radiationAnomalies : this . currentSignals . radiationAnomalies ,
2986+ temporalAnomalies : this . currentSignals . temporalAnomalies ,
2987+ cyberThreats : this . currentSignals . cyberThreats ,
2988+ earthquakes : this . currentSignals . earthquakes ,
2989+ displacementOutflow : this . currentSignals . displacementOutflow ,
2990+ climateStress : this . currentSignals . climateStress ,
2991+ conflictEvents : this . currentSignals . conflictEvents ,
2992+ activeStrikes : this . currentSignals . activeStrikes ,
2993+ orefSirens : this . currentSignals . orefSirens ,
2994+ orefHistory24h : this . currentSignals . orefHistory24h ,
2995+ aviationDisruptions : this . currentSignals . aviationDisruptions ,
2996+ travelAdvisories : this . currentSignals . travelAdvisories ,
2997+ travelAdvisoryMaxLevel : this . currentSignals . travelAdvisoryMaxLevel ,
2998+ gpsJammingHexes : this . currentSignals . gpsJammingHexes ,
2999+ thermalEscalations : this . currentSignals . thermalEscalations ,
3000+ sanctionsDesignations : this . currentSignals . sanctionsDesignations ,
3001+ sanctionsNewDesignations : this . currentSignals . sanctionsNewDesignations ,
3002+ } ;
3003+ }
3004+ if ( this . currentBrief ) data . brief = this . currentBrief ;
3005+ if ( this . currentBriefGeneratedAt ) data . briefGeneratedAt = this . currentBriefGeneratedAt ;
3006+ if ( this . currentBriefCached != null ) data . briefCached = this . currentBriefCached ;
3007+ if ( this . currentHeadlines . length > 0 ) {
3008+ data . headlines = this . currentHeadlines . map ( ( headline ) => ( {
3009+ title : headline . title ,
3010+ source : headline . source ,
3011+ link : headline . link ,
3012+ pubDate : headline . pubDate ? new Date ( headline . pubDate ) . toISOString ( ) : undefined ,
3013+ } ) ) ;
3014+ }
3015+ exportCountryEvidenceMarkdown ( data ) ;
3016+ }
3017+
29213018 private summarizeBrief ( brief : string ) : string {
29223019 const stripped = brief . replace ( / \* \* ( .* ?) \* \* / g, '$1' ) ;
29233020 const lines = stripped . split ( '\n' ) . map ( ( l ) => l . trim ( ) ) . filter ( ( l ) => l . length > 0 ) ;
0 commit comments