This document explains how each of the tools in our editors work. Please consult the table below to jump to any particular reference point.
A manifest is defined as a JSON object with two main sections:
filter
: Contains conditions that specify when the algorithm should classify a record as a match. Each condition type has its own syntax.models
: Defines the machine learning models and associated feature modules used by model_probability
.{
"filter": {
"and": [
{
"attribute_compare": [
"embed.external.uri",
"==",
"https://www.youtube.com/watch?v=E8Ew6K0W3RY"
]
},
{
"regex_matches": ["text", "\\bthe\\b"]
},
{
"regex_negation_matches": ["text", "\\bunwanted_term\\b"]
},
{
"user_network": ["devingaffney.com", "in", "follows"]
},
{
"text_similarity": [
"text",
{
"anchor_text": "This is an important update",
"model_name": "all-MiniLM-L6-v2"
},
">=",
0.3
]
},
{
"model_probability": [
{
"model_name": "news_without_science"
},
">=",
0.9
]
}
]
},
"models": [
{
"feature_modules": [
{
"type": "time_features"
},
{
"model_name": "all-MiniLM-L6-v2",
"type": "vectorizer"
},
{
"type": "post_metadata"
}
],
"model_name": "news_without_science"
}
]
}
The filter
section defines the logical structure of the filtering criteria. Each criterion checks specific attributes, matches patterns, or evaluates machine learning models based on record data.
The attribute_compare
operation allows comparing an attribute of a record to a target value.
{
"attribute_compare": [
"<selector>",
"<operator>",
"<target_value>"
]
}
selector
: Specifies the JSONPath-like path to the attribute in the record.<operator>
: Comparison operator (e.g., ==
, >
, >=
, <
, <=
).<target_value>
: The target value to compare the attribute against.{
"attribute_compare": [
"post.blah.foo",
"==",
"bar"
]
}
The embed_type
is a shorthand to explicitly select or reject posts based on the nature of embedded content.
{
"embed_type": [
"<equality_operator>",
"<embed_name>",
]
}
<equality_operator>
: Either !=
or ==
.<embed_name>
: Type of Embed to select or reject upon - any of image
, link
, post
, image_group
, video
, gif
.{
"embed_type": [
"==",
"video"
]
}
The entity_matches
and entity_excludes
allows you to select or reject posts based on whether they match a set of passed-along values for a given entity type (urls
, domains
, mentions
, or hashtags
).
{
"entity_matches": [
"<entity_type>",
"<entity_values>",
]
}
<entity_type>
: Either langs
, urls
, domains
, mentions
, or hashtags
<entity_values>
: A list of values to match against - for langs, their ISO 639-1 codes, for users, their usernames (i.e. not a DID - we resolve internally) OR starter pack / list URLs, or hashtags without # symbol, or simple URLs{
"entity_matches": [
"hashtags",
["foo", "bar", "baz"]
]
}
The regex_matches
, regex_negation_matches
, regex_any
, and regex_none
operations match or negate a regular expression pattern in an attribute's value.
{
"regex_matches": [
"<var>",
"<regex_pattern>",
"<case_insensitivity>"
]
}
var
: Specifies the JSONPath-like path to the attribute in the record.<regex_pattern>
: A regular expression pattern to match against.<case_insensitivity>
: Whether or not the regex will operate as case-insensitive - when true
it will check any casing.{
"regex_matches": [
"text",
"\\bthe\\b",
false
]
}
{
"regex_negation_matches": [
"text",
"\\bunwanted_term\\b",
true
]
}
For regex_any
or regex_none
, we check the <var>
operate against a list of terms to check and only return true or false if any or none of the terms are present - think of it as a shorthand for not having to write a joined or
regex.
{
"regex_any": [
"<var>",
["<list>", "<of>", "<terms>"],
"<case_insensitivity>"
]
}
var
: Specifies the JSONPath-like path to the attribute in the record.<term_list>
: A set of terms to join together in an or-like check in a regex.<case_insensitivity>
: Whether or not the regex will operate as case-insensitive - when true
it will check any casing.{
"regex_any": [
"text",
["apple", "banana", "peach"],
true
]
}
{
"regex_none": [
"text",
["apple", "banana", "peach"],
false
]
}
The text_similarity
operation evaluates the similarity between the text in an attribute and an anchor text using a transformer model.
{
"text_similarity": [
"<attribute_path>",
{
"anchor_text": "<reference_text>",
"model_name": "<transformer_model_name>"
},
"<operator>",
"<threshold>"
]
}
var
: Path to the text attribute in the record.anchor_text
: The reference text to compare.model_name
: The name of the transformer model used for embeddings.<operator>
: Comparison operator, typically >=
for similarity.<threshold>
: The similarity threshold.{
"text_similarity": [
"text",
{
"anchor_text": "This is an important update",
"model_name": "all-MiniLM-L6-v2"
},
">=",
0.3
]
}
The model_probability
operation evaluates the likelihood that a record matches a specific classification using an XGBoost model.
{
"model_probability": [
{
"model_name": "<xgboost_model_name>"
},
"<operator>",
"<threshold>"
]
}
model_name
: The name of the XGBoost model used for classification.<operator>
: Comparison operator (e.g., >=
for probability).<threshold>
: Probability threshold to determine if the record meets the condition.{
"model_probability": [
{
"model_name": "news_without_science"
},
">=",
0.9
]
}
The social_graph
operation evaluates the inclusion or exclusion of user dids based on a source actor and a direction. Note that when using this, if you do not specify an author to act upon, we will use API requests from your signed-in account.
{
"social_graph": [
"<username>",
"<operator>",
"<direction>"
]
}
username
: The username to pull followers/follows from.<operator>
: either in
or not_in
.<direction>
: either follows
(i.e. users that username
follows) or followers
(i.e. users that username
is followed by){
"social_graph": [
"devingaffney.com",
"in",
"follows"
]
}
The social_list
allows you to specify the did's for a set of users to select/reject based on that list explicitly (i.e. if you don't want to just shorthand through a user account).
{
"social_list": [
"<did_list>",
"<operator>"
]
}
did_list
: The list of user did's to pull from<operator>
: either in
or not_in
.{
"social_list": [
["did:plc:ngokl2gnmpbvuvrfckja3g7p"],
"in"
]
}
The starter_pack_member
allows you to specify the URL for a starter pack of users to select/reject based on that list.
{
"starter_pack_member": [
"<starter_pack_url>",
"<operator>"
]
}
starter_pack_url
: The starter pack URL<operator>
: either in
or not_in
.{
"starter_pack_member": [
"https://bsky.app/starter-pack/propublica.org/3l6iflmcj322n",
"in"
]
}
The list_member
allows you to specify the URL for a list of users to select/reject based on that list.
{
"list_member": [
"<list_url>",
"<operator>"
]
}
list_url
: The list URL<operator>
: either in
or not_in
.{
"list_member": [
"https://bsky.app/profile/numb.comfortab.ly/lists/3lam62tvlqz2l",
"in"
]
}
The magic_audience
allows you to specify the ID of one of your magic audiences to select/reject based on that list.
{
"magic_audience": [
"<audience_id>",
"<operator>"
]
}
audience_id
: The Magic Audience ID<operator>
: either in
or not_in
.{
"list_member": [
"42",
"in"
]
}
The content_moderation
filter allows you to only accept/reject content matches your filter level for a given prediction. We use KoalaAI/Text-Moderation to scan post texts and classify them with our content moderation pipeline - currently it is English only but we intend to allow for multilingual predictions in the future. The current moderation categories we support are as follows:
To use a moderation filter, provide the category, an operator, and a target_value
as follows:
{
"content_moderation": [
"<moderation_category>",
"<operator>",
"<target_value>"
]
}
moderation_category
: The KoalaAI/Text-Moderation moderation category.<operator>
: Comparison operator (e.g., ==
, >
, >=
, <
, <=
).<target_value>
: The target value to compare the attribute against.{
"content_moderation": [
"OK",
">=",
0.9
]
}
language_analysis
machine_learning
'Japanese', 'Dutch', 'Arabic', 'Polish', 'German', 'Italian', 'Portuguese', 'Turkish', 'Spanish', 'Hindi', 'Greek', 'Urdu', 'Bulgarian', 'English', 'French', 'Chinese', 'Russian', 'Thai', 'Swahili', 'Vietnamese'
["language_name", "comparator", "threshold"]
{
"language_analysis": [
"Dutch",
"<=",
0.5
]
}
sentiment_analysis
machine_learning
'Positive', 'Negative', 'Neutral'
["sentiment_category", "comparator", "threshold"]
{
"sentiment_analysis": [
"Negative",
"<=",
0.5
]
}
financial_sentiment_analysis
machine_learning
'Positive', 'Negative', 'Neutral'
["financial_category", "comparator", "threshold"]
{
"financial_sentiment_analysis": [
"Positive",
">=",
0.5
]
}
emotion_sentiment_analysis
machine_learning
'Admiration', 'Amusement', 'Anger', 'Annoyance', 'Approval', 'Caring', 'Confusion', 'Curiosity', 'Desire', 'Disappointment', 'Disapproval', 'Disgust', 'Embarrassment', 'Excitement', 'Fear', 'Gratitude', 'Grief', 'Joy', 'Love', 'Nervousness', 'Optimism', 'Pride', 'Realization', 'Relief', 'Remorse', 'Sadness', 'Surprise', 'Neutral'
.["emotion_category", "comparator", "threshold"]
{
"emotion_sentiment_analysis": [
"Curiosity",
">=",
0.5
]
}
toxicity_analysis
machine_learning
'Toxic', 'Severe Toxicity', 'Obscene', 'Threat', 'Insult', 'Identity Hate'
.["toxic_category", "comparator", "threshold"]
{
"toxicity_analysis": [
"Obscene",
"<=",
0.5
]
}
topic_analysis
machine_learning
'Arts & Culture', 'Business & Entrepreneurs', 'Celebrity & Pop Culture', 'Diaries & Daily Life', 'Family', 'Fashion & Style', 'Film, TV & Video', 'Fitness & Health', 'Food & Dining', 'Gaming', 'Learning & Educational', 'Music', 'News & Social Concern', 'Other Hobbies', 'Relationships', 'Science & Technology', 'Sports', 'Travel & Adventure', 'Youth & Student Life'
.["topic_label", "comparator", "threshold"]
{
"topic_analysis": [
"Gaming",
"<=",
0.5
]
}
text_arbitrary
machine_learning
["tag", "comparator", "threshold"]
{
"text_arbitrary": [
"Gaming",
"<=",
0.5
]
}
image_nsfw
machine_learning
NSFW, SFW
["tag", "comparator", "threshold"]
{
"image_nsfw": [
"NSFW",
"<=",
0.5
]
}
image_arbitrary
machine_learning
["tag", "comparator", "threshold"]
{
"image_arbitrary": [
"scotland",
"<=",
0.5,
0.0
]
}
The models
section defines machine learning models used in model_probability
. Each model entry specifies the model name, feature modules, and configuration. Currently, the only model provided is news_without_science
, an XGBoost classifier trained on ≈100 news article skeets and ≈100 science-based skeets. In the guts of this codebase is the ability to train new models, but its very early. Expect (a) lots of ML modules to be made available over time and (b) the ability to easily train and deploy modules yourself via the site.
model_name
: The unique name of the model, referenced in model_probability
.feature_modules
: An array defining the feature extraction modules for the model.type
: The type of feature (e.g., "time_features"
, "post_metadata"
).model_name
: (Optional) Model used for vectorizing, typically with type "vectorizer"
.{
"models": [
{
"feature_modules": [
{
"type": "time_features"
},
{
"model_name": "all-MiniLM-L6-v2",
"type": "vectorizer"
},
{
"type": "post_metadata"
}
],
"model_name": "news_without_science"
}
]
}
In the LogicEvaluator
class, comparisons between values are handled by the compare
method, which supports several common operators. Each operator is used to compare a given value
to a specified threshold
. Here's a breakdown of how each comparator works:
1. Equality (==
)
value
is equal to threshold
.value == 10
and threshold == 10
, the result is True
.value == threshold
2. Inequality (!=
)
value
is not equal to threshold
.value == 10
and threshold == 10
, the result is False
.value != threshold
2. Greater Than or Equal (>=
)
value
is greater than or equal to threshold
.value
meets or exceeds a minimum requirement.value == 10
and threshold == 5
, the result is True
. If value == 5
and threshold == 5
, the result is also True
.value >= threshold
3. Less Than or Equal (<=
)
value
is less than or equal to threshold
.value
does not exceed a maximum limit.value == 3
and threshold == 5
, the result is True
. If value == 5
and threshold == 5
, the result is also True
.value <= threshold
4. Greater Than (>
)
value
is strictly greater than threshold
.value
must be higher than threshold
.value == 10
and threshold == 5
, the result is True
. If value == 5
and threshold == 5
, the result is False
.value > threshold
5. Less Than (<
)
value
is strictly less than threshold
.value
must be lower than threshold
.value == 3
and threshold == 5
, the result is True
. If value == 5
and threshold == 5
, the result is False
.value < threshold
6. Inclusion (in)
value
is contained within threshold
.value
present in threshold
.value == 3
and threshold == [3,4]
, the result is True
.value in threshold
6. Exclusion (not_in)
value
is contained within threshold
.value
present in threshold
.value == 3
and threshold == [2,4]
, the result is True
.value not in threshold