diff --git a/CHANGELOG.md b/CHANGELOG.md index ebab234..36f91e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.9.5 + +- Unified export schema/version markers to 0.9.5 across session JSON, bundle manifest, compatibility report, SQL header and CowLog header. +- Preserved backward import compatibility for 0.9.1 schemas while adding acceptance for 0.9.5 schemas. +- Carried forward v0.9.3 review queue filtering/export consistency and batch assignment behavior. + ## 0.9.4 - Restored Django runtime target to 6.0.3. diff --git a/README.md b/README.md index 07201a9..46322f0 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# PyBehaviorLog 0.9.4 +# PyBehaviorLog 0.9.5 PyBehaviorLog is an ASGI-first behavioral observation platform built with Django 6.0.3. It is designed for research teams who need video-assisted coding, live observations, structured ethograms, review workflows, and exportable analytics without being locked into a desktop-only workflow. -## What is in this 0.9.4 archive +## What is in this 0.9.5 archive This version extends the earlier CowLog/BORIS-inspired foundations with: diff --git a/tracker/tests/test_compatibility.py b/tracker/tests/test_compatibility.py index 2b364c8..f07216e 100644 --- a/tracker/tests/test_compatibility.py +++ b/tracker/tests/test_compatibility.py @@ -202,4 +202,4 @@ def test_export_endpoints_for_compatibility_formats(self): ) self.assertEqual(response.status_code, 200) payload = json.loads(response.content.decode('utf-8')) - self.assertEqual(payload['schema'], 'pybehaviorlog-0.9.1-session-compatibility-report') + self.assertEqual(payload['schema'], 'pybehaviorlog-0.9.5-session-compatibility-report') diff --git a/tracker/tests/test_helpers.py b/tracker/tests/test_helpers.py index df7fede..787a7c4 100644 --- a/tracker/tests/test_helpers.py +++ b/tracker/tests/test_helpers.py @@ -110,7 +110,7 @@ def test_build_statistics_subjects_transitions_and_integrity(self): def test_build_project_statistics_and_payloads(self): payload = build_ethogram_payload(self.project) - self.assertEqual(payload['schema'], 'pybehaviorlog-0.9.1-ethogram') + self.assertEqual(payload['schema'], 'pybehaviorlog-0.9.5-ethogram') imported_categories, _, imported_behaviors = import_ethogram_payload( self.project, payload, replace_existing=False ) @@ -132,7 +132,7 @@ def test_build_project_statistics_and_payloads(self): def test_import_session_payload_v83(self): payload = { - 'schema': 'pybehaviorlog-0.9.1-session', + 'schema': 'pybehaviorlog-0.9.5-session', 'workflow_status': 'validated', 'review_notes': 'Checked', 'events': [ diff --git a/tracker/tests/test_roundtrip.py b/tracker/tests/test_roundtrip.py index 046b466..86eb4ea 100644 --- a/tracker/tests/test_roundtrip.py +++ b/tracker/tests/test_roundtrip.py @@ -76,7 +76,7 @@ def test_cowlog_fixture_roundtrip_via_pybehaviorlog_json(self): self.assertEqual(report['detected_format'], 'cowlog-results-v1') import_session_payload(session, imported_payload, clear_existing=True) exported_payload = { - 'schema': 'pybehaviorlog-0.9.1-session', + 'schema': 'pybehaviorlog-0.9.5-session', 'events': [ { 'time': event.timestamp_seconds, diff --git a/tracker/tests/test_views.py b/tracker/tests/test_views.py index 5220df4..8550305 100644 --- a/tracker/tests/test_views.py +++ b/tracker/tests/test_views.py @@ -92,7 +92,7 @@ def test_event_api_create_list_and_export_json(self): export_response = self.client.get(reverse('tracker:session_export_json', args=[session.pk])) self.assertEqual(export_response.status_code, 200) - self.assertIn('pybehaviorlog-0.9.1-session', export_response.content.decode('utf-8')) + self.assertIn('pybehaviorlog-0.9.5-session', export_response.content.decode('utf-8')) def test_event_update_and_delete_api(self): session = self.project.sessions.create( @@ -242,7 +242,7 @@ def test_session_import_accepts_csv(self): def test_project_import_boris_json_view(self): payload = { 'schema': 'boris-project-v3', - 'ethogram': {'schema': 'pybehaviorlog-0.9.1-ethogram', 'categories': [], 'modifiers': [], 'subject_groups': [], 'subjects': [], 'variables': [], 'behaviors': [{'name': 'Imported behavior', 'description': '', 'key_binding': 'i', 'color': '#0f766e', 'mode': 'point', 'sort_order': 1, 'category': None}]}, + 'ethogram': {'schema': 'pybehaviorlog-0.9.5-ethogram', 'categories': [], 'modifiers': [], 'subject_groups': [], 'subjects': [], 'variables': [], 'behaviors': [{'name': 'Imported behavior', 'description': '', 'key_binding': 'i', 'color': '#0f766e', 'mode': 'point', 'sort_order': 1, 'category': None}]}, 'subject_groups': [{'name': 'Imported group', 'description': '', 'color': '#123456', 'sort_order': 1}], 'subjects': [{'name': 'Imported subject', 'description': '', 'key_binding': 's', 'color': '#654321', 'sort_order': 1, 'groups': ['Imported group']}], 'variables': [{'label': 'Weight', 'description': '', 'value_type': 'numeric', 'set_values': [], 'default_value': '0', 'sort_order': 1}], @@ -318,7 +318,7 @@ def test_project_import_boris_json_accepts_mapping_shapes(self): payload = { 'schema': 'boris-project-v2', 'ethogram': { - 'schema': 'pybehaviorlog-0.9.1-ethogram', + 'schema': 'pybehaviorlog-0.9.5-ethogram', 'categories': {'General': {'color': '#111111', 'sort_order': 1}}, 'modifiers': {'Near': {'description': 'proximity', 'key': 'n', 'sort_order': 1}}, 'behaviors': {'Imported code': {'description': '', 'key': 'i', 'color': '#0f766e', 'mode': 'point', 'sort_order': 1, 'category': {'name': 'General'}}}, diff --git a/tracker/views.py b/tracker/views.py index 18d52ae..89a0116 100644 --- a/tracker/views.py +++ b/tracker/views.py @@ -128,7 +128,7 @@ def build_release_metadata() -> dict: """Return a small machine-readable release description for health and ops tooling.""" return { 'application': 'PyBehaviorLog', - 'version': '0.9.4', + 'version': '0.9.5', 'django_target': '6.0.3', 'python_minimum': '3.13', 'asgi': True, @@ -1790,8 +1790,8 @@ def build_reproducibility_bundle(project: Project) -> dict[str, bytes]: ) manifest = { - 'schema': 'pybehaviorlog-0.9.1-bundle', - 'version': '0.9.1', + 'schema': 'pybehaviorlog-0.9.5-bundle', + 'version': '0.9.5', 'project': { 'name': project.name, 'description': project.description, @@ -2366,8 +2366,8 @@ def build_session_compatibility_report(session: ObservationSession) -> dict: modifier_event_count = sum(1 for event in ordered_events if event.modifiers.exists()) multi_subject_event_count = sum(1 for event in ordered_events if event.subjects.count() > 1) report = { - 'schema': 'pybehaviorlog-0.9.1-session-compatibility-report', - 'version': '0.9.1', + 'schema': 'pybehaviorlog-0.9.5-session-compatibility-report', + 'version': '0.9.5', 'session': session.title, 'boris': { 'documented_exports': [ @@ -2421,8 +2421,8 @@ def build_session_compatibility_report(session: ObservationSession) -> dict: def build_project_compatibility_report(project: Project) -> dict: """Summarize project-level exchange coverage for BORIS and CowLog.""" return { - 'schema': 'pybehaviorlog-0.9.1-project-compatibility-report', - 'version': '0.9.1', + 'schema': 'pybehaviorlog-0.9.5-project-compatibility-report', + 'version': '0.9.5', 'project': project.name, 'counts': { 'sessions': project.sessions.count(), @@ -2651,6 +2651,7 @@ def import_project_payload( 'pybehaviorlog-0.8.3-bundle', 'pybehaviorlog-0.9-bundle', 'pybehaviorlog-0.9.1-bundle', + 'pybehaviorlog-0.9.5-bundle', }: raise ValueError(_('Unsupported project payload format.')) @@ -2659,7 +2660,7 @@ def import_project_payload( project, { **ethogram_payload, - 'schema': ethogram_payload.get('schema', 'pybehaviorlog-0.9.1-ethogram'), + 'schema': ethogram_payload.get('schema', 'pybehaviorlog-0.9.5-ethogram'), }, replace_existing=False, ) @@ -2870,7 +2871,7 @@ def import_project_payload( def build_ethogram_payload(project: Project) -> dict: # pragma: no cover return { - 'schema': 'pybehaviorlog-0.9.1-ethogram', + 'schema': 'pybehaviorlog-0.9.5-ethogram', 'project': { 'name': project.name, 'description': project.description, @@ -2953,6 +2954,7 @@ def import_ethogram_payload( 'pybehaviorlog-0.8.3-ethogram', 'pybehaviorlog-0.9-ethogram', 'pybehaviorlog-0.9.1-ethogram', + 'pybehaviorlog-0.9.5-ethogram', 'boris-project-v1', 'boris-project-v2', 'boris-project-v3', @@ -3257,6 +3259,7 @@ def import_session_payload( 'pybehaviorlog-0.8.3-session', 'pybehaviorlog-0.9-session', 'pybehaviorlog-0.9.1-session', + 'pybehaviorlog-0.9.5-session', 'cowlog-results-v1', 'boris-tabular-csv-v1', 'boris-tabular-tsv-v1', @@ -5488,7 +5491,7 @@ def session_export_sql(request, pk: int): # pragma: no cover """Export session events as SQL INSERT statements for downstream analysis.""" session = get_accessible_session(request.user, pk) lines = [ - '-- PyBehaviorLog 0.9.1 SQL export', + '-- PyBehaviorLog 0.9.5 SQL export', 'BEGIN;', 'CREATE TABLE IF NOT EXISTS pybehaviorlog_event_export (project text, session text, primary_video text, synced_videos text, observer text, category text, behavior text, behavior_mode text, event_kind text, timestamp_seconds numeric(10,3), subjects text, modifiers text, comment text, created_at text);', ] @@ -5525,7 +5528,7 @@ def session_export_cowlog_txt(request, pk: int): # pragma: no cover response['Content-Disposition'] = ( f'attachment; filename="session_{session.pk}_cowlog_compatible.txt"' ) - response.write('# PyBehaviorLog 0.9.1 CowLog-compatible export\n') + response.write('# PyBehaviorLog 0.9.5 CowLog-compatible export\n') response.write(f'# session\t{session.title}\n') response.write(f'# project\t{session.project.name}\n') response.write(f'# primary_video\t{session.primary_label}\n') @@ -5652,7 +5655,7 @@ def session_export_tsv(request, pk: int): # pragma: no cover def session_export_json(request, pk: int): session = get_accessible_session(request.user, pk) payload = { - 'schema': 'pybehaviorlog-0.9.1-session', + 'schema': 'pybehaviorlog-0.9.5-session', 'project': session.project.name, 'session': session.title, 'video': session.primary_label,