Files
microdao-daarion/services/city-service/tools/fix_microdao_agent_consistency.py
Apple 3ccc0e2d43 feat: TASK 037A/B - MicroDAO Multi-Room Cleanup & UI Polish
TASK 037A: Backend Consistency
- Added db/sql/037_microdao_agent_audit.sql
- Added services/city-service/tools/fix_microdao_agent_consistency.py
- Updated repo_city.get_public_citizens with stricter filtering (node_id, microdao_membership)
- Updated PublicCitizenSummary model to include home_microdao and primary_city_room
- Updated NodeProfile model and get_node_by_id to include microdaos list

TASK 037B: UI Polish
- Updated MicrodaoRoomsSection with role-based colors/icons and mini-map
- Updated /microdao/[slug] with new Hero Block (badges, stats, orchestrator)
- Updated /citizens/[slug] with MicroDAO cross-link in DAIS profile
- Updated /nodes/[nodeId] with MicroDAO Presence section
2025-11-29 01:35:54 -08:00

178 lines
6.2 KiB
Python

import asyncio
import os
import sys
import argparse
import asyncpg
from uuid import UUID
# Add parent directory to path to allow importing from app if needed,
# though we will try to use direct SQL for maintenance script ease.
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@postgres:5432/daarion")
async def get_connection():
return await asyncpg.connect(DATABASE_URL)
async def fix_agents(conn, apply=False):
print("--- CHECKING AGENTS ---")
# 1. Agents with missing public_slug
rows = await conn.fetch("""
SELECT id, display_name FROM agents
WHERE is_public = true AND (public_slug IS NULL OR public_slug = '')
""")
if rows:
print(f"Found {len(rows)} public agents without public_slug.")
for r in rows:
print(f" - {r['display_name']} ({r['id']}) -> will set to {r['id']}")
if apply:
await conn.execute("""
UPDATE agents SET public_slug = id
WHERE id = $1
""", r['id'])
else:
print("OK: All public agents have public_slug.")
# 2. Agents without node_id
# We assume NODE1 as default fallback if we must set it, but per task we mostly log it.
rows = await conn.fetch("""
SELECT id, display_name FROM agents
WHERE is_public = true AND (node_id IS NULL OR node_id = '')
""")
if rows:
print(f"Found {len(rows)} public agents without node_id (WARNING).")
for r in rows:
print(f" - {r['display_name']} ({r['id']})")
# Option: could set to default node if needed, but task says "optionally try to set".
# We will skip auto-setting for now to avoid assigning wrong node.
else:
print("OK: All public agents have node_id.")
# 3. Agents without microdao memberships
rows = await conn.fetch("""
SELECT a.id, a.display_name
FROM agents a
WHERE a.is_public = true
AND NOT EXISTS (SELECT 1 FROM microdao_agents ma WHERE ma.agent_id = a.id)
""")
if rows:
print(f"Found {len(rows)} public agents without MicroDAO membership (WARNING).")
for r in rows:
print(f" - {r['display_name']} ({r['id']})")
else:
print("OK: All public agents have at least one MicroDAO membership.")
async def fix_microdaos(conn, apply=False):
print("\n--- CHECKING MICRODAOS ---")
# 1. MicroDAO without rooms
rows = await conn.fetch("""
SELECT m.id, m.slug, m.name
FROM microdaos m
WHERE m.is_public = true
AND NOT EXISTS (SELECT 1 FROM city_rooms cr WHERE cr.microdao_id = m.id)
""")
if rows:
print(f"Found {len(rows)} public MicroDAOs without rooms (WARNING).")
for r in rows:
print(f" - {r['name']} ({r['slug']})")
else:
print("OK: All public MicroDAOs have at least one room.")
# 2. MicroDAO without PRIMARY room (but has rooms)
rows = await conn.fetch("""
SELECT m.id, m.slug, m.name
FROM microdaos m
WHERE m.is_public = true
AND EXISTS (SELECT 1 FROM city_rooms cr WHERE cr.microdao_id = m.id)
AND NOT EXISTS (SELECT 1 FROM city_rooms cr WHERE cr.microdao_id = m.id AND cr.room_role = 'primary')
""")
if rows:
print(f"Found {len(rows)} MicroDAOs with rooms but NO primary room.")
for r in rows:
print(f" - {r['name']} ({r['slug']})")
# Find candidate: lowest sort_order
candidate = await conn.fetchrow("""
SELECT id, name, sort_order FROM city_rooms
WHERE microdao_id = $1
ORDER BY sort_order ASC, name ASC
LIMIT 1
""", r['id'])
if candidate:
print(f" -> Candidate: {candidate['name']} (sort: {candidate['sort_order']})")
if apply:
print(" -> Setting as primary...")
await conn.execute("""
UPDATE city_rooms SET room_role = 'primary', sort_order = 0
WHERE id = $1
""", candidate['id'])
else:
print("OK: All MicroDAOs with rooms have a primary room.")
# 3. MicroDAO with MULTIPLE primary rooms
rows = await conn.fetch("""
SELECT m.id, m.slug, m.name
FROM microdaos m
JOIN city_rooms cr ON cr.microdao_id = m.id
WHERE cr.room_role = 'primary'
GROUP BY m.id, m.slug, m.name
HAVING COUNT(*) > 1
""")
if rows:
print(f"Found {len(rows)} MicroDAOs with MULTIPLE primary rooms.")
for r in rows:
print(f" - {r['name']} ({r['slug']})")
primaries = await conn.fetch("""
SELECT id, name, sort_order FROM city_rooms
WHERE microdao_id = $1 AND room_role = 'primary'
ORDER BY sort_order ASC, name ASC
""", r['id'])
# Keep the first one, demote others
keep = primaries[0]
others = primaries[1:]
print(f" -> Keeping: {keep['name']}")
for o in others:
print(f" -> Demoting: {o['name']}")
if apply:
await conn.execute("""
UPDATE city_rooms SET room_role = 'team'
WHERE id = $1
""", o['id'])
else:
print("OK: No MicroDAOs have multiple primary rooms.")
async def main():
parser = argparse.ArgumentParser(description="Fix MicroDAO/Agent consistency")
parser.add_argument("--apply", action="store_true", help="Apply changes to DB")
args = parser.parse_args()
print(f"Connecting to {DATABASE_URL}...")
try:
conn = await get_connection()
except Exception as e:
print(f"Failed to connect to DB: {e}")
return
try:
await fix_agents(conn, args.apply)
await fix_microdaos(conn, args.apply)
finally:
await conn.close()
print("\nDone.")
if __name__ == "__main__":
asyncio.run(main())