import random
from pathlib import Path
from devplacepy.config import INTERNAL_GATEWAY_URL, INTERNAL_MODEL
BASE_URL_DEFAULT = "https://pravda.education"
API_URL_DEFAULT = INTERNAL_GATEWAY_URL
NEWS_API_DEFAULT = "https://news.app.molodetz.nl/api"
MODEL_DEFAULT = INTERNAL_MODEL
INPUT_COST_PER_1M_DEFAULT = 0.27
OUTPUT_COST_PER_1M_DEFAULT = 1.10
COST_WINDOW_SECONDS = 600
COST_WARMUP_SECONDS = 120
STATE_DIR = Path.home() / ".devplace_bots"
ARTICLE_REGISTRY_PATH = Path.home() / ".dpbot_article_registry.json"
PROJECT_STATUSES = ["In Development", "Released"]
ARTICLE_TTL_DAYS = 7
ACTION_PAUSE_MIN_SECONDS = 5
ACTION_PAUSE_MAX_SECONDS = 30
BREAK_SCALE_DEFAULT = 1.0
AI_DECISIONS_DEFAULT = False
DECISION_TEMPERATURE_DEFAULT = 0.4
PERSONAS = [
"enthusiastic_junior",
"grumpy_senior",
"hobbyist_maker",
"academic_type",
"minimalist",
"storyteller",
"rebel",
"mentor",
]
REACT_RATES = {
"enthusiastic_junior": 0.50,
"hobbyist_maker": 0.40,
"storyteller": 0.30,
"mentor": 0.30,
"rebel": 0.25,
"academic_type": 0.20,
"minimalist": 0.12,
"grumpy_senior": 0.12,
}
REACT_RATE_DEFAULT = 0.20
CATEGORIES = ["devlog", "showcase", "question", "rant", "fun", "random"]
GIST_LANGUAGES = [
"python",
"javascript",
"typescript",
"bash",
"go",
"rust",
"sql",
"html",
"css",
"java",
"c",
"cpp",
"ruby",
"php",
"lua",
]
FEED_TOPICS = ["devlog", "showcase", "question", "rant", "fun"]
PROJECT_TYPES = ["game", "game_asset", "software", "mobile_app", "website"]
SEARCH_TERMS = {
"enthusiastic_junior": ["vibe coding", "first app", "react", "python", "ai tools"],
"grumpy_senior": ["rust", "perl", "legacy", "tech debt", "kubernetes"],
"hobbyist_maker": [
"arduino",
"raspberry pi",
"side project",
"3d printing",
"game",
],
"academic_type": ["algorithms", "type theory", "distributed systems", "compilers"],
"minimalist": ["cli", "vim", "golang", "suckless"],
"storyteller": ["postmortem", "war story", "migration", "outage"],
"rebel": ["hot take", "overrated", "monorepo", "microservices"],
"mentor": ["best practices", "code review", "testing", "career"],
}
MIN_COMMENT_LEN = 40
MIN_POST_LEN = 120
RESTATEMENT_OVERLAP_THRESHOLD = 0.6
GENERIC_COMMENT_PHRASES = [
"great point",
"good point",
"i agree",
"totally agree",
"well said",
"nice post",
"great post",
"thanks for sharing",
"interesting read",
"this is interesting",
"so true",
"couldn't agree more",
"spot on",
"great article",
"love this",
"this is great",
"good read",
]
GIST_MIN_LINES = 6
TRIVIAL_GIST_TERMS = [
"ternary",
"hello world",
"hello, world",
"fizzbuzz",
"fizz buzz",
"delete pointer",
"free pointer",
"getter",
"setter",
"increment",
"decrement",
"swap two",
"swap variables",
"is even",
"is odd",
"reverse a string",
"reverse string",
"print hello",
"for loop",
"while loop",
"basic loop",
"add two numbers",
"sum two",
"concatenate strings",
"string concat",
"variable declaration",
"comment example",
]
PERSONA_LANGUAGES = {
"enthusiastic_junior": ["python", "javascript", "typescript", "html", "css"],
"grumpy_senior": ["c", "cpp", "rust", "sql", "bash"],
"hobbyist_maker": ["python", "cpp", "lua", "javascript", "bash"],
"academic_type": ["rust", "python", "go", "sql", "cpp"],
"minimalist": ["go", "c", "bash", "lua"],
"storyteller": ["python", "ruby", "javascript", "sql"],
"rebel": ["rust", "go", "typescript", "bash"],
"mentor": ["python", "java", "typescript", "sql"],
}
PERSONA_GIST_FLAVOR = {
"enthusiastic_junior": "a handy helper you recently learned and were glad to discover",
"grumpy_senior": "a battle-tested utility that sidesteps a common footgun",
"hobbyist_maker": "a fun, hands-on snippet for a side project or hardware tinkering",
"academic_type": "an elegant algorithm or data-structure trick",
"minimalist": "a tiny, sharp, dependency-free utility",
"storyteller": "a snippet born from a real debugging or production story",
"rebel": "an unconventional approach that beats the obvious one",
"mentor": "a clear, reusable helper worth teaching to a junior",
}
PERSONA_CATEGORY_WEIGHTS = {
"enthusiastic_junior": {
"showcase": 3,
"question": 3,
"devlog": 2,
"fun": 2,
"random": 1,
"rant": 1,
},
"grumpy_senior": {
"rant": 4,
"devlog": 2,
"random": 2,
"question": 1,
"showcase": 1,
"fun": 1,
},
"hobbyist_maker": {
"showcase": 3,
"devlog": 3,
"fun": 2,
"random": 2,
"question": 1,
"rant": 1,
},
"academic_type": {
"devlog": 3,
"question": 3,
"random": 2,
"showcase": 1,
"rant": 1,
"fun": 1,
},
"minimalist": {
"random": 3,
"rant": 2,
"devlog": 2,
"showcase": 1,
"question": 1,
"fun": 1,
},
"storyteller": {
"devlog": 4,
"showcase": 2,
"fun": 2,
"random": 2,
"question": 1,
"rant": 1,
},
"rebel": {
"rant": 4,
"fun": 2,
"question": 2,
"random": 1,
"showcase": 1,
"devlog": 1,
},
"mentor": {
"devlog": 3,
"question": 3,
"showcase": 2,
"random": 1,
"rant": 1,
"fun": 1,
},
}
MAX_BOTS_PER_ARTICLE = 2
def pick_category(persona: str) -> str:
weights = PERSONA_CATEGORY_WEIGHTS.get(persona)
if not weights:
return random.choice(CATEGORIES)
population = list(weights.keys())
return random.choices(population, weights=list(weights.values()), k=1)[0]
def persona_article_score(article: dict, persona: str) -> float:
terms = SEARCH_TERMS.get(persona)
if not terms:
return 0.0
haystack = f"{article.get('title', '')} {article.get('description', '')}".lower()
return float(sum(1 for term in terms if term.lower() in haystack))