personal memory agent
0

Configure Feed

Select the types of activity you want to include in your feed.

solstone / tests / test_surfaces_profile.py
26 kB 834 lines
1# SPDX-License-Identifier: AGPL-3.0-only 2# Copyright (c) 2026 sol pbc 3 4import json 5import re 6from datetime import UTC, datetime 7 8from solstone.think.surfaces.types import Cadence 9 10 11def _configure_env(tmp_path, monkeypatch) -> None: 12 monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 13 monkeypatch.setenv("SOL_SKIP_SUPERVISOR_CHECK", "1") 14 15 import solstone.think.utils as think_utils 16 17 think_utils._journal_path_cache = None 18 19 20def _write_journal_entity( 21 tmp_path, 22 entity_id: str, 23 name: str, 24 *, 25 entity_type: str = "Person", 26 aka: list[str] | None = None, 27 is_principal: bool = False, 28) -> None: 29 entity_dir = tmp_path / "entities" / entity_id 30 entity_dir.mkdir(parents=True, exist_ok=True) 31 payload: dict[str, object] = {"id": entity_id, "name": name, "type": entity_type} 32 if aka: 33 payload["aka"] = aka 34 if is_principal: 35 payload["is_principal"] = True 36 (entity_dir / "entity.json").write_text(json.dumps(payload), encoding="utf-8") 37 38 39def _write_facet_relationship( 40 tmp_path, facet: str, entity_id: str, *, description: str = "" 41) -> None: 42 relationship_dir = tmp_path / "facets" / facet / "entities" / entity_id 43 relationship_dir.mkdir(parents=True, exist_ok=True) 44 (relationship_dir / "entity.json").write_text( 45 json.dumps({"entity_id": entity_id, "description": description}), 46 encoding="utf-8", 47 ) 48 49 50def _minimal_facet_tree( 51 tmp_path, 52 facets=("work",), 53 *, 54 muted_facets=(), 55 journal_entities: tuple[dict[str, object], ...] = (), 56) -> None: 57 for facet in facets: 58 facet_dir = tmp_path / "facets" / facet 59 facet_dir.mkdir(parents=True, exist_ok=True) 60 (facet_dir / "activities").mkdir(exist_ok=True) 61 (facet_dir / "facet.json").write_text( 62 json.dumps( 63 { 64 "title": facet.title(), 65 "description": "", 66 "color": "", 67 "emoji": "", 68 "muted": facet in set(muted_facets), 69 } 70 ), 71 encoding="utf-8", 72 ) 73 74 for entity in journal_entities: 75 _write_journal_entity( 76 tmp_path, 77 str(entity["id"]), 78 str(entity["name"]), 79 entity_type=str(entity.get("type", "Person")), 80 aka=list(entity.get("aka", [])) 81 if isinstance(entity.get("aka"), list) 82 else None, 83 is_principal=bool(entity.get("is_principal", False)), 84 ) 85 86 87def _utc_ms(day: str, hour: int = 12) -> int: 88 parsed = datetime.strptime(f"{day} {hour:02d}:00:00", "%Y%m%d %H:%M:%S") 89 return int(parsed.replace(tzinfo=UTC).timestamp() * 1000) 90 91 92def _participant( 93 entity_id: str | None, 94 *, 95 name: str | None = None, 96 role: str = "attendee", 97 source: str = "screen", 98 confidence: float = 0.9, 99 context: str = "test participant", 100) -> dict[str, object]: 101 display_name = name or ( 102 entity_id.replace("_", " ").title() if entity_id else "Unknown" 103 ) 104 return { 105 "name": display_name, 106 "role": role, 107 "source": source, 108 "confidence": confidence, 109 "context": context, 110 "entity_id": entity_id, 111 } 112 113 114def _activity_record( 115 day: str, participation: list[dict[str, object]], **kwargs 116) -> dict: 117 activity = str(kwargs.pop("activity", "meeting")) 118 record_id = str(kwargs.pop("record_id", f"{activity}_{day}_120000")) 119 title = str(kwargs.pop("title", f"{record_id} title")) 120 record = { 121 "id": record_id, 122 "activity": activity, 123 "title": title, 124 "description": str(kwargs.pop("description", title)), 125 "details": str(kwargs.pop("details", "")), 126 "participation": participation, 127 "segments": list(kwargs.pop("segments", [])), 128 "active_entities": list(kwargs.pop("active_entities", [])), 129 "created_at": int(kwargs.pop("created_at", _utc_ms(day))), 130 "source": str(kwargs.pop("source", "user")), 131 "hidden": bool(kwargs.pop("hidden", False)), 132 "edits": list(kwargs.pop("edits", [])), 133 } 134 record.update(kwargs) 135 return record 136 137 138def _append_activity(facet: str, day: str, record: dict) -> None: 139 from solstone.think.activities import append_activity_record 140 141 append_activity_record(facet, day, record) 142 143 144def _commitment( 145 *, 146 owner: str = "Mina", 147 owner_entity_id: str | None = "mina", 148 action: str = "send proposal", 149 counterparty: str = "Ravi", 150 counterparty_entity_id: str | None = "ravi", 151 when: str = "tomorrow", 152 context: str = "Commitment context.", 153) -> dict[str, object]: 154 return { 155 "owner": owner, 156 "owner_entity_id": owner_entity_id, 157 "action": action, 158 "counterparty": counterparty, 159 "counterparty_entity_id": counterparty_entity_id, 160 "when": when, 161 "context": context, 162 } 163 164 165def _decision( 166 *, 167 owner: str = "Ravi", 168 owner_entity_id: str | None = "ravi", 169 action: str = "move launch review", 170 context: str = "Decision context.", 171) -> dict[str, object]: 172 return { 173 "owner": owner, 174 "owner_entity_id": owner_entity_id, 175 "action": action, 176 "context": context, 177 } 178 179 180def _write_story_activity( 181 facet: str, 182 day: str, 183 record_id: str, 184 created_at: int, 185 *, 186 commitments: list[dict[str, object]] | None = None, 187 closures: list[dict[str, object]] | None = None, 188 decisions: list[dict[str, object]] | None = None, 189) -> None: 190 from solstone.think.activities import append_activity_record, merge_story_fields 191 192 append_activity_record( 193 facet, 194 day, 195 _activity_record( 196 day, 197 [], 198 record_id=record_id, 199 created_at=created_at, 200 title=f"{record_id} title", 201 ), 202 ) 203 merge_story_fields( 204 facet, 205 day, 206 record_id, 207 story={ 208 "talent": "story", 209 "body": f"{record_id} summary", 210 "topics": ["profile"], 211 "confidence": 0.9, 212 }, 213 commitments=commitments or [], 214 closures=closures or [], 215 decisions=decisions or [], 216 relations=[], 217 actor="story", 218 ) 219 220 221def test_cadence_zero_interactions(tmp_path, monkeypatch): 222 from solstone.think.surfaces import profile as profile_surface 223 224 _configure_env(tmp_path, monkeypatch) 225 _minimal_facet_tree( 226 tmp_path, 227 journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},), 228 ) 229 _write_facet_relationship(tmp_path, "work", "ravi", description="Customer") 230 231 assert profile_surface.cadence("Ravi") == Cadence(0, None, None, None) 232 233 234def test_cadence_single_day(tmp_path, monkeypatch): 235 from solstone.think.surfaces import profile as profile_surface 236 237 _configure_env(tmp_path, monkeypatch) 238 _minimal_facet_tree( 239 tmp_path, 240 journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},), 241 ) 242 _write_facet_relationship(tmp_path, "work", "ravi", description="Customer") 243 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 244 245 _append_activity( 246 "work", 247 "20260418", 248 _activity_record( 249 "20260418", 250 [_participant("ravi", name="Ravi")], 251 record_id="meeting_20260418_a", 252 ), 253 ) 254 255 cadence = profile_surface.cadence("Ravi") 256 assert cadence == Cadence(1, "20260418", None, None) 257 258 259def test_cadence_multi_day(tmp_path, monkeypatch): 260 from solstone.think.surfaces import profile as profile_surface 261 262 _configure_env(tmp_path, monkeypatch) 263 _minimal_facet_tree( 264 tmp_path, 265 journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},), 266 ) 267 _write_facet_relationship(tmp_path, "work", "ravi", description="Customer") 268 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 269 270 for index, day in enumerate(["20260410", "20260414", "20260418"], start=1): 271 _append_activity( 272 "work", 273 day, 274 _activity_record( 275 day, 276 [_participant("ravi", name="Ravi")], 277 record_id=f"meeting_{index}", 278 ), 279 ) 280 281 cadence = profile_surface.cadence("Ravi") 282 assert cadence is not None 283 assert cadence.recent_interactions_count_30d == 3 284 assert cadence.last_seen == "20260418" 285 assert cadence.avg_interval_days == 4.0 286 assert cadence.gone_quiet_since is None 287 288 289def test_cadence_gone_quiet_threshold(tmp_path, monkeypatch): 290 from solstone.think.surfaces import profile as profile_surface 291 292 _configure_env(tmp_path, monkeypatch) 293 _minimal_facet_tree( 294 tmp_path, 295 journal_entities=( 296 {"id": "quiet_person", "name": "Quiet Person", "type": "Person"}, 297 {"id": "boundary_person", "name": "Boundary Person", "type": "Person"}, 298 ), 299 ) 300 _write_facet_relationship(tmp_path, "work", "quiet_person", description="Quiet") 301 _write_facet_relationship( 302 tmp_path, "work", "boundary_person", description="Boundary" 303 ) 304 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 305 306 for day in ["20260401", "20260406"]: 307 _append_activity( 308 "work", 309 day, 310 _activity_record( 311 day, 312 [_participant("quiet_person", name="Quiet Person")], 313 record_id=f"quiet_{day}", 314 ), 315 ) 316 for day in ["20260405", "20260410"]: 317 _append_activity( 318 "work", 319 day, 320 _activity_record( 321 day, 322 [_participant("boundary_person", name="Boundary Person")], 323 record_id=f"boundary_{day}", 324 ), 325 ) 326 327 quiet = profile_surface.cadence("Quiet Person") 328 boundary = profile_surface.cadence("Boundary Person") 329 330 assert quiet is not None 331 assert quiet.gone_quiet_since == 14 332 assert boundary is not None 333 assert boundary.gone_quiet_since is None 334 335 336def test_cadence_gone_quiet_returns_int_days(tmp_path, monkeypatch): 337 from solstone.think.surfaces import profile as profile_surface 338 339 _configure_env(tmp_path, monkeypatch) 340 _minimal_facet_tree( 341 tmp_path, 342 journal_entities=( 343 {"id": "far_person", "name": "Far Person", "type": "Person"}, 344 {"id": "equal_person", "name": "Equal Person", "type": "Person"}, 345 {"id": "recent_person", "name": "Recent Person", "type": "Person"}, 346 ), 347 ) 348 for entity_id, description in ( 349 ("far_person", "Far"), 350 ("equal_person", "Equal"), 351 ("recent_person", "Recent"), 352 ): 353 _write_facet_relationship(tmp_path, "work", entity_id, description=description) 354 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 355 356 for entity_id, days in ( 357 ("far_person", ("20260330", "20260404")), 358 ("equal_person", ("20260405", "20260410")), 359 ("recent_person", ("20260406", "20260411")), 360 ): 361 for day in days: 362 _append_activity( 363 "work", 364 day, 365 _activity_record( 366 day, 367 [_participant(entity_id, name=entity_id.replace("_", " ").title())], 368 record_id=f"{entity_id}_{day}", 369 ), 370 ) 371 372 far = profile_surface.cadence("Far Person") 373 equal = profile_surface.cadence("Equal Person") 374 recent = profile_surface.cadence("Recent Person") 375 376 assert far is not None 377 assert far.gone_quiet_since == 16 378 assert equal is not None 379 assert equal.gone_quiet_since is None 380 assert recent is not None 381 assert recent.gone_quiet_since is None 382 383 384def test_cadence_distinct_days_vs_record_count(tmp_path, monkeypatch): 385 from solstone.think.surfaces import profile as profile_surface 386 387 _configure_env(tmp_path, monkeypatch) 388 _minimal_facet_tree( 389 tmp_path, 390 journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},), 391 ) 392 _write_facet_relationship(tmp_path, "work", "ravi", description="Customer") 393 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 394 395 for suffix in ("a", "b", "c"): 396 _append_activity( 397 "work", 398 "20260418", 399 _activity_record( 400 "20260418", 401 [_participant("ravi", name="Ravi")], 402 record_id=f"meeting_20260418_{suffix}", 403 ), 404 ) 405 406 cadence = profile_surface.cadence("Ravi") 407 assert cadence == Cadence(3, "20260418", None, None) 408 409 410def test_resolve_exact_and_slug_and_aka_and_fuzzy(tmp_path, monkeypatch): 411 from solstone.think.surfaces import profile as profile_surface 412 413 _configure_env(tmp_path, monkeypatch) 414 _minimal_facet_tree( 415 tmp_path, 416 journal_entities=( 417 { 418 "id": "john_borthwick", 419 "name": "John Borthwick", 420 "type": "Person", 421 "aka": ["JB"], 422 }, 423 ), 424 ) 425 _write_facet_relationship( 426 tmp_path, "work", "john_borthwick", description="Investor" 427 ) 428 429 queries = ["John Borthwick", "john_borthwick", "JB", "John Borthwik"] 430 resolved_ids = { 431 profile_surface._resolve_target(query).entity_id # noqa: SLF001 432 for query in queries 433 } 434 435 assert resolved_ids == {"john_borthwick"} 436 437 438def test_facets_filter_narrows_display_not_cadence(tmp_path, monkeypatch): 439 from solstone.think.surfaces import profile as profile_surface 440 441 _configure_env(tmp_path, monkeypatch) 442 _minimal_facet_tree( 443 tmp_path, 444 facets=("personal", "work"), 445 journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},), 446 ) 447 _write_facet_relationship(tmp_path, "work", "ravi", description="Work contact") 448 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 449 _append_activity( 450 "work", 451 "20260418", 452 _activity_record( 453 "20260418", 454 [_participant("ravi", name="Ravi")], 455 record_id="meeting_20260418", 456 ), 457 ) 458 459 profile = profile_surface.full("Ravi", facets=["personal"]) 460 461 assert profile is not None 462 assert profile.facets == () 463 assert profile.description is None 464 assert profile.cadence.recent_interactions_count_30d == 1 465 466 467def test_include_mentions_toggle(tmp_path, monkeypatch): 468 from solstone.think.surfaces import profile as profile_surface 469 470 _configure_env(tmp_path, monkeypatch) 471 _minimal_facet_tree( 472 tmp_path, 473 journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},), 474 ) 475 _write_facet_relationship(tmp_path, "work", "ravi", description="Customer") 476 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 477 _append_activity( 478 "work", 479 "20260418", 480 _activity_record( 481 "20260418", 482 [_participant("ravi", name="Ravi", role="mentioned")], 483 record_id="meeting_20260418", 484 ), 485 ) 486 487 assert profile_surface.cadence("Ravi") == Cadence(0, None, None, None) 488 assert profile_surface.cadence("Ravi", include_mentions=True) == Cadence( 489 1, "20260418", None, None 490 ) 491 492 493def test_self_view_returns_is_self_true(tmp_path, monkeypatch): 494 from solstone.think.surfaces import profile as profile_surface 495 496 _configure_env(tmp_path, monkeypatch) 497 _minimal_facet_tree( 498 tmp_path, 499 journal_entities=( 500 { 501 "id": "romeo_montague", 502 "name": "Romeo Montague", 503 "type": "Person", 504 "aka": ["RM"], 505 "is_principal": True, 506 }, 507 ), 508 ) 509 _write_facet_relationship(tmp_path, "work", "romeo_montague", description="Founder") 510 511 profile = profile_surface.full("RM") 512 513 assert profile is not None 514 assert profile.is_self is True 515 516 517def test_full_composes_ledger_open_loops(tmp_path, monkeypatch): 518 from solstone.think.surfaces import ledger as ledger_surface 519 from solstone.think.surfaces import profile as profile_surface 520 521 _configure_env(tmp_path, monkeypatch) 522 _minimal_facet_tree( 523 tmp_path, 524 journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},), 525 ) 526 _write_facet_relationship(tmp_path, "work", "ravi", description="Customer") 527 _write_story_activity( 528 "work", 529 "20260418", 530 "meeting_090000_300", 531 _utc_ms("20260418", 9), 532 commitments=[_commitment(counterparty="Ravi", counterparty_entity_id="ravi")], 533 ) 534 535 expected = tuple(ledger_surface.list(state="open", counterparty="ravi")) 536 profile = profile_surface.full("Ravi") 537 538 assert profile is not None 539 assert profile.open_with_them == expected 540 541 542def test_full_composes_ledger_closed_30d(tmp_path, monkeypatch): 543 from solstone.think.surfaces import ledger as ledger_surface 544 from solstone.think.surfaces import profile as profile_surface 545 546 _configure_env(tmp_path, monkeypatch) 547 _minimal_facet_tree( 548 tmp_path, 549 journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},), 550 ) 551 _write_facet_relationship(tmp_path, "work", "ravi", description="Customer") 552 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 553 _write_story_activity( 554 "work", 555 "20260410", 556 "meeting_090000_300", 557 _utc_ms("20260410", 9), 558 commitments=[_commitment(counterparty="Ravi", counterparty_entity_id="ravi")], 559 ) 560 _write_story_activity( 561 "work", 562 "20260415", 563 "meeting_100000_300", 564 _utc_ms("20260415", 10), 565 closures=[ 566 { 567 "owner": "Mina", 568 "owner_entity_id": "mina", 569 "action": "send proposal", 570 "counterparty": "Ravi", 571 "counterparty_entity_id": "ravi", 572 "resolution": "sent", 573 "context": "Closure context.", 574 } 575 ], 576 ) 577 _write_story_activity( 578 "work", 579 "20260301", 580 "meeting_080000_300", 581 _utc_ms("20260301", 8), 582 commitments=[ 583 _commitment( 584 action="archive contract", 585 counterparty="Ravi", 586 counterparty_entity_id="ravi", 587 ) 588 ], 589 ) 590 _write_story_activity( 591 "work", 592 "20260305", 593 "meeting_083000_300", 594 _utc_ms("20260305", 8), 595 closures=[ 596 { 597 "owner": "Mina", 598 "owner_entity_id": "mina", 599 "action": "archive contract", 600 "counterparty": "Ravi", 601 "counterparty_entity_id": "ravi", 602 "resolution": "archived", 603 "context": "Old closure context.", 604 } 605 ], 606 ) 607 608 expected = tuple( 609 ledger_surface.list( 610 state="closed", 611 closed_since=profile_surface._day_minus(30), # noqa: SLF001 612 counterparty="ravi", 613 ) 614 ) 615 profile = profile_surface.full("Ravi") 616 617 assert profile is not None 618 assert profile.closed_with_them_30d == expected 619 assert len(profile.closed_with_them_30d) >= 1 620 assert all( 621 item.action != "archive contract" for item in profile.closed_with_them_30d 622 ) 623 624 625def test_full_composes_ledger_decisions(tmp_path, monkeypatch): 626 from solstone.think.surfaces import ledger as ledger_surface 627 from solstone.think.surfaces import profile as profile_surface 628 629 _configure_env(tmp_path, monkeypatch) 630 _minimal_facet_tree( 631 tmp_path, 632 journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},), 633 ) 634 _write_facet_relationship(tmp_path, "work", "ravi", description="Customer") 635 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 636 _write_story_activity( 637 "work", 638 "20260419", 639 "meeting_090000_300", 640 _utc_ms("20260419", 9), 641 decisions=[_decision(owner="Ravi", owner_entity_id="ravi")], 642 ) 643 644 expected = tuple(ledger_surface.decisions(involving="ravi")) 645 profile = profile_surface.full("Ravi") 646 647 assert profile is not None 648 assert profile.decisions_involving_them == expected 649 assert len(profile.decisions_involving_them) >= 1 650 651 652def test_full_decisions_involving_them_includes_old(tmp_path, monkeypatch): 653 from solstone.think.surfaces import profile as profile_surface 654 655 _configure_env(tmp_path, monkeypatch) 656 _minimal_facet_tree( 657 tmp_path, 658 journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},), 659 ) 660 _write_facet_relationship(tmp_path, "work", "ravi", description="Customer") 661 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 662 _write_story_activity( 663 "work", 664 "20260301", 665 "meeting_090000_300", 666 _utc_ms("20260301", 9), 667 decisions=[_decision(owner="Ravi", owner_entity_id="ravi")], 668 ) 669 670 profile = profile_surface.full("Ravi") 671 672 assert profile is not None 673 assert any( 674 decision.day == "20260301" for decision in profile.decisions_involving_them 675 ) 676 677 678def test_not_found_returns_none(tmp_path, monkeypatch): 679 from solstone.think.surfaces import profile as profile_surface 680 681 _configure_env(tmp_path, monkeypatch) 682 _minimal_facet_tree(tmp_path) 683 684 assert profile_surface.full("missing") is None 685 686 687def test_brief_shape_and_counts(tmp_path, monkeypatch): 688 from solstone.think.surfaces import ledger as ledger_surface 689 from solstone.think.surfaces import profile as profile_surface 690 691 _configure_env(tmp_path, monkeypatch) 692 _minimal_facet_tree( 693 tmp_path, 694 journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},), 695 ) 696 _write_facet_relationship(tmp_path, "work", "ravi", description="Customer") 697 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 698 _write_story_activity( 699 "work", 700 "20260418", 701 "meeting_090000_300", 702 _utc_ms("20260418", 9), 703 commitments=[_commitment(counterparty="Ravi", counterparty_entity_id="ravi")], 704 ) 705 _write_story_activity( 706 "work", 707 "20260419", 708 "meeting_100000_300", 709 _utc_ms("20260419", 10), 710 decisions=[_decision(owner="Ravi", owner_entity_id="ravi")], 711 ) 712 713 brief = profile_surface.brief("Ravi") 714 715 assert brief is not None 716 assert tuple(brief.__dataclass_fields__) == ( 717 "entity_id", 718 "name", 719 "type", 720 "description", 721 "last_seen", 722 "open_loop_count", 723 "decisions_count_30d", 724 ) 725 assert brief.entity_id == "ravi" 726 assert brief.name == "Ravi" 727 assert brief.type == "Person" 728 assert brief.description == "Customer" 729 assert brief.last_seen is None 730 assert brief.open_loop_count == len( 731 ledger_surface.list(state="open", counterparty="ravi") 732 ) 733 assert brief.decisions_count_30d == len( 734 ledger_surface.decisions(involving="ravi", since="20260321") 735 ) 736 assert not hasattr(brief, "is_self") 737 assert not hasattr(brief, "generated_at") 738 739 740def test_list_active_sort_dedup_window(tmp_path, monkeypatch): 741 from solstone.think.surfaces import profile as profile_surface 742 743 _configure_env(tmp_path, monkeypatch) 744 _minimal_facet_tree(tmp_path, facets=("personal", "work")) 745 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 746 747 _append_activity( 748 "work", 749 "20260418", 750 _activity_record( 751 "20260418", 752 [ 753 _participant("zoe", name="Zoe"), 754 _participant("anna", name="Anna"), 755 _participant(None, name="Unknown"), 756 ], 757 record_id="meeting_work", 758 ), 759 ) 760 _append_activity( 761 "personal", 762 "20260419", 763 _activity_record( 764 "20260419", 765 [_participant("anna", name="Anna")], 766 record_id="meeting_personal", 767 ), 768 ) 769 _append_activity( 770 "work", 771 "20260301", 772 _activity_record( 773 "20260301", 774 [_participant("outside_window", name="Outside")], 775 record_id="meeting_old", 776 ), 777 ) 778 779 assert profile_surface.list_active(window_days=30) == ["anna", "zoe"] 780 781 782def test_list_active_excludes_mentioned(tmp_path, monkeypatch): 783 from solstone.think.surfaces import profile as profile_surface 784 785 _configure_env(tmp_path, monkeypatch) 786 _minimal_facet_tree(tmp_path) 787 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 788 _append_activity( 789 "work", 790 "20260418", 791 _activity_record( 792 "20260418", 793 [_participant("ravi", name="Ravi", role="mentioned")], 794 record_id="meeting_mentioned", 795 ), 796 ) 797 798 assert profile_surface.list_active(window_days=30) == [] 799 800 801def test_muted_facet_included_in_cadence(tmp_path, monkeypatch): 802 from solstone.think.surfaces import profile as profile_surface 803 804 _configure_env(tmp_path, monkeypatch) 805 _minimal_facet_tree( 806 tmp_path, 807 facets=("quiet",), 808 muted_facets=("quiet",), 809 journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},), 810 ) 811 _write_facet_relationship(tmp_path, "quiet", "ravi", description="Muted facet") 812 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 813 _append_activity( 814 "quiet", 815 "20260418", 816 _activity_record( 817 "20260418", 818 [_participant("ravi", name="Ravi")], 819 record_id="meeting_quiet", 820 ), 821 ) 822 823 cadence = profile_surface.cadence("Ravi") 824 assert cadence == Cadence(1, "20260418", None, None) 825 826 827def test_utc_day_math(tmp_path, monkeypatch): 828 from solstone.think.surfaces import profile as profile_surface 829 830 _configure_env(tmp_path, monkeypatch) 831 832 assert re.fullmatch(r"\d{8}", profile_surface._today_day()) is not None # noqa: SLF001 833 monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420") 834 assert profile_surface._day_minus(30) == "20260321" # noqa: SLF001