Skip to content
Open
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
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,27 @@ You're done! Access the app at `localhost:8501` in your browser.

The recommender system works by loading a set of random video links. Once the user hits the Run button, a video will be shown, and the system will suggest whether it recommends the video or not. The user can then provide feedback using "pain" or "pleasure" signals to guide the recommendation process. Based on this feedback, the system adjusts its responses and suggests another video. This cycle continues, allowing for more accurate and personalized recommendations over time.

## Travel Domain Demo

This fork also includes a second runnable domain for the paid recommender expansion bounty: a real-time personal travel-experience recommender.

It uses a local catalog of city experiences, encodes each recommendation into the same eight-bit input shape as the original AO architecture (`[3, 2, 1, 2]`), and trains a live AO Agent when `ao_core`/`ao_arch` are installed. Reviewers without the private AO packages still get a deterministic fallback ranker, so the demo remains runnable from a clean checkout.

Run the travel demo:

```bash
streamlit run travel_recommender.py
```

Run the domain tests:

```bash
python -m unittest discover -s tests
```


## Contributing

Fork the repository, make your changes, and submit a pull request for review.



50 changes: 50 additions & 0 deletions tests/test_travel_domain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import unittest

from travel_domain import CATALOG, TravelPreferences, encode_experience, rank_experiences


class TravelDomainTest(unittest.TestCase):
def test_encoding_matches_existing_ao_architecture_width(self):
preferences = TravelPreferences(mood="active")
encoded = encode_experience(CATALOG[0], preferences)

self.assertEqual(len(encoded), 8)
self.assertTrue(all(bit in (0, 1) for bit in encoded))

def test_budget_filter_penalizes_over_budget_experiences(self):
preferences = TravelPreferences(mood="active", max_budget="low", pace="energetic")
ranked = rank_experiences(CATALOG, preferences)

top_five = [experience for experience, _, _ in ranked[:5]]
self.assertTrue(all(experience.budget in {"free", "low"} for experience in top_five))

def test_positive_feedback_promotes_activity_type(self):
preferences = TravelPreferences(mood="curious", max_budget="medium", pace="balanced")
baseline = rank_experiences(CATALOG, preferences)
promoted = rank_experiences(CATALOG, preferences, positive_feedback=["nightlife"])

baseline_best_nightlife = min(
index
for index, (experience, _, _) in enumerate(baseline)
if experience.activity_type == "nightlife"
)
promoted_best_nightlife = min(
index
for index, (experience, _, _) in enumerate(promoted)
if experience.activity_type == "nightlife"
)
self.assertLess(promoted_best_nightlife, baseline_best_nightlife)

def test_negative_feedback_suppresses_specific_experience(self):
preferences = TravelPreferences(mood="relaxed", max_budget="medium", pace="calm")
target = "reykjavik-geothermal-dip"
ranked = rank_experiences(CATALOG, preferences, negative_feedback=[target])

target_rank = next(
index for index, (experience, _, _) in enumerate(ranked) if experience.id == target
)
self.assertGreater(target_rank, 2)


if __name__ == "__main__":
unittest.main()
Loading