Python CLI tool that fetches AI/agent-related Internet-Drafts from the IETF Datatracker, rates them using Claude, generates embeddings via Ollama for similarity/clustering, and produces markdown reports. Features: - Fetch drafts by keyword from Datatracker API with full text download - Batch analysis with Claude (token-optimized, responses cached in SQLite) - Embedding-based similarity search and overlap cluster detection - Reports: overview, landscape by category, overlap clusters, weekly digest - SQLite with FTS5 for full-text search across 260 tracked drafts Initial analysis of 260 drafts reveals OAuth agent auth (13 drafts) and agent gateway/collaboration (10 drafts) as the most crowded clusters, while AI safety/alignment is underserved with the highest quality scores. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
73 lines
2.1 KiB
Python
73 lines
2.1 KiB
Python
"""Data models for drafts, ratings, and categories."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
|
|
|
|
@dataclass
|
|
class Draft:
|
|
name: str # e.g. "draft-zheng-dispatch-agent-identity-management"
|
|
rev: str # e.g. "00"
|
|
title: str
|
|
abstract: str
|
|
time: str # ISO datetime from API
|
|
dt_id: int | None = None # Datatracker document ID
|
|
pages: int | None = None
|
|
words: int | None = None
|
|
group: str | None = None # Working group name (resolved)
|
|
group_uri: str | None = None # Raw API URI
|
|
expires: str | None = None
|
|
ad: str | None = None # Area director URI
|
|
shepherd: str | None = None
|
|
states: list[str] = field(default_factory=list)
|
|
full_text: str | None = None
|
|
categories: list[str] = field(default_factory=list)
|
|
tags: list[str] = field(default_factory=list)
|
|
fetched_at: str | None = None
|
|
|
|
@property
|
|
def text_url(self) -> str:
|
|
return f"https://www.ietf.org/archive/id/{self.name}-{self.rev}.txt"
|
|
|
|
@property
|
|
def datatracker_url(self) -> str:
|
|
return f"https://datatracker.ietf.org/doc/{self.name}/"
|
|
|
|
@property
|
|
def date(self) -> str:
|
|
"""Return just the date portion of time."""
|
|
if self.time:
|
|
return self.time[:10]
|
|
return ""
|
|
|
|
|
|
@dataclass
|
|
class Rating:
|
|
draft_name: str
|
|
novelty: int # 1-5
|
|
maturity: int # 1-5
|
|
overlap: int # 1-5 (5 = highly overlapping with others)
|
|
momentum: int # 1-5
|
|
relevance: int # 1-5
|
|
summary: str # 2-4 sentence AI summary
|
|
novelty_note: str = ""
|
|
maturity_note: str = ""
|
|
overlap_note: str = ""
|
|
momentum_note: str = ""
|
|
relevance_note: str = ""
|
|
categories: list[str] = field(default_factory=list)
|
|
rated_at: str | None = None
|
|
|
|
@property
|
|
def composite_score(self) -> float:
|
|
"""Weighted composite: novelty and relevance matter most."""
|
|
return (
|
|
self.novelty * 0.30
|
|
+ self.relevance * 0.25
|
|
+ self.maturity * 0.20
|
|
+ self.momentum * 0.15
|
|
+ (6 - self.overlap) * 0.10 # Invert: less overlap = better
|
|
)
|