IETF Draft Analyzer v0.1.0 — track, categorize, and rate AI/agent drafts

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>
This commit is contained in:
2026-02-28 00:36:45 +01:00
commit 6771a4c235
17 changed files with 2823 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
"""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
)