#!/usr/bin/env python """Хардас — сборщик потребностей клиентов студии (t-261002072440-c482ca). Мандат Alex (голосовое m-516173358): поставить хардас клиентам студии, которые его ждут; потребностиclients собирать самим, дёргая Alex поменьше. Этот скрипт — двигалка фазы «сбор»: survey — отправить опросник потребностей клиенту в его чат (Telegram через штатный tg_inbox — тот же бот, что и реплаи; ключ идемпотентности задаётся --key, повтор = SKIP без дубля); collect — пройтись по недавним чатам (TG-инбокс стора) и выписать сигналы потребностей в docs/hardass/needs/needs-.md (дедуп: повторный запуск не плодит копий — перезаписывает период); потребности агрегируются, сырье остаётся в сторе. Использование: ./venv/bin/python scripts/needs_survey.py survey --key needs-2026-10-w1 ./venv/bin/python scripts/needs_survey.py collect Опросник сформулирован по фактическому составу поставки (см. docs/hardass/INSTALL.md, PACKAGE-SIMBIOT-PRESALE.md): спрашиваем о том, что харнес реально умеет сейчас. """ import argparse import json import os import sys from datetime import datetime, timezone ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, ROOT) NEEDS_DIR = os.path.join(ROOT, "docs", "hardass", "needs") SURVEY_TEXT = """🤖 Хардас подключён — опрос потребностей (1 мин, можно голосом) Я — ваш личный хардас Simbiot: живу тактами, сам выполняю задачи и докладываю по графику. Чтобы настроить меня под вас, ответьте на 4 коротких блока — можно в одном сообщении или голосовыми: 1️⃣ РУТИНА: какие 2-3 повторяющихся дела хочется отдать хардасу (например: отчёты, мониторинг, файлы, письма, посты). 2️⃣ ЧАТ И РИТМ: как удобнее общаться — здесь (Telegram-бот) или на портале (страница чата)? Прислать дневной итог работы в удобное время? 3️⃣ ЗНАНИЯ: что хардасу стоит знать о вашем деле (ссылки/доки/описание процессов) — пришлите что есть, я проиндексирую и буду отвечать по ним. 4️⃣ БОЛЬ ПРЯМО СЕЙЧАС: что сегодня делается вручную и раздражает больше всего? С этого начну — первую автоматизацию возьму в работу сам. Ответы копятся в базе потребностей — реализация идёт по ним без лишних вопросов. 🚀""" SURVEY_KEY = "needs-survey-v1" def send_survey(key: str) -> str: """Отправка опросника: идемпотентно через deliver_artifact.""" from harness import commands, tg_inbox, es es.init_db() aid = f"msg:{key or SURVEY_KEY}" def _send(): return bool(tg_inbox.notify_chat(SURVEY_TEXT)) ok = commands.deliver_artifact(aid, kind="message", send=_send, owner="needs-survey", caption="needs-survey") return ("OK: опросник отправлен" if ok else "SKIP: уже доставлен (или некому отправить / чужой захват)") def _signals_from_inbox(limit_per_chat: int = 12): """Свежие входящие из стора по всем чатам (без сырья наружу — тезисы).""" from harness import es model = es.run_projections() out = {} for mid, m in sorted((model.get("inbox") or {}).items(), key=lambda kv: str(kv[1].get("date") or ""))[-60:]: if not isinstance(m, dict): continue chat = str(m.get("chat_id") or "?") text = str(m.get("transcript") or m.get("text") or "").strip() if not text: continue out.setdefault(chat, []).append({ "mid": mid, "who": str(m.get("first_name") or m.get("username") or "")[:40], "date": str(m.get("date") or "")[:19], "kind": str(m.get("kind") or "text"), "text": text[:600], }) return {c: v[-limit_per_chat:] for c, v in out.items()} def collect() -> str: """Агрегация сигналов потребностей в docs/hardass/needs/.""" os.makedirs(NEEDS_DIR, exist_ok=True) period = datetime.now(timezone.utc).strftime("%Y-%m-%d_%H") path = os.path.join(NEEDS_DIR, f"needs-{period}.md") signals = _signals_from_inbox() if not signals: return "нет свежих входящих в сторе — collect сделан (пустой период)" lines = [ f"# Потребности клиентов — {period} (UTC)", "", "Автосборка needs_survey.py (хардас, t-261002072440). Источник: " "входящие чатов харнеса (TG/портал). Сырёё — тезисы, полные тексты " "в ES-сторе (MessageReceived).", "", ] n = 0 for chat, msgs in signals.items(): lines.append(f"## Чат {chat}") for m in msgs: lines.append(f"- `{m['mid']}` {m['date']} {m['who'] or '—'} " f"({m['kind']}): {m['text'][:300]}") n += 1 lines.append("") body = "\n".join(lines) # перезапись того же периода — дедуп по построению (периодный файл) tmp = path + ".tmp" with open(tmp, "w", encoding="utf-8") as f: f.write(body) os.replace(tmp, path) return f"OK: {n} сигналов из {len(signals)} чатов → {path}" def main(): ap = argparse.ArgumentParser(description="Сбор потребностей клиентов хардаса") ap.add_argument("cmd", choices=["survey", "collect"]) ap.add_argument("--key", default=SURVEY_KEY, help="ключ идемпотентности отправки (повтор = SKIP)") args = ap.parse_args() if args.cmd == "survey": print(send_survey(args.key)) else: print(collect()) if __name__ == "__main__": main()