@@ -63,11 +63,21 @@ export interface CongressTrade {
6363 * "250K–500K", "500K–1M", "1M–5M", "5M–25M", "25M–50M"
6464 */
6565export const parseAmountRange = ( range : string ) : number => {
66+ return parseAmountRangeBounds ( range ) . lower ;
67+ } ;
68+
69+ const parseAmountRangeBounds = (
70+ range : string ,
71+ ) : { lower : number ; upper : number } => {
6672 const cleaned = range . replace ( / [ $ , \s ] / g, "" ) ;
67- // Extract the lower bound (before the dash/en-dash)
68- const lowerStr = cleaned . split ( / [ – - ] / ) [ 0 ] ?. trim ( ) ;
69- if ( ! lowerStr ) return 0 ;
70- return parseAmountValue ( lowerStr ) ;
73+ const [ lowerStr , upperStr ] = cleaned . split ( / [ – - ] / ) . map ( ( part ) => part . trim ( ) ) ;
74+ if ( ! lowerStr ) return { lower : 0 , upper : 0 } ;
75+
76+ const lower = parseAmountValue ( lowerStr ) ;
77+ return {
78+ lower,
79+ upper : upperStr ? parseAmountValue ( upperStr ) : lower ,
80+ } ;
7181} ;
7282
7383export const parseAmountValue = ( value : string ) : number => {
@@ -486,6 +496,13 @@ const formatDate = (date: Date): string => {
486496 return date . toLocaleDateString ( "en-US" , { month : "short" , day : "numeric" } ) ;
487497} ;
488498
499+ const formatDateSummary = ( dates : Date [ ] ) : string => {
500+ const formatted = [ ...new Set ( dates . map ( formatDate ) ) ] ;
501+ if ( formatted . length === 0 ) return "" ;
502+ if ( formatted . length === 1 ) return formatted [ 0 ] ?? "" ;
503+ return `${ formatted [ 0 ] } –${ formatted . at ( - 1 ) } ` ;
504+ } ;
505+
489506const formatPartyState = ( trade : CongressTrade ) : string => {
490507 return `${ trade . party } -${ trade . state } ` ;
491508} ;
@@ -552,7 +569,13 @@ export const deduplicateTrades = (
552569 const groups = new Map < string , CongressTrade [ ] > ( ) ;
553570
554571 for ( const trade of trades ) {
555- const key = `${ trade . politician } |${ trade . ticker } |${ trade . type } ` ;
572+ const key = [
573+ trade . politician ,
574+ trade . ticker ,
575+ trade . type ,
576+ trade . tradeDate . toISOString ( ) . split ( "T" ) [ 0 ] ,
577+ trade . disclosureDate . toISOString ( ) . split ( "T" ) [ 0 ] ,
578+ ] . join ( "|" ) ;
556579 const group = groups . get ( key ) ;
557580 if ( group ) {
558581 group . push ( trade ) ;
@@ -599,17 +622,19 @@ export const deduplicateTrades = (
599622} ;
600623
601624const formatGroupedAmount = ( trades : CongressTrade [ ] ) : string => {
602- const amounts = trades . map ( ( t ) => t . amountLower ) . sort ( ( a , b ) => a - b ) ;
603- const low = amounts . at ( 0 ) ;
604- const high = amounts . at ( - 1 ) ;
605- if ( low === undefined || high === undefined )
625+ const ranges = trades . map ( ( t ) => parseAmountRangeBounds ( t . amountRange ) ) ;
626+ const low = ranges . reduce ( ( sum , range ) => sum + range . lower , 0 ) ;
627+ const high = ranges . reduce ( ( sum , range ) => sum + range . upper , 0 ) ;
628+ if ( low === 0 && high === 0 )
606629 return formatAmountDisplay ( trades [ 0 ] ?. amountRange ?? "" ) ;
607- if ( low === high ) return formatAmountDisplay ( trades . at ( 0 ) ?. amountRange ?? "" ) ;
608630 return `$${ formatCompactAmount ( low ) } –$${ formatCompactAmount ( high ) } total` ;
609631} ;
610632
611633const formatCompactAmount = ( amount : number ) : string => {
612- if ( amount >= 1_000_000 ) return `${ ( amount / 1_000_000 ) . toFixed ( 0 ) } M` ;
634+ if ( amount >= 1_000_000 ) {
635+ const millions = amount / 1_000_000 ;
636+ return `${ Number . isInteger ( millions ) ? millions . toFixed ( 0 ) : millions . toFixed ( 1 ) } M` ;
637+ }
613638 if ( amount >= 1_000 ) return `${ ( amount / 1_000 ) . toFixed ( 0 ) } K` ;
614639 return String ( amount ) ;
615640} ;
@@ -632,7 +657,18 @@ export const formatDeduplicatedItem = (
632657 const committeeLine = entry . committeeRelevance
633658 ? `${ entry . committeeRelevance . committee } · `
634659 : "" ;
635- const detail = `${ committeeLine } Combined from ${ entry . count } transactions` ;
660+ const tradeDates = formatDateSummary (
661+ entry . trades . map ( ( trade ) => trade . tradeDate ) ,
662+ ) ;
663+ const disclosureDates = formatDateSummary (
664+ entry . trades . map ( ( trade ) => trade . disclosureDate ) ,
665+ ) ;
666+ const detailParts = [
667+ `Combined from ${ entry . count } transactions` ,
668+ tradeDates ? `traded ${ tradeDates } ` : "" ,
669+ disclosureDates ? `filed ${ disclosureDates } ` : "" ,
670+ ] . filter ( Boolean ) ;
671+ const detail = `${ committeeLine } ${ detailParts . join ( " · " ) } ` ;
636672 return {
637673 text,
638674 detail,
0 commit comments