Score photos once, weigh them per storefront
A marketplace asks sellers for a good main photo. "Is this a good photo?" hides what makes it good, and different storefronts care about different things. Valen scores each photo once on three dimensions; code combines them with weights each storefront can change without another model call.
| Studio | Kitchen counter | Dim | Cropped |
|---|---|---|---|
![]() | ![]() | ![]() | ![]() |
How it works
Three score questions, each with three levels described as situations a person could recognize:
| Dimension | Levels |
|---|---|
lighting | poor · fair · good |
background | cluttered · plain · studio |
framing | cut off · loose · centered |
A score runs from 0 to 2 over three levels; code divides by 2 so each dimension is on 0–1, then takes a weighted sum per storefront:
| Profile | lighting | background | framing |
|---|---|---|---|
marketplace | 0.3 | 0.2 | 0.5 |
premium_shop | 0.3 | 0.5 | 0.2 |
Run it
Download the bundle once. The recorded answers in caches/composite_scoring.json let the script run without an API key; --replay off calls the free hosted API instead.
curl -fLO https://valen-team.github.io/cookbook/valen-cookbook.zip
python -m zipfile -e valen-cookbook.zip .
cd valen-cookbook && python -m pip install -r requirements.txt
python composite_scoring.py
python composite_scoring.py --replay off --base-url https://api.valen-team.workers.dev/v1
Code
"""Score listing photos once per dimension, then let code weigh the dimensions for each storefront. Apache-2.0."""
import json
from common import HERE, client, emit, image_bytes, image_input, parser, request
DIMENSIONS = {
"lighting": ("How well lit is the mug?", [
("poor", "Dark, heavily shadowed or color-cast; the glaze is hard to see"),
("fair", "Ordinary indoor light; the glaze is visible but flat"),
("good", "Bright, even light that shows the glaze clearly")]),
"background": ("How clean is the background behind the mug?", [
("cluttered", "Other household objects compete with the mug"),
("plain", "A table or cloth with nothing else in view"),
("studio", "A seamless studio backdrop")]),
"framing": ("How much of the mug is in the frame?", [
("cut_off", "Part of the mug, such as the handle, is outside the frame"),
("loose", "The whole mug is visible but small or off-center"),
("centered", "The whole mug is centered and fills the frame")]),
}
# Weights are business choices in code; changing them never needs another model call.
PROFILES = {"marketplace": {"lighting": 0.3, "background": 0.2, "framing": 0.5},
"premium_shop": {"lighting": 0.3, "background": 0.5, "framing": 0.2}}
def score(predict, photo):
questions = [{"name": name, "type": "score", "instructions": text,
"levels": [{"label": label, "description": description} for label, description in levels]}
for name, (text, levels) in DIMENSIONS.items()]
answers = request(predict, image_input(image_bytes(HERE / "media" / photo)), questions)
# Scores run 0-2 over three levels; divide by 2 so every dimension is on 0-1.
return {"photo": photo, **{name: answers[name]["score"] / 2 for name in DIMENSIONS}}
def rank(scores, weights):
total = lambda s: round(sum(weights[name] * s[name] for name in DIMENSIONS), 3)
return sorted(({"photo": s["photo"], "score": total(s)} for s in scores), key=lambda x: x["score"], reverse=True)
def main():
args = parser(__doc__).parse_args()
photos = json.loads((HERE / "composite_scoring_data.json").read_text())
with client(args) as predict:
scores = [score(predict, photo) for photo in photos]
emit({"dimensions": scores, "rankings": {name: rank(scores, w) for name, w in PROFILES.items()}}, args)
if __name__ == "__main__":
main()
Output
The kitchen-counter photo is well lit and fully framed, so the marketplace ranks it second; the premium shop weighs the clutter and ranks it last.
{
"dimensions": [
{
"photo": "mug-studio.jpg",
"lighting": 0.9991636368077078,
"background": 0.9938775610787292,
"framing": 0.9752801720575722
},
{
"photo": "mug-counter.jpg",
"lighting": 0.9860608196005157,
"background": 0.006156532209375251,
"framing": 0.9074895660257324
},
{
"photo": "mug-dim.jpg",
"lighting": 0.17881670806358135,
"background": 0.641231147103601,
"framing": 0.8176063590643421
},
{
"photo": "mug-cropped.jpg",
"lighting": 0.9932250863251958,
"background": 0.5433575647609814,
"framing": 0.29707197970072985
}
],
"rankings": {
"marketplace": [
{
"photo": "mug-studio.jpg",
"score": 0.986
},
{
"photo": "mug-counter.jpg",
"score": 0.751
},
{
"photo": "mug-dim.jpg",
"score": 0.591
},
{
"photo": "mug-cropped.jpg",
"score": 0.555
}
],
"premium_shop": [
{
"photo": "mug-studio.jpg",
"score": 0.992
},
{
"photo": "mug-cropped.jpg",
"score": 0.629
},
{
"photo": "mug-dim.jpg",
"score": 0.538
},
{
"photo": "mug-counter.jpg",
"score": 0.48
}
]
}
}
Make it yours
- Store the per-dimension scores, not only the total. New weights, filters or a learned model can use them later without calling Valen again.
- The photos are generated for this recipe.



