100100 "location" : "Las Vegas, NV" ,
101101 "lat" : 36.1699 ,
102102 "lon" : - 115.1398 ,
103- "start_date" : "2027 -01-27 " ,
104- "end_date" : "2027 -01-29 " ,
103+ "start_date" : "2026 -01-28 " ,
104+ "end_date" : "2026 -01-30 " ,
105105 "abstract_open" : "" ,
106106 "abstract_close" : "" ,
107- "abstract_note" : "2027 dates TBD ; check IMARI website" ,
107+ "abstract_note" : "Dates may change ; check IMARI website for updates " ,
108108 "type" : "Conference" ,
109109 "url" : "https://imari.org" ,
110110 "projects" : [
180180 "lat" : 39.9526 ,
181181 "lon" : - 75.1652 ,
182182 "start_date" : "2027-02-20" ,
183- "end_date" : "2027-02-24 " ,
183+ "end_date" : "2027-02-20 " ,
184184 "abstract_open" : "" ,
185185 "abstract_close" : "" ,
186186 "abstract_note" : "Abstract dates TBD; see Biophysical Society website for updates" ,
199199 "location" : "San Diego, CA" ,
200200 "lat" : 32.7157 ,
201201 "lon" : - 117.1611 ,
202- "start_date" : "2027 -03-21 " ,
203- "end_date" : "2027 -03-24" ,
202+ "start_date" : "2026 -03-24 " ,
203+ "end_date" : "2026 -03-24" ,
204204 "abstract_open" : "" ,
205205 "abstract_close" : "" ,
206206 "abstract_note" : "Abstract dates TBD; check CROI website" ,
359359 "location" : "Philadelphia, PA" ,
360360 "lat" : 39.9526 ,
361361 "lon" : - 75.1652 ,
362- "start_date" : "2026-05-17 " ,
363- "end_date" : "2026-05-20 " ,
362+ "start_date" : "2026-09-22 " ,
363+ "end_date" : "2026-09-23 " ,
364364 "abstract_open" : "2025-10-31" ,
365365 "abstract_close" : "2026-01-09" ,
366366 "abstract_note" : "" ,
379379 "location" : "Washington, DC" ,
380380 "lat" : 38.9072 ,
381381 "lon" : - 77.0369 ,
382- "start_date" : "2026 -06-11 " ,
383- "end_date" : "2026 -06-15 " ,
382+ "start_date" : "2027 -06-22 " ,
383+ "end_date" : "2027 -06-25 " ,
384384 "abstract_open" : "2025-10-15" ,
385385 "abstract_close" : "2026-01-21" ,
386386 "abstract_note" : "Travel awards: Oct 15 - Dec 2, 2025; Abstracts: Dec 2, 2025 - Jan 21, 2026" ,
441441 "lat" : 32.9342 ,
442442 "lon" : - 97.0781 ,
443443 "start_date" : "2026-07-18" ,
444- "end_date" : "2026-07-21 " ,
444+ "end_date" : "2026-07-18 " ,
445445 "abstract_open" : "" ,
446446 "abstract_close" : "" ,
447447 "abstract_note" : "" ,
551551 "location" : "Houston, TX" ,
552552 "lat" : 29.7174 ,
553553 "lon" : - 95.4018 ,
554- "start_date" : "2026-09-14 " ,
554+ "start_date" : "2026-09-15 " ,
555555 "end_date" : "2026-09-17" ,
556556 "abstract_open" : "" ,
557557 "abstract_close" : "" ,
588588 "location" : "Lisbon, Portugal" ,
589589 "lat" : 38.7223 ,
590590 "lon" : - 9.1393 ,
591- "start_date" : "2026-09-22 " ,
592- "end_date" : "2026-09-24 " ,
591+ "start_date" : "2026-04-26 " ,
592+ "end_date" : "2026-04-26 " ,
593593 "abstract_open" : "2026-03-10" ,
594594 "abstract_close" : "2026-03-31" ,
595595 "abstract_note" : "" ,
@@ -679,7 +679,6 @@ def _format_conference_date(date_str, conference_name, field_name):
679679 f"Invalid { field_name } date { date_str !r} for conference { conference_name !r} in research/_conferences_data.py"
680680 ) from exc
681681
682-
683682def format_conference_abstract_deadline (conference ):
684683 if conference .get ("abstract_open" ) and conference .get ("abstract_close" ):
685684 open_date = _format_conference_date (conference ["abstract_open" ], conference ["name" ], "abstract_open" )
@@ -697,6 +696,59 @@ def format_conference_abstract_deadline(conference):
697696 "name" : conf ["name" ],
698697 "abstract_display" : format_conference_abstract_deadline (conf ),
699698 }
700- for conf in conferences
699+ for conf in sorted ( conferences , key = lambda c : c . get ( "start_date" ) or "" )
701700 if conf .get ("url" )
702701}
702+
703+
704+ def _validate_conferences (conf_list ):
705+ """Run light validation on conference entries and raise helpful errors.
706+
707+ Checks performed:
708+ - `start_date` and `end_date` parse as YYYY-MM-DD when present
709+ - `start_date` is not after `end_date` when both present
710+ - `lat` and `lon` are numeric when present
711+ - `url` is present and non-empty
712+ """
713+ for conf in conf_list :
714+ name = conf .get ("name" , "(unknown)" )
715+
716+ # Dates
717+ sd = conf .get ("start_date" )
718+ ed = conf .get ("end_date" )
719+ try :
720+ if sd :
721+ sd_dt = datetime .strptime (sd , "%Y-%m-%d" )
722+ else :
723+ sd_dt = None
724+ if ed :
725+ ed_dt = datetime .strptime (ed , "%Y-%m-%d" )
726+ else :
727+ ed_dt = None
728+ except ValueError as exc :
729+ raise ValueError (f"Invalid date for conference { name !r} : { exc } " ) from exc
730+
731+ if sd_dt and ed_dt and sd_dt > ed_dt :
732+ raise ValueError (
733+ f"Conference { name !r} has start_date { sd !r} after end_date { ed !r} "
734+ )
735+
736+ # Coordinates
737+ lat = conf .get ("lat" )
738+ lon = conf .get ("lon" )
739+ try :
740+ if lat is not None :
741+ float (lat )
742+ if lon is not None :
743+ float (lon )
744+ except (TypeError , ValueError ) as exc :
745+ raise ValueError (f"Invalid coordinates for conference { name !r} : { exc } " ) from exc
746+
747+ # URL
748+ url = conf .get ("url" )
749+ if not url :
750+ raise ValueError (f"Conference { name !r} is missing a URL field" )
751+
752+
753+ # Run validation at import time so problems are visible early
754+ _validate_conferences (conferences )
0 commit comments