diff --git a/src/soa_builder/web/app.py b/src/soa_builder/web/app.py
index b4db60b..19b30bd 100644
--- a/src/soa_builder/web/app.py
+++ b/src/soa_builder/web/app.py
@@ -62,6 +62,7 @@
_migrate_add_epoch_type,
_migrate_visit_columns,
_migrate_timing_add_member_of_timeline,
+ _migrate_instances_add_member_of_timeline,
)
from .routers import activities as activities_router
from .routers import arms as arms_router
@@ -161,6 +162,7 @@ def _configure_logging():
# Database migration steps
+_migrate_instances_add_member_of_timeline()
_migrate_timing_add_member_of_timeline()
_migrate_visit_columns()
_migrate_add_epoch_type()
diff --git a/src/soa_builder/web/migrate_database.py b/src/soa_builder/web/migrate_database.py
index fb10b8f..14a2db2 100644
--- a/src/soa_builder/web/migrate_database.py
+++ b/src/soa_builder/web/migrate_database.py
@@ -941,3 +941,25 @@ def _migrate_timing_add_member_of_timeline():
conn.close()
except Exception as e: # pragma: no cover
logger.warning("timing member_of_timeline migration failed: %s", e)
+
+
+def _migrate_instances_add_member_of_timeline():
+ """Add optional member_of_timeline"""
+ try:
+ conn = _connect()
+ cur = conn.cursor()
+ cur.execute(
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='instances'"
+ )
+ if not cur.fetchone():
+ conn.close()
+ return
+ cur.execute("PRAGMA table_info(instances)")
+ cols = {r[1] for r in cur.fetchall()}
+ if "member_of_timeline" not in cols:
+ cur.execute("ALTER TABLE instances ADD COLUMN member_of_timeline TEXT")
+ conn.commit()
+ logger.info("Added member_of_timeline column to instances table")
+ conn.close()
+ except Exception as e:
+ logger.warning("instances member_of_timeline migration failed: %s", e)
diff --git a/src/soa_builder/web/routers/instances.py b/src/soa_builder/web/routers/instances.py
index 3bb9562..c699ecc 100644
--- a/src/soa_builder/web/routers/instances.py
+++ b/src/soa_builder/web/routers/instances.py
@@ -9,7 +9,7 @@
from ..audit import _record_instance_audit
from ..db import _connect
from ..schemas import InstanceCreate, InstanceUpdate
-from ..utils import soa_exists, get_encounter_id, get_epoch_uid
+from ..utils import soa_exists, get_encounter_id, get_epoch_uid, get_schedule_timeline
router = APIRouter()
logger = logging.getLogger("soa_builder.web.routers.instances")
@@ -33,7 +33,7 @@ def list_instances(soa_id: int):
cur = conn.cursor()
cur.execute(
"SELECT id,instance_uid,name,label,description,default_condition_uid,epoch_uid,timeline_id,"
- "timeline_exit_id,order_index,encounter_uid FROM instances WHERE soa_id=? ORDER BY order_index,id",
+ "timeline_exit_id,order_index,encounter_uid,member_of_timeline FROM instances WHERE soa_id=? ORDER BY order_index,id",
(soa_id,),
)
rows = [
@@ -49,6 +49,7 @@ def list_instances(soa_id: int):
"timeline_exit_id": r[8],
"order_index": r[9],
"encounter_uid": r[10],
+ "member_of_timeline": r[11],
}
for r in cur.fetchall()
]
@@ -65,6 +66,8 @@ def ui_list_instances(request: Request, soa_id: int):
instances = list_instances(soa_id)
encounter_options = get_encounter_id(soa_id)
epoch_options = get_epoch_uid(soa_id)
+ schedule_timelines_options = get_schedule_timeline(soa_id)
+
return templates.TemplateResponse(
"instances.html",
{
@@ -73,6 +76,7 @@ def ui_list_instances(request: Request, soa_id: int):
"instances": instances,
"encounter_options": encounter_options,
"epoch_options": epoch_options,
+ "schedule_timelines_options": schedule_timelines_options,
},
)
@@ -120,7 +124,7 @@ def create_instance(soa_id: int, payload: InstanceCreate):
new_uid = f"ScheduledActivityInstance_{next_n}"
cur.execute(
"INSERT INTO instances (soa_id,instance_uid,name,label,description,default_condition_uid,epoch_uid,"
- "timeline_id,timeline_exit_id,order_index,encounter_uid) VALUES (?,?,?,?,?,?,?,?,?,?,?)",
+ "timeline_id,timeline_exit_id,order_index,encounter_uid,member_of_timeline) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
(
soa_id,
new_uid,
@@ -133,6 +137,7 @@ def create_instance(soa_id: int, payload: InstanceCreate):
_nz(payload.timeline_exit_id),
next_ord,
_nz(payload.encounter_uid),
+ _nz(payload.member_of_timeline),
),
)
instance_id = cur.lastrowid
@@ -144,6 +149,12 @@ def create_instance(soa_id: int, payload: InstanceCreate):
"name": name,
"label": (payload.label or "").strip() or None,
"description": (payload.description or "").strip() or None,
+ "default_condition_uid": (payload.default_condition_uid or "").strip() or None,
+ "epoch_uid": (payload.epoch_uid or "").strip() or None,
+ "timeline_id": (payload.timeline_id or "").strip() or None,
+ "timeline_exit_id": (payload.timeline_exit_id or "").strip() or None,
+ "encounter_uid": (payload.encounter_uid or "").strip() or None,
+ "member_of_timeline": (payload.member_of_timeline or "").strip() or None,
}
_record_instance_audit(soa_id, "create", instance_id, before=None, after=after)
@@ -163,6 +174,7 @@ def ui_create_instance(
timeline_id: Optional[str] = Form(None),
timeline_exit_id: Optional[str] = Form(None),
encounter_uid: Optional[str] = Form(None),
+ member_of_timeline: Optional[str] = Form(None),
):
payload = InstanceCreate(
name=name,
@@ -173,6 +185,7 @@ def ui_create_instance(
timeline_id=timeline_id,
timeline_exit_id=timeline_exit_id,
encounter_uid=encounter_uid,
+ member_of_timeline=member_of_timeline,
)
create_instance(soa_id, payload)
return RedirectResponse(url=f"/ui/soa/{int(soa_id)}/instances", status_code=303)
@@ -192,7 +205,7 @@ def update_instance(soa_id: int, instance_id: int, payload: InstanceUpdate):
cur = conn.cursor()
cur.execute(
"SELECT id,instance_uid,name,label,description,default_condition_uid, epoch_uid,"
- "timeline_id,timeline_exit_id,order_index,encounter_uid from instances WHERE soa_id=? and id=?",
+ "timeline_id,timeline_exit_id,order_index,encounter_uid,member_of_timeline from instances WHERE soa_id=? and id=?",
(
soa_id,
instance_id,
@@ -215,6 +228,7 @@ def update_instance(soa_id: int, instance_id: int, payload: InstanceUpdate):
"timeline_exit_id": row[8],
"order_index": row[9],
"encounter_uid": row[10],
+ "member_of_timeline": row[11],
}
new_name = (payload.name if payload.name is not None else before["name"]) or ""
new_label = payload.label if payload.label is not None else before["label"]
@@ -246,10 +260,15 @@ def update_instance(soa_id: int, instance_id: int, payload: InstanceUpdate):
if payload.encounter_uid is not None
else before["encounter_uid"]
)
+ new_member_of_timeline = (
+ payload.member_of_timeline
+ if payload.member_of_timeline is not None
+ else before["member_of_timeline"]
+ )
cur.execute(
"UPDATE instances SET name=?, label=?, description=?, default_condition_uid=?, epoch_uid=?, "
- "timeline_id=?, timeline_exit_id=?, encounter_uid=? WHERE id=? and soa_id=?",
+ "timeline_id=?, timeline_exit_id=?, encounter_uid=?, member_of_timeline=? WHERE id=? and soa_id=?",
(
_nz(new_name),
_nz(new_label),
@@ -259,6 +278,7 @@ def update_instance(soa_id: int, instance_id: int, payload: InstanceUpdate):
_nz(new_timeline_id),
_nz(new_timeline_exit_id),
_nz(new_encounter_uid),
+ _nz(new_member_of_timeline),
instance_id,
soa_id,
),
@@ -266,7 +286,7 @@ def update_instance(soa_id: int, instance_id: int, payload: InstanceUpdate):
conn.commit()
cur.execute(
"SELECT id,instance_uid,name,label,description,default_condition_uid,epoch_uid,timeline_id,"
- "timeline_exit_id,order_index,encounter_uid FROM instances WHERE soa_id=? and id=?",
+ "timeline_exit_id,order_index,encounter_uid,member_of_timeline FROM instances WHERE soa_id=? and id=?",
(
soa_id,
instance_id,
@@ -286,6 +306,7 @@ def update_instance(soa_id: int, instance_id: int, payload: InstanceUpdate):
"timeline_exit_id": r[8],
"order_index": r[9],
"encounter_uid": r[10],
+ "member_of_timeline": r[11],
}
mutable = [
"name",
@@ -296,6 +317,7 @@ def update_instance(soa_id: int, instance_id: int, payload: InstanceUpdate):
"timeline_id",
"timeline_exit_id",
"encounter_uid",
+ "member_of_timeline",
]
update_fields = [
f for f in mutable if (before.get(f) or None) != (after.get(f) or None)
@@ -324,6 +346,7 @@ def ui_update_instance(
timeline_id: Optional[str] = Form(None),
timeline_exit_id: Optional[str] = Form(None),
encounter_uid: Optional[str] = Form(None),
+ member_of_timeline: Optional[str] = Form(None),
):
payload = InstanceUpdate(
name=name,
@@ -334,6 +357,7 @@ def ui_update_instance(
timeline_id=timeline_id,
timeline_exit_id=timeline_exit_id,
encounter_uid=encounter_uid,
+ member_of_timeline=member_of_timeline,
)
update_instance(soa_id, instance_id, payload)
return RedirectResponse(url=f"/ui/soa/{int(soa_id)}/instances", status_code=303)
diff --git a/src/soa_builder/web/schemas.py b/src/soa_builder/web/schemas.py
index e82ab5f..041e366 100644
--- a/src/soa_builder/web/schemas.py
+++ b/src/soa_builder/web/schemas.py
@@ -12,6 +12,7 @@ class InstanceUpdate(BaseModel):
timeline_id: Optional[str] = None
timeline_exit_id: Optional[str] = None
encounter_uid: Optional[str] = None
+ member_of_timeline: Optional[str] = None
class InstanceCreate(BaseModel):
@@ -23,6 +24,7 @@ class InstanceCreate(BaseModel):
timeline_id: Optional[str] = None
timeline_exit_id: Optional[str] = None
encounter_uid: Optional[str] = None
+ member_of_timeline: Optional[str] = None
class TimingCreate(BaseModel):
diff --git a/src/soa_builder/web/templates/instances.html b/src/soa_builder/web/templates/instances.html
index 2033a64..43293c8 100644
--- a/src/soa_builder/web/templates/instances.html
+++ b/src/soa_builder/web/templates/instances.html
@@ -6,23 +6,23 @@
Scheduled Activity Instances for SoA {{ soa_id }}
@@ -62,6 +71,7 @@ Scheduled Activity Instances for SoA {{ soa_id }}
Timeline ID |
Timeline Exit ID |
Encounter |
+ Member of Timeline |
Save |
Delete Instance |
@@ -91,6 +101,14 @@ Scheduled Activity Instances for SoA {{ soa_id }}
{% endfor %}
+
+
+ |
|
diff --git a/src/usdm/generate_schedule_timelines.py b/src/usdm/generate_schedule_timelines.py
new file mode 100644
index 0000000..7f0c317
--- /dev/null
+++ b/src/usdm/generate_schedule_timelines.py
@@ -0,0 +1,263 @@
+#!/usr/bin/env python3
+# Prefer absolute import; fallback to adding src/ to sys.path when run directly
+from typing import Optional, List, Dict, Any, Callable, Set
+import logging
+
+try:
+ from soa_builder.web.app import _connect # reuse existing DB connector
+except ImportError:
+ import sys
+ from pathlib import Path
+
+ here = Path(__file__).resolve()
+ src_dir = here.parents[2] / "src"
+ if src_dir.exists() and str(src_dir) not in sys.path:
+ sys.path.insert(0, str(src_dir))
+ from soa_builder.web.app import _connect # type: ignore
+
+
+def _timing_uids_for_timeline(soa_id: int, schedule_timeline_uid: str) -> Set[str]:
+ conn = _connect()
+ cur = conn.cursor()
+ cur.execute(
+ """
+ SELECT t.timing_uid FROM schedule_timelines s INNER JOIN timing t
+ ON s.schedule_timeline_uid = t.member_of_timeline AND s.soa_id = t.soa_id
+ WHERE s.soa_id=? AND s.schedule_timeline_uid=? ORDER BY t.timing_uid
+ """,
+ (
+ soa_id,
+ schedule_timeline_uid,
+ ),
+ )
+ rows = cur.fetchall()
+ conn.close()
+ return {r[0] for r in rows if r and r[0]}
+
+
+def _load_generate_study_timings():
+ """Return the timing builder from usdm.generate_study_timings (tries several names)."""
+ try:
+ import usdm.generate_study_timings as gst
+ except Exception:
+ import sys
+ from pathlib import Path
+
+ here = Path(__file__).resolve()
+ src_dir = here.parents[2] / "src"
+ if src_dir.exists() and str(src_dir) not in sys.path:
+ sys.path.insert(0, str(src_dir))
+ import usdm.generate_study_timings as gst
+ for name in (
+ "build_usdm_study_timings",
+ "build_usdm_timings",
+ "generate_study_timings",
+ ):
+ fn = getattr(gst, name, None)
+ if callable(fn):
+ return fn
+ raise ImportError("usdm.generate_study_timings missing expected builder function")
+
+
+def _load_generate_study_instances():
+ """Return the instances builder from usdm.generate_scheduled_activity_instances."""
+ try:
+ import usdm.generate_scheduled_activity_instances as gsai
+ except Exception:
+ import sys
+ from pathlib import Path
+
+ here = Path(__file__).resolve()
+ src_dir = here.parents[2] / "src"
+ if src_dir.exists() and str(src_dir) not in sys.path:
+ sys.path.insert(0, str(src_dir))
+ import usdm.generate_scheduled_activity_instances as gsai
+ for name in (
+ "build_usdm_instances",
+ "generate_scheduled_activity_instances",
+ ):
+ fn = getattr(gsai, name, None)
+ if callable(fn):
+ return fn
+ raise ImportError(
+ "usdm.generate_scheduled_activity_instances missing expected builder function"
+ )
+
+
+def _nz(s: Optional[str]) -> Optional[str]:
+ s = (s or "").strip()
+ return s or None
+
+
+generate_study_timings = _load_generate_study_timings()
+generate_study_instances = _load_generate_study_instances()
+
+
+def build_usdm_schedule_timelines(soa_id: int) -> List[Dict[str, Any]]:
+ """
+ Build USDM scheduleTimelines
+
+ :param soa_id: soa identifier
+ :type soa_id: int
+ :return: USDM JSON for scheduleTimelines entity
+ :rtype: List[Dict[str, Any]]
+
+
+ Output:
+ - id: string
+ - extensionAttributes?: string[]
+ - name: string
+ - label?: string
+ - description?: string
+ - mainTimeline?: bool
+ - entryCondition?: string
+ - entryId?: string
+ - exits?: dict[
+ {
+ - id: string
+ - extensionAttributes?: string[]
+ - instanceType: "ScheduleTimelineExit"
+ },
+ ]
+ - timings?: [
+ {
+ - id: string
+ - extensionAttributes?: string[]
+ - name: string
+ - label?: string
+ - description?: string
+ - type?: {
+ - id: string
+ - extensionAttributes: string[]
+ - code: string
+ - codeSystem: string
+ - codeSystemVersion: string
+ - decode: string
+ - instanceType: "Code"
+ }
+ - value?: string
+ - valueLabel?: string
+ - relativeToFrom?: {
+ - id: string
+ - extensionAttributes: string[]
+ - code: string
+ - codeSystem: string
+ - codeSystemVersion: string
+ - decode: string
+ - instanceType: "Code"
+ }
+ - relativeFromScheduledInstanceId?: string
+ - relativeToScheduledInstanceId?: string
+ - windowLower?: string
+ - windowUpper?: string
+ - windowLabel?: string
+ - instanceType: "Timing"
+ },
+ ]
+ - instances?: [
+ {
+ - id: string
+ - extensionAttributes: string[]|[]
+ - name: string
+ - label?: string
+ - description?: string
+ - defaultConditionId?: string
+ - epochId?: string
+ - instanceType: "ScheduledActivityInstance"
+ - timelineId?: string
+ - timelineExitId?: string
+ - activityIds?: string[]
+ - encounterId?: string
+ }
+ ]
+ - plannedDuration?: string
+ - instanceType: "ScheduleTimeline"
+ """
+ conn = _connect()
+ cur = conn.cursor()
+ cur.execute(
+ """
+ SELECT schedule_timeline_uid,name,label,description,main_timeline,entry_condition,
+ entry_id,exit_id FROM schedule_timelines WHERE soa_id=? ORDER BY length(schedule_timeline_uid),
+ schedule_timeline_uid
+ """,
+ (soa_id,),
+ )
+ rows = cur.fetchall()
+ conn.close()
+ out: List[Dict[str, Any]] = []
+
+ for i, r in enumerate(rows):
+ (
+ schedule_timeline_uid,
+ name,
+ label,
+ description,
+ mainTimeline,
+ entryCondition,
+ entryId,
+ exitId,
+ ) = (
+ r[0],
+ r[1],
+ r[2],
+ r[3],
+ r[4],
+ r[5],
+ r[6],
+ r[7],
+ )
+
+ scheduleTimeline = {
+ "id": schedule_timeline_uid,
+ "extensionAttributes": [],
+ "name": name,
+ "label": _nz(label),
+ "description": _nz(description),
+ "mainTimeline": bool(mainTimeline),
+ "entryCondition": _nz(entryCondition),
+ "entryId": _nz(entryId),
+ "exits": [],
+ "timings": generate_study_timings(soa_id, schedule_timeline_uid),
+ "instances": generate_study_instances(soa_id, schedule_timeline_uid),
+ "plannedDuration": None,
+ "instanceType": "ScheduleTimeline",
+ }
+ out.append(scheduleTimeline)
+ return out
+
+
+if __name__ == "__main__":
+ import argparse
+ import json
+ import logging
+ import sys
+
+ logger = logging.getLogger("usdm.generate_schedule_timelines")
+
+ parser = argparse.ArgumentParser(
+ description="Export USDM Schedule Timelines for an SOA."
+ )
+ parser.add_argument(
+ "soa_id", type=int, help="SOA id to export Schedule Timelines for"
+ )
+ parser.add_argument(
+ "-o", "--output", default="-", help="Output file path or '-' for stdout"
+ )
+ parser.add_argument("--indent", type=int, default=2, help="JSON indent")
+ args = parser.parse_args()
+
+ try:
+ activities = build_usdm_schedule_timelines(args.soa_id)
+ except Exception:
+ logger.exception(
+ "Failed to build Schedule Timelines for soa_id=%s", args.soa_id
+ )
+ sys.exit(1)
+
+ payload = json.dumps(activities, indent=args.indent)
+ if args.output in ("-", "/dev/stdout"):
+ sys.stdout.write(payload + "\n")
+ else:
+ with open(args.output, "w", encoding="utf-8") as f:
+ f.write(payload + "\n")
diff --git a/src/usdm/generate_scheduled_activity_instances.py b/src/usdm/generate_scheduled_activity_instances.py
index 96b70dc..971181e 100644
--- a/src/usdm/generate_scheduled_activity_instances.py
+++ b/src/usdm/generate_scheduled_activity_instances.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
# Prefer absolute import; fallback to adding src/ to sys.path when run directly
-from typing import Optional, List, Dict, Any, Tuple
+from typing import Optional, List, Dict, Any
try:
from soa_builder.web.app import _connect # reuse existing DB connector
@@ -40,7 +40,9 @@ def _get_activity_ids(soa_id: int, encounter_uid: str) -> List[str]:
return activity_uids
-def build_usdm_instances(soa_id: int) -> List[Dict[str, Any]]:
+def build_usdm_instances(
+ soa_id: int, member_of_timeline: Optional[str] = None
+) -> List[Dict[str, Any]]:
"""
Build USDM instances objects for the given SOA
@@ -60,11 +62,24 @@ def build_usdm_instances(soa_id: int) -> List[Dict[str, Any]]:
"""
conn = _connect()
cur = conn.cursor()
- cur.execute(
- "SELECT id,instance_uid,name,label,description,default_condition_uid,epoch_uid,"
- "timeline_id,timeline_exit_id,encounter_uid FROM instances where soa_id=? ORDER BY instance_uid",
- (soa_id,),
- )
+ if member_of_timeline and member_of_timeline.strip():
+ cur.execute(
+ """
+ SELECT id,instance_uid,name,label,description,default_condition_uid,epoch_uid,
+ timeline_id,timeline_exit_id,encounter_uid FROM instances where soa_id=? AND member_of_timeline=?
+ ORDER BY length(instance_uid), instance_uid
+ """,
+ (soa_id, member_of_timeline.strip()),
+ )
+ else:
+ cur.execute(
+ """
+ SELECT id,instance_uid,name,label,description,default_condition_uid,epoch_uid,
+ timeline_id,timeline_exit_id,encounter_uid FROM instances where soa_id=?
+ ORDER BY length(instance_uid), instance_uid
+ """,
+ (soa_id,),
+ )
rows = cur.fetchall()
conn.close()
out: List[Dict[str, Any]] = []
@@ -131,6 +146,12 @@ def build_usdm_instances(soa_id: int) -> List[Dict[str, Any]]:
"-o", "--output", default="-", help="Output file path or '-' for stdout"
)
parser.add_argument("--indent", type=int, default=2, help="JSON indent")
+ parser.add_argument(
+ "--member-of-timeline",
+ dest="member_of_timeline",
+ default=None,
+ help="Optional schedule_timeline_uid to filter instances",
+ )
args = parser.parse_args()
try:
diff --git a/src/usdm/generate_study_timings.py b/src/usdm/generate_study_timings.py
index 2905333..5896177 100644
--- a/src/usdm/generate_study_timings.py
+++ b/src/usdm/generate_study_timings.py
@@ -42,7 +42,9 @@ def _get_timing_code_values(soa_id: int, code_uid: str) -> Tuple[str, str, str,
return code_code, code_decode, code_system, code_system_version
-def build_usdm_timings(soa_id: int) -> List[Dict[str, Any]]:
+def build_usdm_timings(
+ soa_id: int, member_of_timeline: Optional[str] = None
+) -> List[Dict[str, Any]]:
"""
Build USDM Timings-Output objects for the given SOA.
@@ -81,12 +83,25 @@ def build_usdm_timings(soa_id: int) -> List[Dict[str, Any]]:
"""
conn = _connect()
cur = conn.cursor()
- cur.execute(
- "SELECT id,timing_uid,name,label,description,type,value,value_label,relative_to_from,"
- "relative_from_schedule_instance,relative_to_schedule_instance,window_label,window_upper,"
- "window_lower,order_index FROM timing WHERE soa_id=? order by timing_uid",
- (soa_id,),
- )
+ if member_of_timeline and member_of_timeline.strip():
+ cur.execute(
+ """
+ SELECT id,timing_uid,name,label,description,type,value,value_label,relative_to_from,
+ relative_from_schedule_instance,relative_to_schedule_instance,window_label,window_upper,
+ window_lower,order_index FROM timing WHERE soa_id=? AND member_of_timeline=? order by length(timing_uid),
+ timing_uid
+ """,
+ (soa_id, member_of_timeline.strip()),
+ )
+ else:
+ cur.execute(
+ """
+ SELECT id,timing_uid,name,label,description,type,value,value_label,relative_to_from,
+ relative_from_schedule_instance,relative_to_schedule_instance,window_label,window_upper,
+ window_lower,order_index FROM timing WHERE soa_id=? order by length(timing_uid), timing_uid
+ """,
+ (soa_id,),
+ )
rows = cur.fetchall()
conn.close()
out: List[Dict[str, Any]] = []
@@ -184,10 +199,18 @@ def build_usdm_timings(soa_id: int) -> List[Dict[str, Any]]:
"-o", "--output", default="-", help="Output file path or '-' for stdout"
)
parser.add_argument("--indent", type=int, default=2, help="JSON indent")
+ parser.add_argument(
+ "--member-of-timeline",
+ dest="member_of_timeline",
+ default=None,
+ help="Optional schedule_timeline_uid to filter timings",
+ )
args = parser.parse_args()
try:
- activities = build_usdm_timings(args.soa_id)
+ activities = build_usdm_timings(
+ args.soa_id, member_of_timeline=args.member_of_timeline
+ )
except Exception:
logger.exception("Failed to build timings for soa_id=%s", args.soa_id)
sys.exit(1)