Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.9.3

- Refined review queue filtering logic into a shared helper for maintainability.
- Aligned review-segment CSV export with active queue filters used in the UI.
- Bumped release metadata and docs to 0.9.3.
- Documented Granian as the default ASGI command for local startup parity.

## 0.9.2

- Added batch assignment for review segments directly from the session player.
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# PyBehaviorLog 0.9.2
# PyBehaviorLog 0.9.3

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.

Expand Down Expand Up @@ -57,9 +57,11 @@ source .venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver
granian --interface asgi --host 127.0.0.1 --port 8000 config.asgi:application
```

For ASGI-parity in local development, use Granian (instead of the Django dev server) as shown above.

## Quick start with Docker

```bash
Expand Down
9 changes: 9 additions & 0 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,12 @@ coverage run manage.py test
coverage report --fail-under=80
pre-commit run --all-files
```


## Local ASGI run

Use Granian directly to mirror production ASGI behavior:

```bash
granian --interface asgi --host 127.0.0.1 --port 8000 config.asgi:application
```
54 changes: 53 additions & 1 deletion tracker/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.2',
'version': '0.9.3',
'django_target': '6.0.3',
'python_minimum': '3.13',
'asgi': True,
Expand Down Expand Up @@ -476,6 +476,58 @@ def build_review_queue(user) -> dict:
},
}

def _filter_review_segments(
rows: list[ObservationSegment],
*,
user,
project_filter: str = '',
status_filter: str = '',
assignee_filter: str = '',
reviewer_filter: str = '',
query_filter: str = '',
) -> list[ObservationSegment]:
filtered = list(rows)
if project_filter.isdigit():
filtered = [item for item in filtered if item.session.project_id == int(project_filter)]

if status_filter == 'open':
filtered = [item for item in filtered if item.status != ObservationSegment.STATUS_DONE]
elif status_filter in {
ObservationSegment.STATUS_TODO,
ObservationSegment.STATUS_IN_PROGRESS,
ObservationSegment.STATUS_DONE,
}:
filtered = [item for item in filtered if item.status == status_filter]

if assignee_filter == 'me':
filtered = [item for item in filtered if item.assignee_id == user.id]
elif assignee_filter == 'unassigned':
filtered = [item for item in filtered if item.assignee_id is None]

if reviewer_filter == 'me':
filtered = [item for item in filtered if item.reviewer_id == user.id]
elif reviewer_filter == 'unassigned':
filtered = [item for item in filtered if item.reviewer_id is None]

query_normalized = query_filter.strip().lower()
if query_normalized:
filtered = [
item
for item in filtered
if query_normalized in item.title.lower()
or query_normalized in item.session.title.lower()
or query_normalized in item.session.project.name.lower()
]

return filtered


def _review_queue_project_choices(queue_rows: list[ObservationSegment]) -> list[Project]:
by_id: dict[int, Project] = {}
for item in queue_rows:
by_id[item.session.project_id] = item.session.project
return sorted(by_id.values(), key=lambda project: project.name.lower())


def _get_owned_category(user, pk: int) -> BehaviorCategory:
category = get_object_or_404(BehaviorCategory.objects.select_related('project'), pk=pk)
Expand Down
Loading