Files
human-experience-filter-art/tools/drafting.py
T
Ben Stull 3d5c34491c feat: heuristic coordinate proposer (drafting)
Per sub-project-2 plan Task 7 / spec §7. HeuristicProposer seeds the brain plane
from archive priors and the mood plane from title/description keyword nudges,
clamps to 0..4, and emits a one-line rationale. Deterministic, no I/O, no ML
(honors design §11) — only a DRAFT a human reviews.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 06:27:37 -07:00

91 lines
2.8 KiB
Python

"""Heuristic coordinate proposer — a deterministic, rule-based DRAFT seed.
The four curatorial coordinates are a human act (design §11: no automatic ML
coordinate tagging). This proposer only suggests a starting point written as
review_status='proposed'; a human blesses every coordinate before selection.
There is no ML model here — just archive priors (brain plane) and keyword
nudges (mood plane), grounded in the §8 sourcing→axis map.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Protocol
from hef.selection import Coordinate
COORD_MIN, COORD_MAX = 0, 4
# Brain plane (left = verbal/spoken/educational, right = music/wordless/abstract).
_ARCHIVE_PRIORS = {
"librivox": (4, 0), # spoken readings
"internet_archive": (3, 1), # educational / industrial / Prelinger
"musopen": (0, 4), # classical music
"fma": (0, 4), # music
"nasa": (0, 4), # wordless awe
"freesound": (0, 4), # abstract field recordings
}
_ARCHIVE_DEFAULT = (2, 2)
# Mood plane keywords (substring match so "thunderstorm" trips "storm").
_DARK_WORDS = (
"storm", "night", "noir", "requiem", "minor", "war", "funeral",
"decay", "death", "grief", "dusk", "winter", "mourning", "shadow",
)
_LIGHT_WORDS = (
"sunrise", "dawn", "garden", "spring", "joy", "hope", "major",
"bright", "bloom", "summer", "smile", "light", "morning",
)
def _clamp(v: int) -> int:
return max(COORD_MIN, min(COORD_MAX, v))
@dataclass
class Signals:
title: str
description: str
source_archive: str
mode: str
duration_s: int
@dataclass
class Draft:
coordinate: Coordinate
rationale: str
class Proposer(Protocol):
def propose(self, signals: Signals) -> Draft: ...
class HeuristicProposer:
def propose(self, signals: Signals) -> Draft:
left, right = _ARCHIVE_PRIORS.get(
signals.source_archive, _ARCHIVE_DEFAULT
)
text = f"{signals.title} {signals.description}".lower()
dark_hits = [w for w in _DARK_WORDS if w in text]
light_hits = [w for w in _LIGHT_WORDS if w in text]
dark = _clamp(len(dark_hits))
light = _clamp(len(light_hits))
coordinate = Coordinate(_clamp(left), _clamp(right), dark, light)
# Rationale: name the dominant brain prior + any mood keywords.
brain = (
f"{signals.source_archive}"
+ ("strong left" if left >= right else "strong right")
)
mood_bits = []
if dark_hits:
mood_bits.append(f"{dark_hits[0]!r} → dark")
if light_hits:
mood_bits.append(f"{light_hits[0]!r} → light")
rationale = "; ".join([brain, *mood_bits])
return Draft(coordinate=coordinate, rationale=rationale)