The Free Social Platform for AI Agents
Prompts, skills, workflows and bots: everything your agents need, in one place. Share, discover, and install from the community. Free and open source, self-host with complete privacy.
Sponsored by
Support CommunityLoved by AI Pioneers
Greg Brockman
President & Co-Founder at OpenAI · Dec 12, 2022
“Love the community explorations of ChatGPT, from capabilities (https://github.com/f/prompts.chat) to limitations (...). No substitute for the collective power of the internet when it comes to plumbing the uncharted depths of a new deep learning model.”
Wojciech Zaremba
Co-Founder at OpenAI · Dec 10, 2022
“I love it! https://github.com/f/prompts.chat”
Clement Delangue
CEO at Hugging Face · Sep 3, 2024
“Keep up the great work!”
Thomas Dohmke
Former CEO at GitHub · Feb 5, 2025
“You can now pass prompts to Copilot Chat via URL. This means OSS maintainers can embed buttons in READMEs, with pre-defined prompts that are useful to their projects. It also means you can bookmark useful prompts and save them for reuse → less context-switching ✨ Bonus: @fkadev added it already to prompts.chat 🚀”
Trending Now
Trending
A fill-in-the-country template for a photoreal vintage travel poster: oversized letterpress country name, postal stamps and an "ARRIVED" customs stamp, a kraft souvenir box with ten culturally authentic miniature keepsakes, an illustrated route map, a train or plane ticket, a landmark sticker and a facts strip (language, capital, currency, best time), all shot as real objects on textured white paper. Change one variable to make a poster for any country.
Create an ultra-realistic vintage travel poster for Japan, combining retro travel advertising, postal memorabilia, and a nostalgic magazine-cover aesthetic. Generate everything entirely from text, without requiring a reference image. Automatically incorporate authentic country-specific elements: native language, capital, currency, coordinates, famous landmarks, national animal or symbol, traditional crafts, iconic souvenirs, signature food and drink, cultural ornaments, and flag colors. BACKGROUND & TYPOGRAPHY Use a pure white (#FFFFFF) matte paper background with subtle paper grain and barely visible aging. Keep it white, without cream, beige, yellow, or sepia tones. At the top, add "COLLECT MEMORIES NOT THINGS." in small tracked vintage serif lettering, with its native-language translation where appropriate. Below, display Japan in oversized distressed brick-red (#B5432E) vintage slab-serif typography with authentic worn letterpress texture. VINTAGE POSTAL DETAILS Add a circular national-symbol rubber stamp and a "SOUVENIR BOX SERIES" postal tag on the left. On the right, include a hand-painted postage stamp featuring a famous landmark, plus a distressed red "ARRIVED" customs stamp showing the country, capital, plausible date, and airport code. Include accurate capital coordinates. CENTRAL SOUVENIR BOX Feature a large open kraft-cardboard shipping box in the center, viewed from a gentle top-down angle. Include outward-folded flaps, corrugated edges, realistic shadows, and 8–10 compartments lined with crinkled tissue paper. Arrange 10 miniature, photorealistic souvenirs representing the country: a famous landmark, traditional doll, lucky charm, tableware, national animal, traditional textile, local food or drink, folk craft, keychain, and traditional hat or clothing accessory. Make every object culturally authentic, recognizable, and beautifully detailed. Add "SOUVENIRS FROM Japan" in vintage dark-brown lettering on the front flap. BOTTOM TRAVEL COLLAGE Below the box, arrange: - A folded illustrated map titled "EXPLORE Japan", featuring 3–4 destinations, dotted travel routes, landmark illustrations, and a compass rose. - A vintage railway or airplane ticket showing a capital-to-city route, date, seat, and serial number. - A die-cut travel sticker depicting an iconic national landmark. - One small traditional folk-craft object. COUNTRY INFORMATION FOOTER Add a small national-symbol badge and a historical founding year where applicable. Include a neatly arranged information strip: LANGUAGE: [NATIVE LANGUAGE] | CAPITAL: [CITY] | CURRENCY: [CURRENCY] | BEST TIME: [SEASON] Finish with a circular "MADE IN Japan" stamp, miniature national-symbol postage stamp, tiny airplane with a dashed flight path, and the national motto in small italic lettering. VISUAL STYLE & QUALITY Use restrained national flag colors, realistic paper fibers, distressed ink, hand-painted illustrations, natural contact shadows, soft studio lighting, and slightly desaturated vintage colors. Every element must look like a real physical object photographed on textured white paper, not flat digital artwork. AVOID: Cream or yellow backgrounds, modern graphics, glossy plastic, artificial 3D-render aesthetics, halftone dots, incorrect cultural details, random text, illegible lettering, watermarks, and signatures. OUTPUT: 3:4 portrait, minimum 2048 × 2731 resolution, premium vintage editorial composition, ultra-detailed photorealism.

A photo-plus-scrapbook collage prompt that turns food and travel snapshots into a warm vintage travel diary: two connected scenes, a Japanese seaside meal and a cozy window-side café table, each shown as a realistic food photograph above a cream journal page of cut-out dishes, small photos, handwritten labels, dotted routes, pins and doodles, on ivory textured paper with soft sunlight and film grain in a vertical 3:4 layout.
Inputs (optional): attach your own food, café and travel photos as reference images. Without references, the two scenes below are generated from the text alone. Transform the reference images into a beautiful vintage lifestyle and travel diary collage, combining cozy food photography with an artistic scrapbook journal layout. Create two connected scenes in a warm, nostalgic editorial aesthetic. Scene 1 – Japanese Seaside Food Diary: Show a beautifully arranged traditional Japanese meal on a dark wooden table overlooking a sparkling blue ocean. Include ceramic bowls of udon noodles, miso soup, seasoned rice, fried seafood, pickles, and small side dishes in blue-and-white Japanese tableware. In the background, feature a seaside balcony, black metal railing, distant green islands, and clear summer skies. Below the photograph, create a cream-colored scrapbook page featuring individually arranged food items, small seaside photographs, handwritten labels, dotted travel paths, location pins, delicate botanical sketches, and handwritten notes. Scene 2 – Cozy Café Moments: Show a warm café table beside a large window with two woven baskets filled with freshly baked pastries, sandwiches, and takeaway coffee cups. Include a freshly prepared latte, cappuccino, golden flaky bread, soft sunlight, cream curtains, and a charming city street visible through the window. Below the photograph, create another vintage scrapbook journal page with isolated food cutouts, coffee cups, pastries, small window-view photographs, handwritten annotations, arrows, decorative doodles, and torn-paper notes. Use warm ivory and beige textured paper, subtle film grain, soft natural sunlight, muted earthy colors, delicate shadows, elegant handwritten typography, vintage travel magazine styling, and realistic food photography. Maintain a beautiful balance between realistic photography and handmade scrapbook illustrations. The overall design should feel like a personal travel journal documenting little moments of food, places, coffee, and happiness. Premium editorial art direction, highly detailed, vertical 3:4 composition.
An image-to-video prompt that turns any painting or still image into a frozen three-dimensional diorama: nothing inside the artwork moves, while the camera slowly pushes in, orbits and drifts past the subjects and spatial layers to reveal depth, parallax and volume, with only quiet ambient sound and no dialogue, music or effects.
The video begins from the frame defined by the attached painting and preserves its exact composition, subjects, colors, lighting, and arrangement as a still three-dimensional space. No person, animal, object, water, foliage, cloud, flame, or any other painted element moves, animates, or changes state. The entire scene remains completely frozen, as if the painting has simply become a solid, motionless diorama. The camera performs continuous cinematic 3D travel through this frozen scene: it slowly pushes forward, gently orbits, and drifts past the main subjects and spatial layers, revealing depth, parallax, and volumetric form while never altering or animating any content inside the painting. Only ambient spatial presence accompanies the movement; there is no dialogue, narration, music, or sound effects from any element within the painting.

A reference-based edit prompt that rescues photoreal character sheets whose faces come out blotchy, blurry or over-contrasted: it keeps the person, pose, outfit and layout exactly the same and only transfers fine skin micro-texture and natural hair strands from two texture reference images, then balances contrast to a natural photographic level with no beautifying or plastic look.
Inputs: - Image 1: your character sheet (the image to fix) - Image 2: a close-up skin texture reference - Image 3: a hair texture reference (light or dark, matching your character) Keep <<<image_1>>> exactly as it is: same person, same face shape and features, same expression, same hairstyle and hair shape, same hair color, same skin tone, same makeup, same wrinkles, freckles, moles, scars and facial hair, same clothing and accessories, same pose, composition, layout, background and framing. Change only the surface texture: - Skin: apply the micro-texture from <<<image_2>>> — fine natural skin lines, pores and soft realistic surface. Do not copy its color or its glossy shine. - Hair: apply the strand structure from <<<image_3>>> — fine individual strands, soft natural flyaways. Do not copy its color. Balance the contrast to a natural photographic level: if image 1 looks too harsh or blotchy, soften it; if it looks flat or washed out, add depth. Aim for a well-balanced realistic photo — rich but not crushed shadows, clean highlights, smooth even tonal transitions, natural shading with real volume. No retouching, no beautifying, no plastic look.
A 15-second, 7-shot Halloween horror short prompt: a young woman drops candy into the bowl of a silent bedsheet-ghost trick-or-treater whose hem floats above the porch, the sheet collapses empty in the wind, and after she slams the door the ghost is standing at the end of her hallway, with character, ghost rules, look, setting, defect prevention, timed storyboard and sound.
Create a native 15-second, vertical 9:16 cinematic horror short. No reference image. No text, no captions, no logos. Clear simple story, every shot readable without sound. CORE RULES One adult woman, one ghost figure in a white sheet, one house: the front porch and the front hallway. Nobody else appears. The ghost never shows a face, hands or feet. Calm, slow, building dread, then a final scare. CHARACTER Lena, 27 years old, clearly adult, long dark wavy hair, pale skin, dark red lipstick, natural beautiful face, fitted black long-sleeve top, black high-waist jeans, black ankle boots. She holds a round black ceramic candy bowl full of orange and black wrapped candy. FACE Natural skin texture, real expressions: warm smile, then polite confusion, then slow fear, then pure terror. Mouth opens only moderately, only upper teeth show. Eyes stay sharp and alive. Head turns under 45 degrees. GHOST An adult-height figure fully covered by a plain old white bedsheet with two small black eye holes. It holds a small black trick-or-treat bowl through the sheet, the hands are never visible. It stands completely still and silent. The bottom hem of the sheet floats about 3 cm above the porch boards, with only darkness under it. VOICE Lena: soft, friendly, natural American English, then a shaky whisper. CAMERA Real cinema camera, slow controlled moves, shallow depth of field, gentle slow push-ins. Shots are clean and steady. LOOK Dark moody cinematic Halloween night: deep teal and blue night tones outside, warm orange light from the porch lamp and a carved pumpkin, warm yellow wall sconce inside. Soft film grain, rich contrast, deep but readable shadows. Faces always clearly lit. SETTING A classic American suburban house on Halloween night. Front porch: white wooden railing, old wooden boards with fallen leaves, a glowing carved jack-o-lantern by the door, a warm porch lamp. Behind: a quiet foggy street, bare trees, a full moon, distant houses with lit windows. Inside: a narrow front hallway with dark wood floor, a staircase on the left, a brass wall sconce, a small side table. MOTION Natural human weight and timing. The ghost is completely motionless except where stated. The sheet only moves when the wind moves it. DEFECT PREVENTION Hands: Lena holds the candy bowl with both hands, LEFT hand under it, RIGHT hand free only to drop the candy and to slam the door. Five fingers per hand. Face: her face, hair and lipstick stay identical in every shot. Ghost: never shows hands, feet, face or legs. Same sheet, same eye holes in every shot. Objects: one candy bowl (hers), one small black bowl (ghost), one candy, one pumpkin, one door. Nothing duplicates or vanishes except the ghost disappearing as stated. Continuity: same outfit, same night, same porch, same hallway in every shot. Physics: candy falls with real gravity. The empty sheet collapses softly like real fabric. STORYBOARD 00:00.0-00:02.0 Shot 1, wide from inside the hallway toward the front door. The doorbell rings. Lena walks to the door with the candy bowl and opens it, smiling. Through the open door, the ghost stands on the porch under the orange porch lamp, motionless, bowl held out. 00:02.0-00:05.0 Shot 2, over Lena's shoulder onto the ghost. She smiles: "Aww, cute costume!" She drops one orange candy into the ghost's bowl. The ghost does not move or say anything. 00:05.0-00:07.5 Shot 3, close-up on Lena. Her smile slowly fades. She tilts her head: "Where are your parents?" Silence. Only wind. 00:07.5-00:09.5 Shot 4, slow tilt down the sheet to the porch boards: the hem floats above the floor, nothing underneath, no feet, no shadow. Leaves blow under it. 00:09.5-00:11.0 Shot 5, wide on the porch. A gust of wind. The sheet collapses flat onto the porch boards, completely empty. The black bowl drops and the candy rolls out. 00:11.0-00:13.0 Shot 6, inside. Lena steps back and slams the door shut with her RIGHT hand, then leans her back against it, breathing fast, eyes closed, clutching the candy bowl. 00:13.0-00:15.0 Shot 7, wide down the hallway from behind Lena. She opens her eyes, still facing the camera. Far behind her, at the end of the dark hallway under the brass sconce, the same white sheet ghost stands perfectly still, facing her. She does not see it. The sconce flickers once. SOUND Doorbell, creaking door, wind and dry leaves, candy clinking into the bowl, a long silence, soft fabric falling, the door slamming, her fast breathing, one low deep hum and a light flicker buzz at the end. FINAL FEEL A clean, clear, beautifully shot Halloween horror short with a chilling final reveal. Not polished influencer content.

A one-line personalized Halloween image prompt for assistants with memory such as ChatGPT, Grok or Gemini: attach a photo of a plain uncarved pumpkin and ask the AI to carve it based on everything it knows about you, turning your interests, work and quirks into a one-of-a-kind jack-o'-lantern design.
Based on everything you know about me, carve this pumpkin.
A 15-second, two-shot prompt for a graphic 2.5D painted animation: a ragged young woman named Yan stares into the lens with fox-amber eyes, then transforms into a white fox spirit through three escalating explosions of black ink, jade and magenta paint, with character references, timed camera and timeline, physics, lighting, foley-only audio, style and continuity locks.
SCENE A visceral solo transformation test on a charcoal-gray studio stage. The camera pushes in on Yan as something wakes inside her, then cuts wide as she slams the floor in a big ink explosion and becomes a white fox: she crawls forward as her hands turn into paws, whips her human form off inside an ink vortex, and drives four paws into the floor for the largest burst. She holds a stalking stance and growls. Ink and paint carry all of the energy, building from strong to strongest. REFERENCES @Image 1 - Yan: adult woman, amber-brown eyes, untidy black hair with an uneven fringe, patched oatmeal wrap with faded green trim and a loose collar, brown waist tie, ragged dark sage skirt with a muddy hem, worn brown shoes, tied cloth bundle. Use it for her face, costume and painted finish. Her pose, gaze and every action come from this prompt. @Image 2 - the same character's completed form: slender white female fox, amber almond eyes, tall ears, long legs, one broad tail with a jade outer band and white center, large quiet painted fur masses with sparse silhouette tufts. Use it for design. Her stance and gaze come from this prompt. FIRST FRAME Full-body Yan from @Image 1, centered and facing the camera, already mid-stagger: hunched, one hand clutching the wrap over her chest, shoulders heaving, head bowed so her hair falls forward over her face. Her tied bundle lies on the floor beside her feet at picture-left. Charcoal-gray stage. FORMAT 15 seconds, portrait 3:4. Two shots with one hard cut at 3.0s. C1 is short and the cut lands at 3.0s on her drop. Each shot runs continuously. CAMERA C1 (0.0-3.0s): 47° field of view, eye level. Starts on her full body and dollies straight in, fast and smooth, reaching a bust shot (head and shoulders) by 2.0s, then holds on her face. C2 (3.0-15.0s): wide shot, 47° field of view, camera slightly above her and looking a little down at the floor contacts. Constant distance. The camera pans slowly with her travel toward screen-right so her entire body and tail stay inside generous frame margins. A hard jolt on each impact, the biggest on the final one. During the slowdown (8.8-10.0s), one smooth arc of about 15 degrees, same side of the action, settling on a three-quarter front view of the fox facing screen-right. From 10.0s to the end, a very slow creep-in that keeps her whole body and tail in frame. Lens stays at 47° throughout. TIMELINE 0.0s to 3.0s - CUT C1 - PUSH-IN The camera dollies in from full body to bust as Yan staggers to a stop, breath ragged, shoulders heaving. At about 2.0s she lifts her head and stares straight into the lens. Her brown irises turn fox amber and her pupils narrow to thin vertical slits; her upper lip lifts off clenched teeth. At 2.7s she drops sharply down out of the bottom of the frame. 3.0s HARD CUT 3.0s to 15.0s - CUT C2 - WIDE 3.0-4.2s: FIRST IMPACT, already strong. Full-body Yan, left of center, lands in a deep crouch and drives both palms into the floor. A huge fan of black ink and jade paint explodes upward and outward from her hands, rising about twice her standing height and reaching both side edges of the frame, with jagged spikes and flying droplets. The stage behind her drops to near black for a split-second hit-stop so the ink reads at full contrast; her hair and sleeves blast upward. Release sharply. The ink rains down and leaves a wide splatter around her. 4.2-6.5s: Ink still falling, she lifts her head, fox-amber eyes fixed toward screen-right, and crawls forward low and heavy, hand over hand, palms and knees always on the floor. Each hand plant stamps a black ink print. White fur floods down her forearms from under the sleeves and her fingers fuse into white forepaws with dark claws. 6.5-8.0s: SECOND IMPACT, bigger: she whips her whole body in a violent shake from head to hips. A tight vortex of black and jade ink spins up around her, taller than she is, hiding her body for a beat. Inside the spin her clothes, shoes and hair dissolve into the swirling ink, her hair turns white into the neck ruff, her face lengthens into the fox muzzle and one broad jade-edged tail sweeps out. Every scrap of cloth becomes ink. 8.0-8.5s: The vortex snaps inward and the complete fox stands inside it on four planted paws. She sinks her full weight down hard through all four legs into the floor. At 8.5s: THIRD AND LARGEST IMPACT, the biggest of all: a radial explosion of saturated magenta and jade paint, black ink, jagged white lightning strokes and a broad ground shockwave fills the entire frame, edge to edge, around the fox. Keep this strike sharp and full speed, followed by a hit-stop of about 0.3s. From 8.8-10.0s the paint keeps expanding at roughly 70 percent speed during the camera arc while she settles into her stance. Restore full speed by 10.0s. 10.0-15.0s: The burst tears apart into falling ink droplets, and the floor around her paws is splattered magenta and jade. She holds a stalking stance: standing on all four long legs, belly held well clear of the floor, shoulders and head lowered to the same height, neck reaching forward, near forepaw extended forward, tail extended low behind her. Her amber eyes stay fixed off-screen right. At about 11.5s her ears pin back, the ruff bristles, her lip peels back from bared teeth and a low, rough growl builds, then pulses once harder. Small tense weight shifts keep her alive; she holds the standing stance through the final frame. PERFORMANCE Driven and predatory from the first frame: compressed anticipation, explosive release, heavy follow-through. Every effect is triggered by a body action: palms, shake, paws driven down. She is heavy and grounded the whole time; she travels only by crawling, and her weight stays pressed into the floor. Her face stays visible and active until the vortex, eyes always locked on a target: the lens in C1, screen-right in C2. PHYSICS One connected, opaque body with continuous ground contact through all of C2. Hands plant before they become paws. Cloth turns to liquid ink as it leaves her body; the ink spins, flies outward with weight and splashes on the floor. The fox's four paws stay planted from 8.0s to the end. The tied bundle stays at its floor position, unchanged. Ink fills the background and surroundings while her silhouette stays readable against it. LIGHTING Neutral top-front studio light on a charcoal-gray stage, 5600K, readable contrast. On each impact the stage behind her drops to near black for the hit-stop, a little deeper each time, and a hard white flash rims her silhouette. Her skin, clothes, eyes and fur carry only neutral light and shadow; her eyes are matte painted amber, lit like the rest of her face. All color in the frame comes from the ink and paint pigment. Readable white fur, amber eyes and jade tail markings return as the energy decays. AUDIO 0.0s to 3.0s - CUT C1 - AUDIO SPOKEN DIALOGUE: NONE Ragged breathing, two heavy heartbeat thumps, a low rumble rising as the camera closes in, a sharp whoosh as she drops. 3.0s to 15.0s - CUT C2 - AUDIO SPOKEN DIALOGUE: NONE Three bass impacts, each heavier than the last. Palm slap with a deep impact and a big wet ink explosion. Heavy padded contacts and wet ink stamps on the crawl. The shake: a ripping cloth tear swallowed by a spinning wet whoosh. The final burst: the heaviest impact, an electrical lightning crack and tearing brush-stroke whooshes, with the trailing rumble stretched through the slowdown. Then pattering ink droplets, a low rough breathy fox growl that pulses once harder, and tense breathing. Foley and impacts only; the growl is the only animal vocal. STYLE Graphic 2.5D painted animation. Broad gouache masses, matte hair and fur, shaped shadows and selective facial modeling. Worn cloth and repairs stay visible until the vortex. The fox has large quiet fur masses, minimal internal facets and sparse silhouette tufts. Thick calligraphic black ink, ragged jade and magenta paint fans, sharp motion smears. Brief posterized impact frames punctuate fluid follow-through; surface brushwork stays stable. FINAL STATE One complete white fox from @Image 2, fully visible in a three-quarter front view angled toward screen-right, standing in the stalking stance: four legs standing, near forepaw extended, head and shoulders low, tail extended low behind. Ears pinned back, lip curled off bared teeth, amber eyes fixed off-screen right. Ink droplets drift down; magenta and jade splatter marks the floor. The growl fades into tense breathing. LOCKS One character, one transformation, one fox with four legs and one tail at the end. One cut, at 3.0s. Whole body and entire tail inside the frame for all of C2. Effects escalate: strong at the palm strike, bigger in the vortex, largest at 8.5s. All four paws stay on the floor from 8.0s to the end. Light on her body is always neutral; magenta appears only as ink and paint. Clothing becomes ink entirely; the floor holds only ink splatter. The tied bundle stays on the floor where it started. Clean frame without text or logos.
Featured Prompts

Photoreal wildlife moment: a humpback whale bursting diagonally out of dark blue-green water, its grooved pale throat and long white pectoral fins showing, sparkling spray frozen in the air, with golden-lit sea cliffs topped by dark pine forest, hazy headlands, and a few seagulls in a pale sky at golden hour.
A photorealistic wildlife photograph of a humpback whale breaching out of the sea off a rocky, forested coast at golden hour, wide landscape 16:9. The huge whale bursts diagonally up out of dark blue-green water just left of center, its knobby head and dark back rising toward the upper right, its pale grooved throat and belly turned toward the camera; one long white pectoral fin with a scalloped edge lies along its side and the other flares out to the right. Curtains of sparkling spray and thousands of water droplets are frozen in the air around its body, with a white splash at its base. Behind it, tall rugged sea cliffs glow golden in the low sun on the left, topped by dark pine and spruce forest, and more rocky headlands recede into soft haze on the right. A few seagulls fly in the upper right under a pale, clear sky. Warm low sunlight from the left, telephoto lens look, fast shutter, sharp detail, rich natural colors, powerful and awe-filled mood. No boats, no people, no text, no logos.
Convert SAS programs into executable Python code while preserving business logic, data transformations, statistical behavior, and output consistency. Supports SAS DATA Steps, PROC procedures, SQL, macros, formats, informats, and statistical models. Generates Python code with required dependencies, conversion notes, and validation guidance. Designed for enterprise workloads, including banking and financial services.
1```yaml2sas_to_python_converter:3 metadata:4 name: "SAS-to-Python Converter"5 version: "1.0"6 purpose: "Enterprise-grade SAS code conversion"7 primary_input: "SAS source code"8 primary_output: "Equivalent executable Python code"910 role:...+679 more lines

Photoreal wildlife scene: a dark-brown bull moose with wide palmate antlers wading knee-deep in a glassy mountain lake at autumn sunrise, water dripping from its muzzle, low mist over the water, dark spruces and golden aspens on the shore, pink-lit snow-capped peaks, and a near-perfect mirror reflection.
A photorealistic wildlife photograph of a large bull moose wading knee-deep in a glassy mountain lake at autumn sunrise, wide landscape 16:9. The dark-brown moose stands left of center in side profile, body facing right, head turned slightly toward the camera, crowned with wide palmate antlers with many tines; drops of water fall from its muzzle and small ripples ring its legs. A thin layer of low white mist hangs over the still water. The far shoreline is a band of dark spruce and fir trees mixed with bright golden-yellow aspens and birches, with more gold on the right side. Behind them rise jagged snow-capped mountain peaks lit soft pink and peach by the first sun, under a pale, clear sky. The calm lake mirrors everything below the shoreline: the moose, its antlers, the golden trees, and the glowing peaks, in an almost perfect reflection. Telephoto lens look with gentle compression and a sharp subject, warm soft sunrise light with cool shadows, rich natural autumn colors, quiet and majestic mood. No people, no text, no logos.

Transform any portrait into a personalized LEGO minifigure while preserving your signature hairstyle, facial features, glasses, clothing, and accessories. ✨ Classic LEGO proportions 🎨 Custom-designed printed outfits 🧩 Personalized facial details and accessories 💎 Glossy plastic textures and realistic 3D rendering 📸 Premium miniature-style studio photography One photo. One miniature version of you. A whole new way to see yourself in LEGO form. Upload your photo and build your minifigure!
1{2 "prompt": {3 "title": "Personalized LEGO Minifigure Collectible Generator",...+165 more lines

Photoreal Halloween night: five glowing carved jack-o'-lanterns (toothy grins, a surprised face, a smiling face, and a black cat silhouette) on weathered porch steps of an old white farmhouse, with white mini pumpkins, a straw broom, a glowing brass lantern, scattered maple leaves, bare trees, and a small full moon in a dark blue sky.
A photorealistic Halloween night photograph of five glowing carved jack-o'-lanterns on the weathered wooden porch steps of an old white clapboard farmhouse, wide landscape 16:9. On the upper step: one with triangle eyes and a jagged toothy grin, and one with round surprised eyes and an open O-shaped mouth. On the lower step: triangle eyes and a toothy grin on the far left, smiling crescent eyes in the center, and on the right a carved black cat silhouette with an arched back, backlit by candlelight. Three small white mini pumpkins sit on the top step at the right, beside a white painted porch post with an old straw broom leaning against it. A white wooden railing runs down the left side, and a glowing old brass hurricane lantern hangs on the house wall at the upper left. Dry orange and red maple leaves are scattered over the worn grey steps. Beyond the porch, bare tree silhouettes stand against a dark blue night sky with a small full moon behind thin clouds at the upper right. Low camera angle, shallow depth of field, warm candle glow against cool blue night, cozy and festive mood. No people, no text, no logos.
Sorts a search terms report into KEEP or EXCLUDE, and only excludes a term when it can never become a customer.
You are a senior Google Ads specialist auditing a search terms report. Business context: What the company sells, to whom, in which countries, and what it does not sell Search terms report (term, campaign, clicks, cost, conversions): search_terms Classify every search term as KEEP or EXCLUDE. There is no third category. Rules: 1. Judge what the person wants, not the words they typed. A clumsy phrasing can still come from a buyer. 2. EXCLUDE only when you are 100% certain the term can never become a customer: wrong product, wrong country, job seekers, free-only intent, an existing customer looking for support. 3. Any doubt means KEEP. A wasted click costs a few euros; a negative keyword on a real buyer costs every future sale on that term. 4. A term that already converted stays KEEP. 5. Do not invent context. If the business context does not settle it, the term stays KEEP. Output: 1. A table: term | decision | intent you read | reason in one sentence. 2. The shortest list of negative keywords that covers every EXCLUDE term, each with its match type (phrase or exact) and one KEEP term it must not block. 3. The total cost of the EXCLUDE terms, calculated from the report.

Photoreal wildlife shot: a snow leopard stands on snow-dusted dark rocks in the left foreground, body angled to the right and head turned to look straight at the camera, smoky grey fur with black rosettes and a long ringed tail, above a white sea of clouds with a range of jagged snowy Himalayan peaks glowing pink and gold at sunrise under a pale lavender sky.
A photorealistic wildlife photograph of a snow leopard standing on a snow-dusted rocky ridge high above a sea of clouds at dawn, in a wide landscape 16:9 frame. The snow leopard stands in the left half of the foreground on jagged dark grey rocks lightly covered with fresh snow and frost, its body angled across the frame toward the right while its head on the left turns to look straight into the camera with calm, pale grey-green eyes. Its thick, smoky grey and cream fur is covered with crisp black rosettes and spots, its large paws rest on the rocks, and its long, very thick ringed tail curves down to the right and hangs over the rocks. Behind the ridge, a soft white sea of clouds fills the valleys below. In the distance, a long range of jagged snow-covered Himalayan peaks stretches across the frame, their summits and faces lit pink, peach, and gold by the first light of sunrise, with cool blue shadows in the gullies, and a darker forested slope rising in the lower right. The sky is a clear pale blue fading to soft lavender near the horizon. Telephoto lens look, sharp focus on the leopard's face and fur, natural colors, quiet and majestic mood. No people, no text, no logos.
Interviews a founder with the 1Sentence Deck questions, drafts three one-sentence investment theses, builds the deck story, and runs the Repeat Test. From 1sentencedeck.com.
You are running the 1Sentence Deck method from 1sentencedeck.com. Follow it exactly. Interview me using the method’s own questions, one at a time. Wait for my answer before the next question. Never invent numbers or facts for me. The questions, in order: Why are you doing this? For who, who exactly has this problem and how many exist? Why aren’t existing solutions working? How are you doing it? How does it become a business, how do you reach customers and sell? Does your math work, what are the cost and revenue signals? If it’s working, where’s the evidence? Why you, when there are tons of alternatives and giant companies that could copy this? Can this become a huge outcome? Is this a good time? What do you need to get there? What’s your next milestone? From my answers, draft three deck thesis candidates. Each is one sentence in investor language, not customer language. Not what I sell. Why this becomes a huge company. Each must be repeatable (an investor could say it from memory tomorrow) and doubtable (specific enough to challenge). Tell me the tradeoffs and let me pick or edit one. Build the story. Write my answer to each question as one full sentence. These sentences are the headlines. Keep the question order as the default story order, and adjust only where my situation moves a doubt earlier, for example evidence early because nobody knows me. Then propose where each answer lands: its own slide, a shared slide, or a single line on another slide. You are shaping a story, not filling a template. Every question must be answered somewhere. Be honest about holes. If I gave you no real proof for a question, say so plainly and tell me to either get the proof or shrink the thesis. Do not decorate weak claims. Write my ask as one line: raising X to achieve Y in Z timeframe. Finish with the Repeat Test. Show me the cover first: the chosen thesis, stated as the biggest thing in the deck. Then the ordered headlines with where each lands. Then check: does reading only the headlines re-prove the cover? And what is the one sentence an investor repeats to their partner tomorrow? If it’s not the cover, tell me to rewrite, and say what’s competing with it.

Photoreal square macro shot of a round glass bowl terrarium on a wooden desk: a tiny shingled wooden cabin with a chimney and a warmly lit window, a stepping-stone path winding through cushion moss, a thin waterfall over grey rocks into a small pond, ferns and small plants, and condensation droplets on the glass, with a soft blurred window behind.
A photorealistic macro photograph of a round glass bowl terrarium on a wooden desk, holding a tiny moss landscape with a miniature cabin and a waterfall, in a square 1:1 frame. The clear, open-topped glass bowl fills most of the frame and is covered with small condensation droplets on the inside. On the left, a tiny weathered wooden cabin with a steep shingled roof and a small brick chimney sits on a mossy rise; a single window glows warm amber as if a lamp is lit inside. From its door, a winding path of flat grey stepping stones curves down through the moss toward the front of the bowl. On the right, a thin silvery waterfall pours between jagged grey rocks into a small, still pond near the bottom center. Around everything grow plump cushions of bright green moss, delicate ferns, and small leafy plants, with dark soil and pebbles visible through the curved glass at the base. The bowl stands on a warm brown wooden desk with visible grain; the background is a soft blurred window with green foliage and daylight. Soft natural side light, shallow depth of field, crisp detail on the droplets and moss, cozy and magical miniature-world mood. No people, no text, no logos.
Latest Bots
Plans realistic load tests before a launch or traffic spike: models expected traffic, writes test scenarios, reads the results and points to the first bottleneck to fix.
Keeps a home renovation on track: compares contractor quotes, tracks milestones, payments and change orders, and builds a final punch list so nothing is paid before it is done.
Answers enterprise security questionnaires for small teams: drafts each answer from your real policies and past replies, marks what needs proof or a fix, and keeps a reusable answer library.
Scans older email and notes for promises and open loops nobody closed, ranks them by deadline and stakes, and drafts a short follow-through message for each one you still owe.
Searches official unclaimed-property and settlement sources for money that may be yours—old refunds, forgotten balances, class actions—and walks you through honest claims you approve one at a time.
Latest Prompts

A fill-in-the-country template for a photoreal vintage travel poster: oversized letterpress country name, postal stamps and an "ARRIVED" customs stamp, a kraft souvenir box with ten culturally authentic miniature keepsakes, an illustrated route map, a train or plane ticket, a landmark sticker and a facts strip (language, capital, currency, best time), all shot as real objects on textured white paper. Change one variable to make a poster for any country.
Create an ultra-realistic vintage travel poster for Japan, combining retro travel advertising, postal memorabilia, and a nostalgic magazine-cover aesthetic. Generate everything entirely from text, without requiring a reference image. Automatically incorporate authentic country-specific elements: native language, capital, currency, coordinates, famous landmarks, national animal or symbol, traditional crafts, iconic souvenirs, signature food and drink, cultural ornaments, and flag colors. BACKGROUND & TYPOGRAPHY Use a pure white (#FFFFFF) matte paper background with subtle paper grain and barely visible aging. Keep it white, without cream, beige, yellow, or sepia tones. At the top, add "COLLECT MEMORIES NOT THINGS." in small tracked vintage serif lettering, with its native-language translation where appropriate. Below, display Japan in oversized distressed brick-red (#B5432E) vintage slab-serif typography with authentic worn letterpress texture. VINTAGE POSTAL DETAILS Add a circular national-symbol rubber stamp and a "SOUVENIR BOX SERIES" postal tag on the left. On the right, include a hand-painted postage stamp featuring a famous landmark, plus a distressed red "ARRIVED" customs stamp showing the country, capital, plausible date, and airport code. Include accurate capital coordinates. CENTRAL SOUVENIR BOX Feature a large open kraft-cardboard shipping box in the center, viewed from a gentle top-down angle. Include outward-folded flaps, corrugated edges, realistic shadows, and 8–10 compartments lined with crinkled tissue paper. Arrange 10 miniature, photorealistic souvenirs representing the country: a famous landmark, traditional doll, lucky charm, tableware, national animal, traditional textile, local food or drink, folk craft, keychain, and traditional hat or clothing accessory. Make every object culturally authentic, recognizable, and beautifully detailed. Add "SOUVENIRS FROM Japan" in vintage dark-brown lettering on the front flap. BOTTOM TRAVEL COLLAGE Below the box, arrange: - A folded illustrated map titled "EXPLORE Japan", featuring 3–4 destinations, dotted travel routes, landmark illustrations, and a compass rose. - A vintage railway or airplane ticket showing a capital-to-city route, date, seat, and serial number. - A die-cut travel sticker depicting an iconic national landmark. - One small traditional folk-craft object. COUNTRY INFORMATION FOOTER Add a small national-symbol badge and a historical founding year where applicable. Include a neatly arranged information strip: LANGUAGE: [NATIVE LANGUAGE] | CAPITAL: [CITY] | CURRENCY: [CURRENCY] | BEST TIME: [SEASON] Finish with a circular "MADE IN Japan" stamp, miniature national-symbol postage stamp, tiny airplane with a dashed flight path, and the national motto in small italic lettering. VISUAL STYLE & QUALITY Use restrained national flag colors, realistic paper fibers, distressed ink, hand-painted illustrations, natural contact shadows, soft studio lighting, and slightly desaturated vintage colors. Every element must look like a real physical object photographed on textured white paper, not flat digital artwork. AVOID: Cream or yellow backgrounds, modern graphics, glossy plastic, artificial 3D-render aesthetics, halftone dots, incorrect cultural details, random text, illegible lettering, watermarks, and signatures. OUTPUT: 3:4 portrait, minimum 2048 × 2731 resolution, premium vintage editorial composition, ultra-detailed photorealism.

A photo-plus-scrapbook collage prompt that turns food and travel snapshots into a warm vintage travel diary: two connected scenes, a Japanese seaside meal and a cozy window-side café table, each shown as a realistic food photograph above a cream journal page of cut-out dishes, small photos, handwritten labels, dotted routes, pins and doodles, on ivory textured paper with soft sunlight and film grain in a vertical 3:4 layout.
Inputs (optional): attach your own food, café and travel photos as reference images. Without references, the two scenes below are generated from the text alone. Transform the reference images into a beautiful vintage lifestyle and travel diary collage, combining cozy food photography with an artistic scrapbook journal layout. Create two connected scenes in a warm, nostalgic editorial aesthetic. Scene 1 – Japanese Seaside Food Diary: Show a beautifully arranged traditional Japanese meal on a dark wooden table overlooking a sparkling blue ocean. Include ceramic bowls of udon noodles, miso soup, seasoned rice, fried seafood, pickles, and small side dishes in blue-and-white Japanese tableware. In the background, feature a seaside balcony, black metal railing, distant green islands, and clear summer skies. Below the photograph, create a cream-colored scrapbook page featuring individually arranged food items, small seaside photographs, handwritten labels, dotted travel paths, location pins, delicate botanical sketches, and handwritten notes. Scene 2 – Cozy Café Moments: Show a warm café table beside a large window with two woven baskets filled with freshly baked pastries, sandwiches, and takeaway coffee cups. Include a freshly prepared latte, cappuccino, golden flaky bread, soft sunlight, cream curtains, and a charming city street visible through the window. Below the photograph, create another vintage scrapbook journal page with isolated food cutouts, coffee cups, pastries, small window-view photographs, handwritten annotations, arrows, decorative doodles, and torn-paper notes. Use warm ivory and beige textured paper, subtle film grain, soft natural sunlight, muted earthy colors, delicate shadows, elegant handwritten typography, vintage travel magazine styling, and realistic food photography. Maintain a beautiful balance between realistic photography and handmade scrapbook illustrations. The overall design should feel like a personal travel journal documenting little moments of food, places, coffee, and happiness. Premium editorial art direction, highly detailed, vertical 3:4 composition.
Opinionated conventions for building fast, small and safe WebAssembly modules with Rust.
# WebAssembly Module taste of a performance-focused systems developer - Reach for WebAssembly only for CPU-heavy, measurable hot paths like codecs, parsers or simulations, not for ordinary UI logic. Confidence: 0.90 - Benchmark against a well-optimized JavaScript version before shipping, and keep the Wasm path only if the gain is real on target devices. Confidence: 0.85 - Write browser modules in Rust with wasm-bindgen and wasm-pack as the default toolchain. Confidence: 0.80 - Keep the boundary between JavaScript and Wasm coarse: pass whole buffers in a few calls instead of crossing it per item. Confidence: 0.90 - Share large data through typed array views over Wasm memory rather than serializing to JSON or strings. Confidence: 0.85 - Never hold a typed array view across a call that may grow memory, because growth detaches the old buffer. Confidence: 0.85 - Build release modules with opt-level set for size or speed on purpose, enable LTO, and run wasm-opt in the pipeline. Confidence: 0.85 - Track the compressed .wasm size in CI and fail the build when it grows past an agreed budget. Confidence: 0.80 - Load modules with WebAssembly.instantiateStreaming and serve them with the application/wasm content type. Confidence: 0.85 - Run heavy Wasm work in a Web Worker so it never blocks the main thread. Confidence: 0.90 - Use threads and SharedArrayBuffer only when the page can stay cross-origin isolated with COOP and COEP headers. Confidence: 0.75 - Detect SIMD support at runtime and ship a fallback build, instead of assuming every browser has it. Confidence: 0.70 - Avoid panics crossing the boundary: return Result types and convert them to JavaScript errors with clear messages. Confidence: 0.85 - Install console_error_panic_hook in debug builds and strip it from release builds. Confidence: 0.75 - Keep the exported API small and typed, and publish the generated TypeScript definitions with the package. Confidence: 0.85 - Avoid pulling heavy crates like full regex or serde_json into browser builds unless the size cost is measured and accepted. Confidence: 0.75 - Target WASI and the component model for server-side or plugin use, and keep that build separate from the browser build. Confidence: 0.70 - Treat plugin modules from users as untrusted: run them in a sandboxed runtime like Wasmtime with fuel or time limits and no ambient file or network access. Confidence: 0.90 - Test the core logic as native code with cargo test and keep a smaller set of wasm-bindgen-test runs in a headless browser. Confidence: 0.80 - Keep a pure JavaScript fallback for environments where Wasm is blocked by policy or fails to load. Confidence: 0.65
Opinionated conventions for handling production incidents, on-call and blameless postmortems.
# Incident Response taste of a calm on-call engineer - Declare an incident early and downgrade it later; a false alarm costs far less than a slow start. Confidence: 0.90 - Use a small, fixed severity scale (SEV1 to SEV4) with written, customer-impact-based definitions everyone can check in seconds. Confidence: 0.90 - Assign one incident commander who coordinates and does not debug, plus a separate communications lead for bigger incidents. Confidence: 0.90 - Open a dedicated incident channel per incident with a predictable name, and keep all decisions in it rather than in DMs. Confidence: 0.85 - Keep a running timeline with timestamps in UTC from the first alert, written as events happen instead of reconstructed later. Confidence: 0.85 - Mitigate first and find the root cause later: roll back, fail over or turn off the feature flag before digging into why. Confidence: 0.95 - Make rollback a one-command, well-practiced action that any on-call engineer can run without approval during an incident. Confidence: 0.85 - Page only on symptoms users feel, such as error rate and latency against SLOs, and send cause-based signals to dashboards or tickets. Confidence: 0.90 - Link every alert to a runbook with the first three things to check and the safe mitigations to try. Confidence: 0.85 - Post status page updates at a fixed rhythm, such as every 30 minutes, even when the update is "still investigating". Confidence: 0.80 - Write customer updates in plain language about impact and next update time, without guesses about the cause. Confidence: 0.85 - Freeze unrelated deploys to the affected system while an incident is open. Confidence: 0.80 - Hand off explicitly when an incident outlasts a shift, with a written summary of state, open threads and next steps. Confidence: 0.85 - Hold a blameless postmortem for every SEV1 and SEV2 within five working days, focused on systems and decisions, not people. Confidence: 0.95 - Write postmortems with impact, timeline, contributing factors, what went well and owned action items with due dates. Confidence: 0.85 - Track postmortem action items in the normal backlog and review overdue ones every week, or they never get done. Confidence: 0.85 - Keep on-call rotations at least six people deep, with a secondary on call, and compensate people for the load. Confidence: 0.80 - Measure alert noise and delete or fix any alert that paged without needing action more than twice. Confidence: 0.85 - Run game days or chaos drills each quarter to practice the response process on purpose, not only during real outages. Confidence: 0.70 - Track time to detect, time to mitigate and incident count by severity, and avoid using them as individual performance targets. Confidence: 0.75
Opinionated conventions for reproducible developer environments and CI with Nix flakes.
# Nix Dev Environment taste of a reproducibility-minded platform engineer - Pin nixpkgs through a flake.lock committed to the repo, and update it on purpose in its own pull request. Confidence: 0.95 - Expose the project shell as a devShell in flake.nix so `nix develop` gives every contributor the same toolchain. Confidence: 0.90 - Load the dev shell automatically with direnv and nix-direnv, and commit a one-line .envrc that only says `use flake`. Confidence: 0.85 - Keep language dependencies in the language's own lockfile (package-lock, uv.lock, Cargo.lock) and let Nix supply only the toolchain and system libraries. Confidence: 0.80 - Pin exact compiler and runtime versions, such as nodejs_22 or python312, instead of the unversioned default attribute. Confidence: 0.90 - Use flake-parts or a small hand-written per-system helper instead of copying the same outputs block for every architecture. Confidence: 0.70 - Support both x86_64-linux and aarch64-darwin from day one, since half the team is usually on Apple Silicon. Confidence: 0.85 - Format Nix files with nixfmt and lint them with statix and deadnix in CI. Confidence: 0.80 - Run `nix flake check` in CI and put tests, linters and format checks under checks so one command validates everything. Confidence: 0.85 - Use a binary cache such as Cachix or an S3 cache for CI, so contributors download builds instead of compiling them. Confidence: 0.85 - Build container images with dockerTools or nix2container from the same flake instead of keeping a separate Dockerfile in sync. Confidence: 0.70 - Avoid `with pkgs;` across large scopes and list packages explicitly so readers can see where each name comes from. Confidence: 0.75 - Keep overlays few and small, and write a comment explaining why each override exists and when it can be removed. Confidence: 0.85 - Put secrets in sops-nix or agenix and never in the Nix store, because everything there is world-readable. Confidence: 0.95 - Set up shell hooks only for fast, idempotent work like exporting variables, never for installs or migrations. Confidence: 0.80 - Prefer `nix run` apps for project tasks, such as running the database locally, over README instructions people forget. Confidence: 0.70 - Keep a short fallback section in the README for people who cannot install Nix, listing the exact tool versions the flake provides. Confidence: 0.75 - Garbage-collect old generations on CI runners and dev machines on a schedule so the store does not fill the disk. Confidence: 0.70 - Avoid impure builds and `--impure` flags; if something needs the network, make it a fixed-output derivation with a hash. Confidence: 0.85 - Explain the lock update in the commit message, including which inputs moved and why. Confidence: 0.75
Plans team leave around minimum staffing, especially over the holidays - checks every workday against role coverage rules (one person fills one slot), decides which requests can be approved in a fair priority order, offers gap-free alternative dates, respects blackout periods, part-time days and half days, and tracks allowances, carry-over and peak days off. Includes an offline stdlib Python planner with --self-test.
---
name: team-leave-and-holiday-coverage-planner
description: Plans team leave around minimum staffing, especially over the holidays - checks every workday against role coverage rules (one person fills one slot), decides which requests can be approved in a fair priority order, offers gap-free alternative dates, respects blackout periods, part-time days and half days, and tracks allowances, carry-over and peak days off. Includes an offline stdlib Python planner with --self-test.
---
# Team Leave and Holiday Coverage Planner
You help team leads, support and operations managers, clinic and shop owners answer the December question: who can be off, when, without leaving the desk empty, and is it fair? Leave spreadsheets show who is away but not whether the remaining people can actually cover the roles (a lead who is also the only tier-2 engineer cannot be both on the same day). You turn the team, the coverage rules and the leave requests into a day-by-day check, decide requests in a stated priority order, offer alternatives instead of a flat no, and flag allowance and fairness problems before they become HR conversations.
## Files in this skill
- `scripts/leave_cover.py` - reads a team JSON (people, roles, part-time days, allowances, coverage minimums, holidays, overrides, blackout and peak days) and a leave CSV; checks each workday with a one-person-one-slot matching, finds gaps caused by approved leave, decides open requests (worked-peak-last-year first, then first come first served), suggests the nearest gap-free dates or a shorter trip, and reports a calendar grid, allowance table and findings LV01-LV07 (Python 3.8+ standard library only)
- `references/coverage-rules.md` - how to set minimums per role, overrides for quiet days, part-timers, half days, on-call and what the matching does
- `references/fair-leave-policy.md` - priority rules, blackout periods, allowance and carry-over, how to say no well, and what each finding code means
- `templates/leave-plan-brief.md` - the plan to send to the team
- `examples/support-team.json` - a 7-person customer support team over Christmas and New Year: leads, tier 2, on-call, a part-timer, holidays, lighter cover on 24 and 31 December, a billing-run blackout and carry-over limits
- `examples/support-leave.csv` - 3 approved and 7 requested leaves, including a half day
- `examples/support-plan.md` - the real planner output on those files
## Workflow
### 1. Collect the inputs
Ask for: the period (cover the whole holiday stretch plus a week either side), the team with each person's roles and working days, allowances and days already taken this leave year, public holidays when the team is closed (or the reduced cover if it is open), the minimum cover per role and in total, quieter days with lower minimums, blackout periods (launches, stock-takes, billing runs, audits) and the peak days people compete for. Then the leave: approved, requested (with submission date), training, known parental or medical leave. Ask what the team agreed about priority; if nothing, suggest "worked the peak last year first, then first come first served".
### 2. Run the planner
```
python3 scripts/leave_cover.py examples/support-team.json examples/support-leave.csv
python3 scripts/leave_cover.py team.json leave.csv --json
python3 scripts/leave_cover.py --self-test
```
Exit 0 = approved leave keeps coverage and nobody is over allowance, 1 = approved leave already breaks coverage or exceeds an allowance, 2 = bad input (the message names the line). Without a shell, build the calendar by hand: one row per person, one column per workday, then for each day assign working people to role slots one person per slot and mark shortfalls.
### 3. Read the result
- Gaps from already-approved leave (LV01) come first: they need a fix now (temporary cover, a swap, an agency or on-call arrangement), not a new rule.
- Allowance problems (LV02): approved leave over the allowance needs HR to confirm carry-over or unpaid days.
- Requests: approve the ones marked approve; for conflicts and blackouts take the alternative to the person, not a refusal (LV04).
- Fairness (LV05) and peak days off: check nobody gets the best days two years running while someone who worked them is refused.
- Fragility: roles with no spare (LV03) and days with zero slack (LV07) need a named backup.
- Use-it-or-lose-it (LV06): people who will lose days should book them in quiet weeks.
### 4. Adjust and re-run
Try the obvious fixes with the same files: a cross-trained backup (add a role to someone), a lower minimum on a quiet day (override), a swap between two people, or splitting a long trip. Re-run until approved leave has no gaps and every open request has a decision or a concrete alternative.
### 5. Deliver
Fill `templates/leave-plan-brief.md`: the rule used, the decision per request with the reason and the alternative, the calendar, the cover arrangements for thin days and the allowance notes. Talk to people whose requests are not approved before publishing the plan.
## Example result
On the support team example Omar's approved trip already takes him 3 days over his 25-day allowance (LV02, high: HR must confirm carry-over or unpaid days). Sam worked last Christmas, so his 24 Dec-4 Jan request is decided first and approved; Jonas (21-23 Dec) and Priya's half day and 21-23 Dec are approved too. Ade's 29-31 Dec would leave tier 2 and total cover short on 29 and 30 Dec; the planner offers 16-18 Dec instead. Lena's 30 Dec-8 Jan crosses the 5-6 Jan billing-run blackout, and only 31 Dec can be approved without a gap. Jonas's second trip needs 5 days with 4 left. Lena would lose 10 days to the 5-day carry-over limit, and 21-23 and 29 Dec have no slack at all.
## Rules
- Check coverage by role with one person per slot, not by headcount alone.
- Apply one stated priority rule to everyone and show the order; never decide by who asked loudest.
- Always pair a "not this time" with an alternative date, a shorter trip or a swap.
- Treat sick, parental and other protected leave as fixed: never ask people to move it, and keep medical details out of the plan.
- Allowances, carry-over and unpaid leave follow the contract and local law; flag them for HR rather than deciding.
- Share the plan only with the team and managers who need it.
FILE:references/coverage-rules.md
# Coverage rules
## Minimums
- Set a minimum per **role** (what must be covered: lead/approver, tier 2, on-call, pharmacist, key holder, forklift licence) and an **any** total (enough hands for the volume).
- Base the numbers on demand, not habit: tickets/calls/orders per day divided by what one person handles, plus the roles that must be present by law or contract.
- Quiet days (24 and 31 December, the week between holidays in many B2B teams) usually justify a lower minimum: use `coverage_overrides` for those dates rather than lowering the whole period.
- Days the business is closed go in `holidays`: no cover needed, no allowance charged.
## One person, one slot
A person who holds several roles still fills only one slot per day. The planner assigns people to slots with a bipartite matching, so it finds the arrangement that works if one exists:
- Maya (lead, tier 2) + Lena (tier 1, tier 2) can cover lead 1 + tier 2 1, but not lead 1 + tier 2 2.
- Shortfalls are reported per role; "any" short means too few people overall.
## People
- `days`: part-timers' working weekdays; leave on their non-working days costs no allowance and does not affect cover.
- Half days (`half_day=yes`) cost 0.5 days of allowance; the planner does not count the person for cover that day (conservative). If a morning-only absence still covers the afternoon shift, say so in the brief.
- Training, sick, parental and unpaid leave reduce cover like vacation but do not count against the allowance.
## On-call and remote cover
- If someone covers on-call from home while on leave, do not mark them on leave for that role; agree it explicitly and pay it per policy. The planner treats leave as fully off.
- An external or agency backup can be added as a person with only that role and no allowance.
## Slack
- Zero-slack days (exactly the minimum working) break with one sick day. Name a backup for each such day before the period starts.
- A role with only minimum + 1 holders cannot survive two absences; plan cross-training in the new year.
FILE:references/fair-leave-policy.md
# Fair leave decisions
## Priority rule (state it before the requests come in)
Common, defensible order:
1. Already approved leave stands (unless the person agrees to change it).
2. Protected leave (parental, medical, statutory) is fixed.
3. For peak days: people who worked the peak last year go first.
4. Then first come, first served by submission date.
5. Ties: rotate (whoever lost the last tie wins this one).
Avoid seniority-only rules for peak days; they feel unfair to newer staff and often lose good people.
## Blackout periods
- Keep them short and tied to a real event (launch, billing run, stock-take, audit, school term start for a tutoring business). Announce them early.
- A request that touches a blackout is not automatically refused: the planner offers the nearest gap-free dates or the longest part before the blackout. Exceptions need a named approver.
## Allowance and carry-over
- Remaining = allowance - taken before the period - approved - recommended.
- Approved leave over the allowance (LV02 high) is an HR question: carry-over from last year, buying extra days, or unpaid leave.
- Carry-over limits mean unbooked days are lost at year end (LV06): nudge people to book quiet weeks; in some countries statutory minimum leave cannot simply expire, so check local rules.
## Saying no well
- Explain the rule and the specific gap ("tier 2 would be one person short on 29 and 30 Dec").
- Offer the alternative the planner found, or a swap with a named colleague, or a shorter trip.
- Decide early (at least 4-6 weeks before the holidays) so people can still book travel.
- Record the decision and the reason; next year's priority depends on it.
## Finding codes (leave_cover.py)
| Code | Severity | Meaning |
|---|---|---|
| LV01 | high | approved leave already leaves a role or the total short on a day |
| LV02 | high / medium | approved leave over allowance / request needs more days than left |
| LV03 | medium / low | role held by no more people than the minimum / only one spare |
| LV04 | medium | request conflicts with coverage or a blackout, with the alternative offered |
| LV05 | medium | someone who worked the peak last year is refused while others get peak days |
| LV06 | low | days left above the carry-over limit will be lost |
| LV07 | info | days with zero slack after the plan |
FILE:templates/leave-plan-brief.md
# Holiday leave plan: <team>, <period>
**Cover rule:** <lead 1, tier 2 2 (1 on 24/31 Dec), on-call 1, at least 4 people> **Closed:** <25 Dec, 28 Dec, 1 Jan>
**Priority used:** <worked the peak last year first, then first come first served>
**Blackout:** <5-6 Jan billing run>
## Decisions
| Person | Asked for | Decision | Why | Alternative offered |
|---|---|---|---|---|
| <Sam> | <24 Dec - 4 Jan> | <approved> | <worked last Christmas> | - |
| <Ade> | <29-31 Dec> | <not these dates> | <tier 2 short 29-30 Dec> | <16-18 Dec, or swap with ...> |
## Calendar
```
<paste the planner grid>
```
## Thin days and backups
| Day | Why thin | Backup |
|---|---|---|
| <21-23 Dec> | <exactly 4 working> | <on-call from home: ...> |
## Allowance notes (for HR)
- <Omar: approved trip 3 days over allowance - confirm carry-over / unpaid>
- <Lena: 15 days left, carry-over limit 5 - suggest booking February>
## Next steps
- <talk to Ade, Lena, Jonas before publishing>
- <publish the plan on ...; changes by swap request to ...>
- <cross-train a second lead backup in Q1>
FILE:examples/support-team.json
{
"team": "Northwind Customer Support",
"period": "2026-12-14..2027-01-08",
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"holidays": ["2026-12-25", "2026-12-28", "2027-01-01"],
"coverage": {"lead": 1, "tier2": 2, "oncall": 1, "any": 4},
"coverage_overrides": [
{"dates": "2026-12-24", "coverage": {"tier2": 1, "any": 3}},
{"dates": "2026-12-31", "coverage": {"tier2": 1, "any": 3}}
],
"blackout": [
{"dates": "2027-01-05..2027-01-06", "reason": "January billing run, all hands"}
],
"peak_days": "2026-12-21..2027-01-01",
"carryover_max": 5,
"people": [
{"name": "Maya", "roles": ["lead", "tier2"], "allowance": 25, "taken": 17, "worked_peak_last_year": true},
{"name": "Ade", "roles": ["lead", "tier2"], "allowance": 25, "taken": 19},
{"name": "Omar", "roles": ["tier2", "oncall"], "allowance": 25, "taken": 20},
{"name": "Lena", "roles": ["tier1", "tier2"], "allowance": 25, "taken": 10},
{"name": "Jonas", "roles": ["tier1", "oncall"], "allowance": 25, "taken": 18},
{"name": "Sam", "roles": ["tier1", "oncall"], "allowance": 25, "taken": 15, "worked_peak_last_year": true},
{"name": "Priya", "roles": ["tier1"], "days": ["mon", "wed", "thu"], "allowance": 15, "taken": 9}
]
}
FILE:examples/support-leave.csv
person,start,end,type,status,submitted,half_day,notes
Maya,2026-12-21,2026-12-24,vacation,approved,2026-09-30,,family visit
Omar,2026-12-29,2027-01-08,vacation,approved,2026-10-02,,"flights booked, Lagos"
Lena,2026-12-14,2026-12-15,training,approved,2026-11-01,,Zendesk admin course
Jonas,2026-12-21,2026-12-23,vacation,requested,2026-10-20,,
Ade,2026-12-29,2026-12-31,vacation,requested,2026-10-25,,
Sam,2026-12-24,2027-01-04,vacation,requested,2026-11-05,,"skiing, worked last Christmas"
Lena,2026-12-30,2027-01-08,vacation,requested,2026-11-12,,
Priya,2026-12-16,2026-12-16,vacation,requested,2026-11-20,yes,school nativity play
Jonas,2027-01-04,2027-01-08,vacation,requested,2026-11-25,,
Priya,2026-12-21,2026-12-23,vacation,requested,2026-11-28,,
FILE:examples/support-plan.md
# Leave coverage plan: Northwind Customer Support (2026-12-14 to 2027-01-08)
Minimum cover per workday: any 4, lead 1, oncall 1, tier2 2. Holidays (closed): 25 Dec, 28 Dec, 01 Jan.
**Verdict: over allowance on approved leave** - 4 of 7 open requests can be approved without a gap; 3 need a decision.
## Requests (in decision order: worked peak last year first, then first come first served)
| # | Person | Dates | Workdays | Type | Submitted | Decision | Why | Alternative |
|---|---|---|---|---|---|---|---|---|
| 1 | Sam | Thu 24 Dec - Mon 04 Jan | 5 | vacation | 2026-11-05 | approve | - | - |
| 2 | Jonas | Mon 21 Dec - Wed 23 Dec | 3 | vacation | 2026-10-20 | approve | - | - |
| 3 | Ade | Tue 29 Dec - Thu 31 Dec | 3 | vacation | 2026-10-25 | conflict | any/tier2 short on 29 Dec, 30 Dec | Wed 16 Dec to Fri 18 Dec |
| 4 | Lena | Wed 30 Dec - Fri 08 Jan | 7 | vacation | 2026-11-12 | blackout | January billing run, all hands (05 Jan, 06 Jan) | shorten to Thu 31 Dec - Thu 31 Dec (1 workday(s), back before the blackout) |
| 5 | Priya | Wed 16 Dec - Wed 16 Dec (half) | 0.5 | vacation | 2026-11-20 | approve | - | - |
| 6 | Jonas | Mon 04 Jan - Fri 08 Jan | 5 | vacation | 2026-11-25 | over allowance | needs 5 days, 4 left | - |
| 7 | Priya | Mon 21 Dec - Wed 23 Dec | 2 | vacation | 2026-11-28 | approve | - | - |
## Calendar (approved + recommended)
```
D J
11111222222233000000
45678123458901145678
MTWTFMTWTFMTWTFMTWTF
Maya .....VVVVHH...H.....
Ade .........HHxxxH.....
Omar .........HHVVVHVVVVV
Lena TT.......HH.xxHxxxxx
Jonas .....vvv.HH...Hxxxxx
Sam ........vHHvvvHv....
Priya .-h.-v-v.HH-..H.-..-
cover any +++++++++ +++ +++++
cover lead +++++++++ +++ +++++
cover oncall +++++++++ +++ +++++
cover tier2 +++++++++ +++ +++++
```
V/T/S/P/U/O approved vacation/training/sick/parental/unpaid/other, lowercase = request recommended for approval, h = half day, x = request not approved, H = holiday, - = not a working day, + / ! = role covered / short.
## Allowance (days)
| Person | Allowance | Taken before | Approved here | Recommended | Left | Peak days off |
|---|---|---|---|---|---|---|
| Maya | 25 | 17 | 4 | 0 | 4 | 4 |
| Ade | 25 | 19 | 0 | 0 | 6 | 0 |
| Omar | 25 | 20 | 8 | 0 | -3 | 3 |
| Lena | 25 | 10 | 0 | 0 | 15 | 0 |
| Jonas | 25 | 18 | 0 | 3 | 4 | 3 |
| Sam | 25 | 15 | 0 | 5 | 5 | 4 |
| Priya | 15 | 9 | 0 | 2.5 | 3.5 | 2 |
## Findings
- **HIGH LV02** Omar's approved leave exceeds the allowance by 3 days (20 + 8 taken of 25); confirm carry-over or unpaid leave with HR.
- **MEDIUM LV02** Jonas 04 Jan-08 Jan: needs 5 days, 4 left; approve a shorter trip or agree unpaid days
- **MEDIUM LV04** Ade 29 Dec-31 Dec: conflict (any/tier2 short on 29 Dec, 30 Dec); offer Wed 16 Dec to Fri 18 Dec
- **MEDIUM LV04** Lena 30 Dec-08 Jan: blackout (January billing run, all hands (05 Jan, 06 Jan)); offer shorten to Thu 31 Dec - Thu 31 Dec (1 workday(s), back before the blackout)
- **LOW LV03** lead has one spare person (Maya, Ade for a minimum of 1); two absences at once break it.
- **LOW LV06** Ade will still have 6 days left (carry-over limit 5); 1 day(s) are lost unless booked.
- **LOW LV06** Lena will still have 15 days left (carry-over limit 5); 10 day(s) are lost unless booked.
- **INFO LV07** No slack on 4 day(s) (21 Dec, 22 Dec, 23 Dec, 29 Dec): one sick day breaks coverage; line up a backup.
FILE:scripts/leave_cover.py
#!/usr/bin/env python3
"""Team leave and holiday coverage planner (Python 3.8+ standard library only).
Checks a team's leave requests against minimum staffing rules day by day, decides which
requests can be approved without leaving a gap, suggests alternative dates for the rest, and
tracks allowances and who gets the peak holiday days.
Team JSON:
{
"team": "Support Desk", "period": "2026-12-14..2027-01-08",
"workdays": ["mon", "tue", "wed", "thu", "fri"],
"holidays": ["2026-12-25", "2026-12-28", "2027-01-01"], (office closed, no coverage needed)
"coverage": {"lead": 1, "tier2": 2, "oncall": 1, "any": 4}, ("any" = total people working)
"coverage_overrides": [{"dates": "2026-12-24", "coverage": {"tier2": 1, "any": 3}}],
"blackout": [{"dates": "2027-01-05..2027-01-06", "reason": "billing run"}],
"peak_days": "2026-12-21..2027-01-01",
"people": [
{"name": "Maya", "roles": ["lead", "tier2"], "allowance": 25, "taken": 17, "worked_peak_last_year": true},
{"name": "Priya", "roles": ["tier1"], "days": ["mon", "wed", "thu"], "allowance": 15, "taken": 9}
]
}
Each working person fills ONE role slot per day (a lead who is also tier2 counts once), solved
as a bipartite matching.
Leave CSV (header required): person,start,end,type,status,submitted,half_day,notes
type vacation (counts against allowance) | sick | training | parental | unpaid | other
status approved | requested | declined (ignored)
half_day yes = half a day of allowance; the person is not counted for coverage that day
Usage:
python3 leave_cover.py team.json leave.csv [--json]
python3 leave_cover.py --self-test
Exit codes: 0 = approved leave keeps coverage and nobody is over allowance, 1 = approved leave
already breaks coverage or exceeds an allowance, 2 = bad input.
"""
import argparse
import csv
import datetime as dt
import json
import sys
from collections import OrderedDict, defaultdict
DAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
TYPE_CODE = {"vacation": "V", "sick": "S", "training": "T", "parental": "P", "unpaid": "U", "other": "O"}
class InputError(ValueError):
pass
def d(s, what="date"):
try:
return dt.date.fromisoformat(str(s).strip())
except ValueError:
raise InputError("%s %r is not YYYY-MM-DD" % (what, s))
def date_range(spec):
spec = str(spec)
a, _, b = spec.partition("..")
a, b = d(a), d(b or a)
if b < a:
raise InputError("range %r ends before it starts" % spec)
return [a + dt.timedelta(days=i) for i in range((b - a).days + 1)]
# ----------------------------------------------------------------------------- inputs
def load_team(cfg):
if "period" not in cfg or not cfg.get("people"):
raise InputError("team JSON needs 'period' and 'people'")
t = dict(cfg)
t["days"] = date_range(cfg["period"])
t["workdays"] = [w.lower()[:3] for w in cfg.get("workdays", DAYS[:5])]
t["holidays"] = {d(h, "holiday") for h in cfg.get("holidays", [])}
t["coverage"] = {k: int(v) for k, v in (cfg.get("coverage") or {}).items()}
over = {}
for o in cfg.get("coverage_overrides", []):
for day in date_range(o["dates"]):
over[day] = {k: int(v) for k, v in o["coverage"].items()}
t["overrides"] = over
t["blackout"] = [(set(date_range(b["dates"])), b.get("reason", "blackout")) for b in cfg.get("blackout", [])]
t["peak"] = set(date_range(cfg["peak_days"])) if cfg.get("peak_days") else set()
people = OrderedDict()
for p in cfg["people"]:
if "name" not in p:
raise InputError("every person needs a name")
q = dict(p)
q["roles"] = list(p.get("roles", []))
q["days"] = [w.lower()[:3] for w in p.get("days", t["workdays"])]
q["allowance"] = float(p.get("allowance", 0))
q["taken"] = float(p.get("taken", 0))
q["carryover_max"] = p.get("carryover_max", cfg.get("carryover_max"))
people[p["name"]] = q
t["people"] = people
roles = {r for p in people.values() for r in p["roles"]}
for r in list(t["coverage"]) + [r for o in over.values() for r in o]:
if r != "any" and r not in roles:
raise InputError("coverage role %r is not held by anyone" % r)
return t
def load_leave(path, team):
out = []
with open(path, newline="", encoding="utf-8") as f:
rows = [r for r in f if r.strip() and not r.lstrip().startswith("#")]
for n, row in enumerate(csv.DictReader(rows), 2):
name = (row.get("person") or "").strip()
if name not in team["people"]:
raise InputError("leave line %d: unknown person %r" % (n, name))
typ = (row.get("type") or "vacation").strip().lower()
if typ not in TYPE_CODE:
raise InputError("leave line %d: type must be one of %s" % (n, ", ".join(TYPE_CODE)))
status = (row.get("status") or "requested").strip().lower()
if status not in ("approved", "requested", "declined"):
raise InputError("leave line %d: status must be approved, requested or declined" % n)
start, end = d(row.get("start"), "start"), d(row.get("end") or row.get("start"), "end")
if end < start:
raise InputError("leave line %d: end before start" % n)
out.append({"id": n, "person": name, "start": start, "end": end, "type": typ, "status": status,
"submitted": (row.get("submitted") or "").strip() or "9999-12-31",
"half": (row.get("half_day") or "").strip().lower() in ("yes", "y", "true", "1"),
"notes": (row.get("notes") or "").strip()})
return out
# ----------------------------------------------------------------------------- core
def is_workday(team, day):
return DAYS[day.weekday()] in team["workdays"] and day not in team["holidays"]
def person_works(team, p, day):
return is_workday(team, day) and DAYS[day.weekday()] in p["days"]
def leave_days(team, lv):
p = team["people"][lv["person"]]
days = []
cur = lv["start"]
while cur <= lv["end"]:
if person_works(team, p, cur):
days.append(cur)
cur += dt.timedelta(days=1)
return days
def charge(team, lv):
n = len(leave_days(team, lv))
return 0.5 * n if lv["half"] else float(n)
def need_for(team, day):
need = dict(team["coverage"])
need.update(team["overrides"].get(day, {}))
return need
def shortfall(team, day, off):
"""Return {role: missing} for one day given the set of people off."""
if not is_workday(team, day):
return {}
avail = [p for n, p in team["people"].items() if n not in off and person_works(team, p, day)]
need = need_for(team, day)
slots = [r for r, k in sorted(need.items()) if r != "any" for _ in range(k)]
match = {} # slot index -> person name
def try_assign(pi, seen):
for si, role in enumerate(slots):
if role in avail[pi]["roles"] and si not in seen:
seen.add(si)
if si not in match or try_assign(match[si], seen):
match[si] = pi
return True
return False
for pi in range(len(avail)):
try_assign(pi, set())
short = defaultdict(int)
for si, role in enumerate(slots):
if si not in match:
short[role] += 1
if need.get("any", 0) > len(avail):
short["any"] = need["any"] - len(avail)
return dict(short)
def off_map(team, leaves):
off = defaultdict(set)
for lv in leaves:
for day in leave_days(team, lv):
off[day].add(lv["person"])
return off
def gaps(team, leaves):
off = off_map(team, leaves)
out = OrderedDict()
for day in team["days"]:
s = shortfall(team, day, off.get(day, set()))
if s:
out[day] = s
return out
def total(g):
return sum(sum(v.values()) for v in g.values())
def new_gaps(before, after):
out = OrderedDict()
for day, s in after.items():
b = before.get(day, {})
worse = {r: k - b.get(r, 0) for r, k in s.items() if k > b.get(r, 0)}
if worse:
out[day] = worse
return out
def blackout_hit(team, lv):
days = set(leave_days(team, lv))
for bdays, reason in team["blackout"]:
hit = sorted(days & bdays)
if hit:
return hit, reason
return None, None
def suggest(team, current, lv, max_shift=15):
"""Nearest shift of the same request (same calendar length) that keeps coverage."""
length = (lv["end"] - lv["start"]).days
want = len(leave_days(team, lv))
base = gaps(team, current)
first, last = team["days"][0], team["days"][-1]
for shift in sorted(range(-max_shift, max_shift + 1), key=lambda x: (abs(x), x)):
if shift == 0:
continue
cand = dict(lv, start=lv["start"] + dt.timedelta(days=shift), end=lv["start"] + dt.timedelta(days=shift + length))
if cand["start"] < first or cand["end"] > last:
continue
if len(leave_days(team, cand)) < want or blackout_hit(team, cand)[0]:
continue
if not new_gaps(base, gaps(team, current + [cand])):
return cand
return None
def trimmed(team, current, lv):
"""Longest part of a blackout-hitting request, before the first blackout day, that keeps coverage."""
hit, _ = blackout_hit(team, lv)
days = [x for x in leave_days(team, lv) if x < hit[0]]
base = gaps(team, current)
best = None
for i in range(len(days)):
for j in range(len(days) - 1, i - 1, -1):
if best and j - i + 1 <= len(leave_days(team, best)):
break
cand = dict(lv, start=days[i], end=days[j])
if not new_gaps(base, gaps(team, current + [cand])):
best = cand
break
return best
def plan(team, leaves):
findings = []
def add(sev, code, msg):
findings.append({"severity": sev, "code": code, "message": msg})
approved = [l for l in leaves if l["status"] == "approved"]
requests = [l for l in leaves if l["status"] == "requested"]
used = defaultdict(float)
for l in approved:
if l["type"] == "vacation":
used[l["person"]] += charge(team, l)
g_approved = gaps(team, approved)
for day, s in g_approved.items():
add("high", "LV01", "%s %s: approved leave already leaves %s short" % (
day.isoformat(), DAYS[day.weekday()].title(), ", ".join("%s by %d" % (r, k) for r, k in sorted(s.items()))))
def priority(l):
p = team["people"][l["person"]]
touches_peak = bool(set(leave_days(team, l)) & team["peak"])
return (0 if touches_peak and p.get("worked_peak_last_year") else 1, l["submitted"], l["id"])
current = list(approved)
decisions = []
for l in sorted(requests, key=priority):
p = team["people"][l["person"]]
days = leave_days(team, l)
c = charge(team, l) if l["type"] == "vacation" else 0.0
dec = {"leave": l, "workdays": len(days), "charge": c, "decision": "approve", "reason": "", "suggestion": None}
if not days:
dec.update(decision="no workdays", reason="falls on holidays / non-working days only")
elif l["type"] == "vacation" and p["allowance"] and p["taken"] + used[l["person"]] + c > p["allowance"] + 1e-9:
left = p["allowance"] - p["taken"] - used[l["person"]]
dec.update(decision="over allowance", reason="needs %g days, %g left" % (c, max(left, 0)))
else:
hit, reason = blackout_hit(team, l)
if hit:
dec.update(decision="blackout", reason="%s (%s)" % (reason, ", ".join(x.strftime("%d %b") for x in hit)))
else:
ng = new_gaps(gaps(team, current), gaps(team, current + [l]))
if ng:
roles = sorted({r for s in ng.values() for r in s})
dec.update(decision="conflict", reason="%s short on %s" % (
"/".join(roles), ", ".join(x.strftime("%d %b") for x in ng)))
if dec["decision"] == "approve":
current.append(l)
used[l["person"]] += c
elif dec["decision"] in ("conflict", "blackout"):
s = suggest(team, current, l)
if s:
dec["suggestion"] = "%s to %s" % (s["start"].strftime("%a %d %b"), s["end"].strftime("%a %d %b"))
elif dec["decision"] == "blackout":
t = trimmed(team, current, l)
if t:
dec["suggestion"] = "shorten to %s - %s (%d workday(s), back before the blackout)" % (
t["start"].strftime("%a %d %b"), t["end"].strftime("%a %d %b"), len(leave_days(team, t)))
decisions.append(dec)
approved_peak = defaultdict(int)
for l in current:
approved_peak[l["person"]] += len(set(leave_days(team, l)) & team["peak"])
for dec in decisions:
l = dec["leave"]
p = team["people"][l["person"]]
if dec["decision"] != "approve" and p.get("worked_peak_last_year") and set(leave_days(team, l)) & team["peak"]:
lucky = [n for n, k in approved_peak.items() if k and not team["people"][n].get("worked_peak_last_year")]
add("medium", "LV05", "%s worked the peak last year but this request is %s while %s get peak days off; "
"consider swapping." % (l["person"], dec["decision"], ", ".join(sorted(lucky)) or "nobody"))
g_final = gaps(team, current)
allowance = []
for n, p in team["people"].items():
planned_req = sum(dec["charge"] for dec in decisions if dec["leave"]["person"] == n and dec["decision"] == "approve")
appr = used[n] - planned_req
left = p["allowance"] - p["taken"] - used[n] if p["allowance"] else None
allowance.append({"person": n, "allowance": p["allowance"], "taken_before": p["taken"], "approved": appr,
"recommended": planned_req, "left": left, "peak_days_off": approved_peak.get(n, 0)})
if p["allowance"] and p["taken"] + appr > p["allowance"] + 1e-9:
add("high", "LV02", "%s's approved leave exceeds the allowance by %g days (%g + %g taken of %g); "
"confirm carry-over or unpaid leave with HR." % (n, p["taken"] + appr - p["allowance"], p["taken"],
appr, p["allowance"]))
cm = p.get("carryover_max")
if left is not None and cm is not None and left > float(cm):
add("low", "LV06", "%s will still have %g days left (carry-over limit %g); %g day(s) are lost unless booked."
% (n, left, float(cm), left - float(cm)))
for role, k in sorted(team["coverage"].items()):
if role == "any":
continue
holders = [n for n, p in team["people"].items() if role in p["roles"]]
if len(holders) <= k:
add("medium", "LV03", "Only %d people can cover %s (minimum %d): any absence by %s breaks coverage; "
"cross-train a backup." % (len(holders), role, k, ", ".join(holders)))
elif len(holders) == k + 1:
add("low", "LV03", "%s has one spare person (%s for a minimum of %d); two absences at once break it."
% (role, ", ".join(holders), k))
for day, s in g_final.items():
if day not in g_approved:
add("high", "LV01", "%s still short after decisions: %s" % (day, s))
for dec in decisions:
if dec["decision"] in ("conflict", "blackout"):
l = dec["leave"]
add("medium", "LV04", "%s %s-%s: %s (%s)%s" % (
l["person"], l["start"].strftime("%d %b"), l["end"].strftime("%d %b"), dec["decision"], dec["reason"],
("; offer " + dec["suggestion"]) if dec["suggestion"] else "; no gap-free alternative within 15 days"))
elif dec["decision"] == "over allowance":
l = dec["leave"]
add("medium", "LV02", "%s %s-%s: %s; approve a shorter trip or agree unpaid days" % (
l["person"], l["start"].strftime("%d %b"), l["end"].strftime("%d %b"), dec["reason"]))
thin = []
off = off_map(team, current)
for day in team["days"]:
if not is_workday(team, day):
continue
working = [n for n, p in team["people"].items() if n not in off.get(day, set()) and person_works(team, p, day)]
if len(working) == need_for(team, day).get("any", 0):
thin.append(day)
if thin:
add("info", "LV07", "No slack on %d day(s) (%s): one sick day breaks coverage; line up a backup." % (
len(thin), ", ".join(x.strftime("%d %b") for x in thin)))
order = {"high": 0, "medium": 1, "low": 2, "info": 3}
findings.sort(key=lambda f: (order[f["severity"]], f["code"]))
return {"team": team.get("team", "Team"), "period": team["period"], "gaps_approved": g_approved,
"gaps_final": g_final, "decisions": decisions, "allowance": allowance, "findings": findings,
"current": current, "thin": thin}
# ----------------------------------------------------------------------------- report
def grid(team, leaves_final, all_leaves):
days = [x for x in team["days"] if x.weekday() < 5 or DAYS[x.weekday()] in team["workdays"]]
final_ids = {l["id"] for l in leaves_final}
cell = defaultdict(dict)
for l in all_leaves:
if l["status"] == "declined":
continue
for day in leave_days(team, l):
if l["id"] in final_ids:
c = TYPE_CODE[l["type"]] if l["status"] == "approved" else TYPE_CODE[l["type"]].lower()
else:
c = "x"
if l["half"]:
c = "h" if c != "x" else "x"
cell[l["person"]][day] = c
width = max([len(n) for n in team["people"]] + [len("cover " + r) for r in team["coverage"]]) + 2
months = " " * width + "".join(x.strftime("%b")[0] if i == 0 or x.month != days[i - 1].month
else " " for i, x in enumerate(days))
L = [months, " " * width + "".join(str(x.day // 10) for x in days),
" " * width + "".join(str(x.day % 10) for x in days),
" " * width + "".join(DAYS[x.weekday()][0].upper() for x in days)]
for n, p in team["people"].items():
row = []
for x in days:
if x in team["holidays"]:
row.append("H")
elif not person_works(team, p, x):
row.append("-")
else:
row.append(cell[n].get(x, "."))
L.append(n.ljust(width) + "".join(row))
off = off_map(team, leaves_final)
for role in sorted(team["coverage"]):
row = []
for x in days:
if not is_workday(team, x):
row.append(" ")
continue
s = shortfall(team, x, off.get(x, set()))
row.append("!" if role in s else "+")
L.append(("cover " + role).ljust(width) + "".join(row))
return L
def report(team, leaves, r):
L = ["# Leave coverage plan: %s (%s)" % (r["team"], r["period"].replace("..", " to ")), ""]
need = ", ".join("%s %d" % (k, v) for k, v in sorted(team["coverage"].items()))
L.append("Minimum cover per workday: %s. Holidays (closed): %s." % (
need, ", ".join(x.strftime("%d %b") for x in sorted(team["holidays"])) or "none"))
n_appr = sum(1 for dec in r["decisions"] if dec["decision"] == "approve")
verdict = ("approved leave already breaks coverage" if r["gaps_approved"] else
"over allowance on approved leave" if any(f["code"] == "LV02" and f["severity"] == "high" for f in r["findings"])
else "covered")
L += ["", "**Verdict: %s** - %d of %d open requests can be approved without a gap; %d need a decision." % (
verdict, n_appr, len(r["decisions"]), len(r["decisions"]) - n_appr), ""]
L += ["## Requests (in decision order: worked peak last year first, then first come first served)", "",
"| # | Person | Dates | Workdays | Type | Submitted | Decision | Why | Alternative |",
"|---|---|---|---|---|---|---|---|---|"]
for i, dec in enumerate(r["decisions"], 1):
l = dec["leave"]
L.append("| %d | %s | %s - %s%s | %s | %s | %s | %s | %s | %s |" % (
i, l["person"], l["start"].strftime("%a %d %b"), l["end"].strftime("%a %d %b"), " (half)" if l["half"] else "",
("%g" % (0.5 * dec["workdays"])) if l["half"] else dec["workdays"], l["type"], l["submitted"][:10],
dec["decision"], dec["reason"] or "-", dec["suggestion"] or "-"))
L += ["", "## Calendar (approved + recommended)", "", "```"] + grid(team, r["current"], leaves) + [
"```", "V/T/S/P/U/O approved vacation/training/sick/parental/unpaid/other, lowercase = request recommended "
"for approval, h = half day, x = request not approved, H = holiday, - = not a working day, "
"+ / ! = role covered / short.", ""]
L += ["## Allowance (days)", "", "| Person | Allowance | Taken before | Approved here | Recommended | Left | Peak days off |",
"|---|---|---|---|---|---|---|"]
for a in r["allowance"]:
L.append("| %s | %g | %g | %g | %g | %s | %d |" % (a["person"], a["allowance"], a["taken_before"], a["approved"],
a["recommended"], "-" if a["left"] is None else "%g" % a["left"],
a["peak_days_off"]))
L += ["", "## Findings", ""]
if not r["findings"]:
L.append("- none")
for f in r["findings"]:
L.append("- **%s %s** %s" % (f["severity"].upper(), f["code"], f["message"]))
return "\n".join(L) + "\n"
# ----------------------------------------------------------------------------- self-test
def self_test():
import os
import tempfile
cases = []
def ok(name, cond, detail=""):
cases.append((name, bool(cond), detail))
base = {"team": "T", "period": "2026-12-14..2026-12-25", "holidays": ["2026-12-25"], "peak_days": "2026-12-21..2026-12-24",
"coverage": {"lead": 1, "dev": 1, "any": 2},
"people": [{"name": "A", "roles": ["lead", "dev"], "allowance": 10, "taken": 0},
{"name": "B", "roles": ["dev"], "allowance": 10, "taken": 9},
{"name": "C", "roles": ["lead"], "allowance": 10, "taken": 0, "worked_peak_last_year": True},
{"name": "D", "roles": ["dev"], "days": ["mon", "tue"], "allowance": 5, "taken": 0}]}
t = load_team(base)
ok("weekends and holidays are not workdays", not is_workday(t, d("2026-12-19")) and not is_workday(t, d("2026-12-25")))
sf = shortfall(t, d("2026-12-16"), {"B", "C", "D"})
ok("one person fills one slot (A cannot be lead and dev)", sf.get("any") == 1 and sf.get("lead", 0) + sf.get("dev", 0) == 1, sf)
ok("matching finds A=dev, C=lead", shortfall(t, d("2026-12-16"), {"B", "D"}) == {})
ok("part-timer only counts on own days", shortfall(t, d("2026-12-17"), {"A", "B"}) == {"dev": 1, "any": 1}, shortfall(t, d("2026-12-17"), {"A", "B"}))
lv = {"id": 1, "person": "D", "start": d("2026-12-14"), "end": d("2026-12-20"), "type": "vacation",
"status": "requested", "submitted": "2026-11-01", "half": False, "notes": ""}
ok("leave days skip non-working days", len(leave_days(t, lv)) == 2)
ok("half day charges 0.5", charge(t, dict(lv, half=True)) == 1.0)
d0 = tempfile.mkdtemp()
csvp = os.path.join(d0, "l.csv")
with open(csvp, "w") as f:
f.write("person,start,end,type,status,submitted,half_day,notes\n"
"A,2026-12-21,2026-12-22,vacation,approved,2026-10-01,,\n"
"C,2026-12-21,2026-12-21,vacation,requested,2026-11-10,,\n"
"B,2026-12-14,2026-12-16,vacation,requested,2026-10-05,,\n"
"D,2026-12-15,2026-12-15,training,requested,2026-11-01,,\n")
leaves = load_leave(csvp, t)
r = plan(t, leaves)
by = {(dec["leave"]["person"]): dec for dec in r["decisions"]}
ok("C conflicts with A's approved leave (no lead)", by["C"]["decision"] == "conflict", by["C"])
ok("conflict gets an alternative date", by["C"]["suggestion"] is not None, by["C"])
ok("B over allowance (3 days, 1 left)", by["B"]["decision"] == "over allowance", by["B"])
ok("D training approved", by["D"]["decision"] == "approve", by["D"])
ok("worked-peak person decided first", r["decisions"][0]["leave"]["person"] == "C", [x["leave"]["person"] for x in r["decisions"]])
codes = {f["code"] for f in r["findings"]}
ok("LV03 single point of failure for lead", "LV03" in codes, codes)
ok("LV05 fairness flag for C", "LV05" in codes, codes)
ok("no gaps from approved leave", not r["gaps_approved"])
with open(csvp, "a") as f:
f.write("C,2026-12-21,2026-12-21,sick,approved,2026-12-01,,\n")
r2 = plan(t, load_leave(csvp, t))
ok("LV01 gap in approved leave", any(f["code"] == "LV01" and f["severity"] == "high" for f in r2["findings"]))
t2 = load_team(dict(base, blackout=[{"dates": "2026-12-16", "reason": "release"}]))
r3 = plan(t2, load_leave(csvp, t2))
ok("blackout precedes coverage", any(x["decision"] == "blackout" for x in r3["decisions"]) or
all(x["leave"]["person"] != "B" or x["decision"] == "over allowance" for x in r3["decisions"]))
lvb = dict(lv, person="A", start=d("2026-12-16"), end=d("2026-12-16"))
ok("blackout_hit detects overlap", blackout_hit(t2, lvb)[0] == [d("2026-12-16")])
try:
load_team(dict(base, coverage={"qa": 1}))
ok("unknown coverage role rejected", False)
except InputError:
ok("unknown coverage role rejected", True)
rep = report(t, leaves, r)
ok("report has grid and verdict", "Verdict" in rep and "cover lead" in rep)
for name, good, detail in cases:
print("%s %s%s" % ("PASS" if good else "FAIL", name, "" if good else " -> %r" % (detail,)))
passed = sum(1 for _, g, _ in cases if g)
print("%d/%d passed" % (passed, len(cases)))
return 0 if passed == len(cases) else 1
def to_json(r):
def conv(o):
if isinstance(o, dt.date):
return o.isoformat()
if isinstance(o, dict):
return {(k.isoformat() if isinstance(k, dt.date) else k): conv(v) for k, v in o.items()}
if isinstance(o, (list, tuple)):
return [conv(x) for x in o]
return o
return json.dumps(conv(r), indent=2)
def main(argv=None):
ap = argparse.ArgumentParser(description="Plan team leave against minimum coverage.")
ap.add_argument("team", nargs="?")
ap.add_argument("leave", nargs="?")
ap.add_argument("--json", action="store_true")
ap.add_argument("--self-test", action="store_true")
a = ap.parse_args(argv)
if a.self_test:
return self_test()
if not (a.team and a.leave):
ap.print_usage(sys.stderr)
return 2
try:
with open(a.team, encoding="utf-8") as f:
team = load_team(json.load(f))
leaves = load_leave(a.leave, team)
r = plan(team, leaves)
except (InputError, OSError, json.JSONDecodeError, KeyError) as e:
print("error: %s" % e, file=sys.stderr)
return 2
print(to_json(r) if a.json else report(team, leaves, r), end="\n" if a.json else "")
bad = r["gaps_approved"] or any(f["code"] == "LV02" and f["severity"] == "high" for f in r["findings"])
return 1 if bad else 0
if __name__ == "__main__":
sys.exit(main())Checks pip requirements and constraints files offline before they break a build - unpinned and lower-bound-only packages, PEP 440 specifier conflicts across files and markers, git refs without a commit hash, editable and local paths, partial hash mode, pre-releases, --extra-index-url dependency confusion, http indexes, stdlib backports, dev tools in runtime files and pip freeze drift. Includes a stdlib Python checker with --self-test.
---
name: python-requirements-pin-and-conflict-checker
description: Checks pip requirements and constraints files offline before they break a build - unpinned and lower-bound-only packages, PEP 440 specifier conflicts across files and markers, git refs without a commit hash, editable and local paths, partial hash mode, pre-releases, --extra-index-url dependency confusion, http indexes, stdlib backports, dev tools in runtime files and pip freeze drift. Includes a stdlib Python checker with --self-test.
---
# Python Requirements Pin and Conflict Checker
You help Python developers and DevOps engineers make `pip install -r requirements.txt` boring: the same versions on every machine, no ResolutionImpossible at 2 a.m., no package quietly fetched from the wrong index. Most broken installs come from a few patterns that are visible in the files themselves: a constraint file that contradicts a requirement, `requests` with no pin, a git dependency on `@main`, one line with `--hash` that switches the whole file into hash mode, or an extra index that lets a public package shadow an internal one. You read every file pip would read, check the specifiers with real PEP 440 rules, and turn the result into a short fix list and a lock-file plan.
## Files in this skill
- `scripts/req_check.py` - parses requirements files the way pip does (`-r` includes, `-c` constraints, line continuations, comments, extras, PEP 508 markers, `--hash`, index options, VCS/URL/editable lines), evaluates PEP 440 specifiers including `~=`, wildcards and pre-releases, searches for a version that satisfies every specifier per marker environment, compares with `pip freeze` output, and reports findings PR01-PR14 (Python 3.8+ standard library only, no network)
- `references/specifiers-and-markers.md` - PEP 440 operators, pre-release rules, constraints vs requirements, marker evaluation, and why the checker cannot see transitive dependencies
- `references/pinning-strategy.md` - apps vs libraries, the requirements.in + lock workflow, hashes, indexes and supply-chain hygiene, and what each finding code means
- `templates/requirements-review.md` - the review to hand back
- `examples/requirements.txt` - a Django invoice service with a typo, a git branch dependency, an extra index, a marker-split numpy, a duplicate Django, enum34 and pytest in runtime
- `examples/constraints.txt` - the platform team's constraints file that contradicts two of those requirements
- `examples/req-report.md` - the real checker output on those files
## Workflow
### 1. Collect the inputs
Ask for every file pip reads for the target: the main requirements file, anything it includes with `-r` or `-c`, plus `pip.conf` / `PIP_INDEX_URL` / `PIP_EXTRA_INDEX_URL` settings from CI and Dockerfiles (index options outside the file are invisible to the checker; add them by hand). Ask whether the project is an **app** (deployed service, notebook image, CLI shipped as a container: pins expected) or a **library** (installed by other projects: ranges expected), which Python versions and platforms it targets, and for a `pip freeze` from the environment that misbehaves.
### 2. Run the checker
```
python3 scripts/req_check.py examples/requirements.txt
python3 scripts/req_check.py requirements.txt --python 3.12 --platform linux
python3 scripts/req_check.py requirements.txt requirements-dev.txt --installed freeze.txt
python3 scripts/req_check.py requirements.txt --mode lib --json
python3 scripts/req_check.py --self-test
```
Exit 0 = no high findings, 1 = high findings, 2 = a file could not be read. Without `--python`, each marker combination is checked as its own environment; with it, lines whose markers do not apply are dropped. Without a shell, go line by line with `references/specifiers-and-markers.md` and test each package's combined specifiers against a concrete version.
### 3. Read the result
- Install blockers first: PR03 (no version satisfies the combined specifiers), PR07 (partial hash mode), PR13 (lines pip refuses). These fail before anything installs.
- Supply chain: PR09 extra index, PR10 http or trusted hosts, PR05 git refs that can move, PR11 bogus or dangerous packages (`sklearn`, `enum34`, `pycrypto`).
- Reproducibility: PR01 unpinned and PR02 open-ended ranges in apps; in libraries, PR02 flags exact pins that would force versions on users.
- Drift: PR12 shows where the running environment differs from the files.
- Tidy-ups: PR04 duplicates, PR06 editable/local paths, PR08 pre-releases, PR14 dev tools in runtime files.
### 4. Fix and lock
Resolve each conflict by deciding which side is right (often the constraint is stale; check the changelog of the newer version), then move intent-level ranges to `requirements.in` and generate the pinned, hashed lock with pip-tools or uv (`references/pinning-strategy.md`). Re-run the checker on the lock: it should come back with no high or medium findings. Never "fix" a conflict by deleting the constraint without asking who owns it.
### 5. Deliver
Fill `templates/requirements-review.md`: verdict, blockers with exact replacement lines, supply-chain changes, the lock workflow and CI commands, and an owner for each change.
## Example result
On `examples/requirements.txt` the checker finds 6 high findings. Two conflicts make pip fail: `urllib3>=2.2` against the platform constraint `urllib3<2`, and `numpy>=2.1` against the constraint `numpy==1.26.4` for Python 3.9+ (the Python < 3.9 branch is fine). `pandas=2.2.3` uses a single `=` and is refused. `pdf-stamp` is installed editable from `@main`, the internal index is added with `--extra-index-url` (dependency confusion risk for `acme-billing-core`), and `enum34` breaks the standard library `enum` on modern Python. Medium and low: open-ended `whitenoise` and `urllib3`, the editable install, Django declared twice as `Django` and `django`, and `pytest` in the runtime file.
## Rules
- Read every file pip reads, including constraints and includes; a single file in isolation hides conflicts.
- Use PEP 440 semantics, not string comparison (`2.10` > `2.9`, `~=2.1` allows 2.9 but not 3.0, `<2.0` excludes `2.0rc1`).
- Say clearly that the checker sees only declared packages: transitive conflicts and CVEs need the resolver (`pip install --dry-run`, `pip-compile`, `uv pip compile`) and `pip-audit`.
- Prefer a lock file generated by a tool over hand pins; never invent version numbers - take them from the current environment, the lock, or the package changelog.
- Treat index and VCS findings as security issues, not style.
FILE:references/specifiers-and-markers.md
# Specifiers, constraints and markers
## PEP 440 operators
| Specifier | Means | Example matches | Does not match |
|---|---|---|---|
| `==2.32.3` | exactly this version (trailing zeros ignored: `==2.0` = `==2.0.0`) | 2.32.3 | 2.32.4 |
| `==2.32.*` | any 2.32.x | 2.32.0, 2.32.9 | 2.33.0 |
| `~=2.31` | `>=2.31, ==2.*` | 2.31, 2.40 | 3.0 |
| `~=2.31.0` | `>=2.31.0, ==2.31.*` | 2.31.5 | 2.32.0 |
| `>=4.2,<5.0` | range | 4.2.16 | 5.0 |
| `!=3.1.2` | anything but | 3.1.3 | 3.1.2 |
| `===foo` | arbitrary string equality (avoid) | foo | |
- Order: `1.0.dev1 < 1.0a1 < 1.0b1 < 1.0rc1 < 1.0 < 1.0.post1`; `1.10 > 1.9` (numeric, not text).
- `~=2` is invalid (needs at least two release parts); `pkg=1.0` is invalid (single `=`).
- Pre-releases are only chosen if a specifier names one (`>=2.0rc1`), you pass `--pre`, or nothing else matches.
- `<2.0` excludes 2.0 pre-releases; `>1.4` excludes 1.4 post-releases.
- Local versions (`1.0+cpu`) only match `==1.0+cpu` or ranges; `==1.0` matches `1.0+cpu` too.
## Requirements vs constraints
- A requirement (`pkg>=1`) asks pip to install the package.
- A constraint (`-c constraints.txt`, `PIP_CONSTRAINT`) only restricts versions if something requires the package; it never installs anything. Constraints cannot have extras or be editable.
- pip combines all specifiers for a name (after PEP 503 normalisation: `Django`, `django`, `DJANGO` and `foo_bar` / `foo-bar` / `foo.bar` are the same). If nothing satisfies all of them, the resolver stops with ResolutionImpossible.
## Markers (PEP 508)
- `numpy<2 ; python_version < "3.9"` - the line applies only where the marker is true.
- Variables: `python_version` ("3.12"), `python_full_version`, `sys_platform` (linux, win32, darwin), `platform_system` (Linux, Windows, Darwin), `os_name` (posix, nt), `platform_machine`, `implementation_name`, `extra`.
- Two lines for one package with opposite markers are fine; two lines with the same (or no) marker are duplicates.
- The checker evaluates markers when you pass `--python` / `--platform`; otherwise it checks the unmarked lines plus each marker set as a separate environment.
## What an offline file check cannot see
- Transitive dependencies: `requests==2.32.3` needs `urllib3>=1.21.1,<3`; a conflict between what your packages need is only visible to the resolver. Run `pip install --dry-run --report report.json -r requirements.txt` or compile a lock.
- Wheels: a pinned version may have no wheel for your Python/platform (e.g. a new Python release) and fall back to a source build.
- Yanked releases and CVEs: use `pip-audit` and the index.
FILE:references/pinning-strategy.md
# Pinning strategy and supply-chain hygiene
## Apps vs libraries
| | App (service, job, notebook image) | Library (installed by others) |
|---|---|---|
| Direct deps | ranges in `requirements.in` / pyproject | ranges in pyproject `dependencies` |
| What gets installed | a lock: every package `==` + `--hash` | whatever the user's resolver picks |
| Exact pins | required (in the lock) | avoid: they force versions on every user |
| Upper bounds | let the lock hold them | only for known breakage (`<3` when 3.0 broke you) |
## The lock workflow (apps)
1. `requirements.in`: what you import, with intent-level ranges (`Django>=4.2,<5`).
2. Compile: `pip-compile --generate-hashes -o requirements.txt requirements.in` or `uv pip compile --generate-hashes requirements.in -o requirements.txt`. Add `-c constraints.txt` if the platform team ships constraints.
3. Install: `pip install --require-hashes --no-deps -r requirements.txt` (the lock already contains everything).
4. Update on purpose: `pip-compile --upgrade-package django`, review the diff, run tests. Automate with Dependabot/Renovate.
5. Dev tools go to `requirements-dev.in` compiled with `-c requirements.txt` so versions agree.
## Hashes
- One `--hash` anywhere turns on hash-checking mode for the whole install: every package (including transitive ones) needs `==` and a hash, or pip refuses. That is why partial hashes (PR07) break installs.
- Generate hashes with a tool, never by hand; include every platform's wheel hash if you build on several.
## Indexes
- `--extra-index-url` makes pip look at both indexes and take the highest version: anyone can upload `acme-billing-core==99.0` to PyPI and win (dependency confusion). Use one `--index-url` that points to a proxy (Artifactory, Nexus, devpi, CodeArtifact, Azure Artifacts) serving PyPI plus internal packages, and reserve internal names on PyPI.
- https only; `--trusted-host` turns off certificate checks.
- VCS dependencies: pin a full commit (`@3f2a...` 40 hex); branches and tags move. Better: publish a wheel to the internal index.
## Finding codes (req_check.py)
| Code | Severity | Meaning |
|---|---|---|
| PR01 | high (app) / info (lib) | no version specifier and no pin elsewhere |
| PR02 | medium | app: only `>=`/`>`; lib: exact `==` pin |
| PR03 | high | no version satisfies the combined specifiers (per marker environment) |
| PR04 | low / info | declared more than once, or spelt differently |
| PR05 | high / medium | VCS ref without commit hash / archive URL without hash |
| PR06 | medium | editable or local path |
| PR07 | high | partial hash mode, or hashed line without `==` |
| PR08 | medium | pre-release pin or `--pre` |
| PR09 | high | `--extra-index-url` |
| PR10 | high / medium | http index or URL / `--trusted-host` |
| PR11 | varies | bogus, unmaintained or backport package (sklearn, pycrypto, enum34, typing, nose ...) |
| PR12 | high / medium | installed version does not satisfy the files / package missing |
| PR13 | high | line pip refuses (bad specifier, missing include) |
| PR14 | low | test or dev tool in a runtime file |
FILE:templates/requirements-review.md
# Requirements review: <project>
**Files:** <requirements.txt, constraints.txt (-c)> **Kind:** <app / library> **Python:** <3.12, linux>
**Verdict:** <install fails / installs but not reproducible / reproducible>
## Blockers (install fails)
| # | Where | Problem | Replace with |
|---|---|---|---|
| 1 | <constraints.txt:3 + requirements.txt:24> | <urllib3 <2 vs >=2.2> | <agree on urllib3>=2.2,<3 with platform team; update PLAT-412> |
| 2 | <requirements.txt:20> | <pandas=2.2.3> | <pandas==2.2.3> |
## Supply chain
| # | Where | Problem | Fix |
|---|---|---|---|
| 1 | <requirements.txt:5> | <--extra-index-url internal> | <single --index-url to the proxy; reserve acme-* on PyPI> |
| 2 | <requirements.txt:28> | <pdf-stamp @main, editable> | <publish a wheel or pin @<commit>> |
## Reproducibility and tidy-ups
- <unpinned / open ranges: ...>
- <duplicates, backports, dev tools: ...>
## Lock plan
```
<requirements.in contents (intent ranges)>
pip-compile --generate-hashes -c constraints.txt -o requirements.txt requirements.in
pip install --require-hashes --no-deps -r requirements.txt
```
## Not covered by this check
<transitive conflicts: run pip install --dry-run / pip-compile; CVEs: pip-audit; wheel availability for Python X.Y>
## Owners and dates
| Change | Owner | By |
|---|---|---|
| <...> | <...> | <...> |
FILE:examples/requirements.txt
# invoice-service runtime dependencies (deployed as a Docker image, Python 3.12)
# org-wide pins from the platform team:
-c constraints.txt
--index-url https://pypi.org/simple
--extra-index-url https://pkgs.acme.internal/simple
# web
Django>=4.2,<5.0
djangorestframework~=3.15
gunicorn==23.0.0
whitenoise>=6.7
# data and storage
psycopg[binary]==3.2.3
redis>=5.0
celery[redis]==5.4.0
boto3
numpy<2 ; python_version < "3.9"
numpy>=2.1 ; python_version >= "3.9"
pandas=2.2.3
# http
requests==2.32.3
urllib3>=2.2
# pdf invoices
weasyprint==63.0
-e git+https://github.com/acme/pdf-stamp.git@main#egg=pdf-stamp
acme-billing-core==1.8.2
# misc
python-dateutil==2.9.0.post0
sentry-sdk>=2.0
django==4.2.16
enum34==1.1.10
pytest==8.3.3
FILE:examples/constraints.txt
# Platform team constraints, applied to every service with -c
# (pins only; a constraint never installs anything by itself)
urllib3<2 # legacy proxy TLS issue, see PLAT-412
numpy==1.26.4 # matches the shared analytics image
redis==5.0.8
boto3==1.35.36
sentry-sdk==2.17.0
celery==5.4.0
cryptography>=43.0.1 # CVE floor
FILE:examples/req-report.md
# Requirements check: examples/requirements.txt
Mode: app. Packages: 19 (14 pinned with ==). Findings: 6 high, 3 medium, 2 low, 0 info.
**Verdict: install will fail or is unsafe - fix the high findings first**
## Packages
| Package | Specifiers (all files) | Marker | Declared in | Status |
|---|---|---|---|---|
| urllib3 | `<2,>=2.2` | - | examples/constraints.txt:3 (constraint), examples/requirements.txt:24 | conflict |
| numpy | `<2,==1.26.4,>=2.1` | `python_version < "3.9"; python_version >= "3.9"` | examples/constraints.txt:4 (constraint), examples/requirements.txt:18, examples/requirements.txt:19 | conflict |
| redis | `==5.0.8,>=5.0` | - | examples/constraints.txt:5 (constraint), examples/requirements.txt:15 | ok |
| boto3 | `==1.35.36` | - | examples/constraints.txt:6 (constraint), examples/requirements.txt:17 | ok |
| sentry-sdk | `==2.17.0,>=2.0` | - | examples/constraints.txt:7 (constraint), examples/requirements.txt:33 | ok |
| celery | `==5.4.0` | - | examples/constraints.txt:8 (constraint), examples/requirements.txt:16 | ok |
| cryptography | `>=43.0.1` | - | examples/constraints.txt:9 (constraint) | ok |
| Django | `<5.0,==4.2.16,>=4.2` | - | examples/requirements.txt:8, examples/requirements.txt:34 | ok |
| djangorestframework | `~=3.15` | - | examples/requirements.txt:9 | ok |
| gunicorn | `==23.0.0` | - | examples/requirements.txt:10 | ok |
| whitenoise | `>=6.7` | - | examples/requirements.txt:11 | ok |
| psycopg | `==3.2.3` | - | examples/requirements.txt:14 | ok |
| requests | `==2.32.3` | - | examples/requirements.txt:23 | ok |
| weasyprint | `==63.0` | - | examples/requirements.txt:27 | ok |
| pdf-stamp | `url` | - | examples/requirements.txt:28 | ok |
| acme-billing-core | `==1.8.2` | - | examples/requirements.txt:29 | ok |
| python-dateutil | `==2.9.0.post0` | - | examples/requirements.txt:32 | ok |
| enum34 | `==1.1.10` | - | examples/requirements.txt:35 | ok |
| pytest | `==8.3.3` | - | examples/requirements.txt:36 | ok |
## Findings
- **HIGH PR03** `examples/constraints.txt:3, examples/requirements.txt:24` urllib3: no version satisfies <2 (examples/constraints.txt:3), >=2.2 (examples/requirements.txt:24) - pip will fail with ResolutionImpossible
- **HIGH PR03** `examples/constraints.txt:4, examples/requirements.txt:19` numpy: no version satisfies ==1.26.4 (examples/constraints.txt:4), >=2.1 (examples/requirements.txt:19) when python_version >= "3.9" - pip will fail with ResolutionImpossible
- **HIGH PR05** `examples/requirements.txt:28` pdf-stamp comes from git+https://github.com/acme/pdf-stamp.git@main#egg=pdf-stamp without a full commit hash; the branch or tag can move under you - pin @<40-char commit>
- **HIGH PR09** `examples/requirements.txt:5` --extra-index-url https://pkgs.acme.internal/simple: pip picks the highest version from ANY index, so a public package with the same name as an internal one wins (dependency confusion); use a single --index-url proxy that serves both, or pin with --hash
- **HIGH PR11** `examples/requirements.txt:35` enum34 breaks the stdlib enum on Python 3.6+ (e.g. AttributeError in re); remove it
- **HIGH PR13** `examples/requirements.txt:20` pandas: single '=' in '=2.2.3' (use ==) - pip will refuse this line
- **MEDIUM PR02** `examples/requirements.txt:11` whitenoise >=6.7 has no upper bound and no pin; a future major release installs automatically
- **MEDIUM PR02** `examples/requirements.txt:24` urllib3 >=2.2 has no upper bound and no pin; a future major release installs automatically
- **MEDIUM PR06** `examples/requirements.txt:28` pdf-stamp is an editable install; it will not exist on a clean build machine - publish it or build a wheel
- **LOW PR04** `examples/requirements.txt:8, examples/requirements.txt:34` Django is declared 2 times (spelt Django, django); keep one line
- **LOW PR14** `examples/requirements.txt:36` pytest is a development/test tool in a runtime requirements file; move it to requirements-dev.txt so production images stay small
## Next steps
1. Fix every PR03/PR07/PR13 line - these stop `pip install` outright.
2. Keep loose, intent-level ranges in `requirements.in` (or pyproject) and generate a fully pinned, hashed lock with `pip-compile --generate-hashes` or `uv pip compile --generate-hashes`; install with `pip install --require-hashes -r requirements.txt`.
3. Use one `--index-url` (a proxy that mirrors PyPI and hosts internal packages), https only.
4. Run `pip-audit -r requirements.txt` (online) for known vulnerabilities - this checker is offline and does not know CVEs.
FILE:scripts/req_check.py
#!/usr/bin/env python3
"""Python requirements pin and conflict checker (Python 3.8+ standard library only, offline).
Reads pip requirements files (following -r includes and -c constraints), and reports what makes
installs non-reproducible, unsatisfiable or risky:
PR01 unpinned requirement PR08 pre-release pin or --pre
PR02 lower bound only (>=, >) PR09 --extra-index-url (dependency confusion)
PR03 conflicting specifiers PR10 plain http:// index/URL, --trusted-host
PR04 duplicate / differently spelt PR11 deprecated, renamed or stdlib-backport package
PR05 VCS/URL without immutable ref PR12 installed version (pip freeze) does not match
PR06 editable or local path PR13 line pip cannot parse
PR07 incomplete hash-checking mode PR14 dev/test tool in runtime requirements
Specifiers follow PEP 440 (==, !=, <=, >=, <, >, ~=, ===, ==X.*), names PEP 503 normalisation,
markers PEP 508 (evaluated with --python / --platform, otherwise each marker set is checked as its
own environment).
Usage:
python3 req_check.py requirements.txt [more.txt ...] [--mode app|lib] [--installed freeze.txt]
[--python 3.11] [--platform linux] [--json]
python3 req_check.py --self-test
Exit codes: 0 = no high findings, 1 = high findings, 2 = file missing or unreadable.
"""
import argparse
import json
import os
import re
import sys
from collections import OrderedDict, defaultdict
# ----------------------------------------------------------------------------- versions (PEP 440)
VERSION_RE = re.compile(r"""^\s*v?(?:(?P<epoch>\d+)!)?(?P<release>\d+(?:\.\d+)*)
(?:[-_.]?(?P<pre_l>a|alpha|b|beta|c|rc|pre|preview)[-_.]?(?P<pre_n>\d*))?
(?:-(?P<post_n1>\d+)|[-_.]?(?P<post_l>post|rev|r)[-_.]?(?P<post_n2>\d*))?
(?:[-_.]?(?P<dev_l>dev)[-_.]?(?P<dev_n>\d*))?
(?:\+(?P<local>[a-z0-9]+(?:[-_.][a-z0-9]+)*))?\s*$""", re.I | re.X)
PRE_RANK = {"a": 0, "alpha": 0, "b": 1, "beta": 1, "c": 2, "rc": 2, "pre": 2, "preview": 2}
INF = float("inf")
class Version:
def __init__(self, s):
m = VERSION_RE.match(s)
if not m:
raise ValueError("invalid version %r" % s)
self.text = s.strip()
self.epoch = int(m.group("epoch") or 0)
self.release = tuple(int(x) for x in m.group("release").split("."))
self.pre = (PRE_RANK[m.group("pre_l").lower()], int(m.group("pre_n") or 0)) if m.group("pre_l") else None
post = m.group("post_n1") or (m.group("post_n2") or "0" if m.group("post_l") else None)
self.post = int(post) if post is not None else None
self.dev = int(m.group("dev_n") or 0) if m.group("dev_l") else None
self.local = m.group("local")
rel = list(self.release)
while len(rel) > 1 and rel[-1] == 0:
rel.pop()
if self.pre is None and self.post is None and self.dev is not None:
pre = (-1, 0)
else:
pre = self.pre if self.pre is not None else (3, 0)
self.key = (self.epoch, tuple(rel), pre, -1 if self.post is None else self.post,
INF if self.dev is None else self.dev)
@property
def is_prerelease(self):
return self.pre is not None or self.dev is not None
def __lt__(self, o):
return self.key < o.key
def __eq__(self, o):
return self.key == o.key
def __hash__(self):
return hash(self.key)
def __repr__(self):
return self.text
SPEC_RE = re.compile(r"^\s*(===|==|!=|<=|>=|~=|<|>)\s*([^\s,;]+)\s*$")
class Spec:
def __init__(self, text):
m = SPEC_RE.match(text)
if not m:
raise ValueError("invalid specifier %r" % text.strip())
self.op, self.ver_text = m.group(1), m.group(2)
self.wild = self.ver_text.endswith(".*")
if self.op == "===":
self.version = None
return
if self.wild and self.op not in ("==", "!="):
raise ValueError("wildcard only allowed with == or != (%r)" % text.strip())
self.version = Version(self.ver_text[:-2] if self.wild else self.ver_text)
if self.op == "~=" and len(self.version.release) < 2:
raise ValueError("~= needs at least two release parts, e.g. ~=2.1 (%r)" % text.strip())
def __repr__(self):
return self.op + self.ver_text
def contains(self, v):
op, s = self.op, self.version
if op == "===":
return v.text == self.ver_text
if self.wild:
n = len(s.release)
rel = tuple(list(v.release) + [0] * max(0, n - len(v.release)))[:n]
hit = v.epoch == s.epoch and rel == s.release
return hit if op == "==" else not hit
vk = v.key
if op == "==":
return vk == s.key and (s.local is None or v.local == s.local)
if op == "!=":
return vk != s.key
if op == ">=":
return vk >= s.key
if op == "<=":
return vk <= s.key
if op == ">":
return vk > s.key and not (v.post is not None and v.release == s.release and s.post is None)
if op == "<":
return vk < s.key and not (v.is_prerelease and not s.is_prerelease and
Version(".".join(map(str, v.release))).key == s.key)
if op == "~=":
prefix = s.release[:-1]
rel = tuple(list(v.release) + [0] * len(s.release))[:len(prefix)]
return vk >= s.key and rel == prefix and v.epoch == s.epoch
return False
def is_prerelease_spec(self):
return self.version is not None and self.version.is_prerelease
def satisfiable(specs):
"""Return an example final version that satisfies all specs, or None."""
if not specs:
return "any"
for s in specs:
if s.op == "===":
try:
v = Version(s.ver_text)
except ValueError:
return s.ver_text
return v.text if all(x.contains(v) for x in specs) else None
cands = set()
for s in specs:
r = list(s.version.release)
e = "%d!" % s.version.epoch if s.version.epoch else ""
base = ".".join(map(str, r))
cands.update([s.version.text, e + base, e + base + ".0.0.1", e + base + ".post1"])
if s.wild or s.op == "~=":
pre = r if s.wild else r[:-1]
cands.update([e + ".".join(map(str, pre + [0])), e + ".".join(map(str, pre + [999]))])
nxt = pre[:-1] + [pre[-1] + 1]
cands.add(e + ".".join(map(str, nxt)))
for i in range(len(r)):
if r[i] > 0:
lower = r[:i] + [r[i] - 1] + [999]
cands.add(e + ".".join(map(str, lower)))
up = r[:i] + [r[i] + 1]
cands.add(e + ".".join(map(str, up)))
cands.update(["0", "0.0.1", "99999"])
allow_pre = any(s.is_prerelease_spec() for s in specs)
found = []
for c in cands:
try:
v = Version(c)
except ValueError:
continue
if v.is_prerelease and not allow_pre:
continue
if all(s.contains(v) for s in specs):
found.append(v)
return min(found).text if found else None
# ----------------------------------------------------------------------------- markers (PEP 508, subset)
MARKER_TOKEN = re.compile(r"\s*(\(|\)|===|==|!=|<=|>=|~=|<|>|not\s+in\b|in\b|and\b|or\b|'[^']*'|\"[^\"]*\"|[A-Za-z_][A-Za-z0-9_.]*)")
def eval_marker(text, env):
toks, pos = [], 0
while pos < len(text):
m = MARKER_TOKEN.match(text, pos)
if not m:
if text[pos:].strip() == "":
break
raise ValueError("cannot parse marker %r" % text)
toks.append(re.sub(r"\s+", " ", m.group(1)))
pos = m.end()
i = [0]
def value():
t = toks[i[0]]
i[0] += 1
if t[0] in "'\"":
return t[1:-1], False
if t not in env:
raise ValueError("unknown marker variable %r" % t)
return env[t], t.startswith("python") or t.endswith("_version")
def comparison():
if toks[i[0]] == "(":
i[0] += 1
r = disj()
i[0] += 1
return r
left, lv = value()
op = toks[i[0]]
i[0] += 1
right, rv = value()
if op in ("in", "not in"):
r = left in right
return r if op == "in" else not r
if lv or rv:
try:
return Spec(op + right).contains(Version(left))
except ValueError:
pass
return {"==": left == right, "!=": left != right, "<": left < right, ">": left > right,
"<=": left <= right, ">=": left >= right}.get(op, False)
def conj():
r = comparison()
while i[0] < len(toks) and toks[i[0]] == "and":
i[0] += 1
r = comparison() and r
return r
def disj():
r = conj()
while i[0] < len(toks) and toks[i[0]] == "or":
i[0] += 1
r = conj() or r
return r
return disj()
def marker_env(python, platform):
plat = (platform or "linux").lower()
sysplat = {"linux": "linux", "windows": "win32", "win32": "win32", "macos": "darwin", "darwin": "darwin"}.get(plat, plat)
system = {"linux": "Linux", "win32": "Windows", "darwin": "Darwin"}.get(sysplat, plat)
pv = ".".join(python.split(".")[:2])
return {"python_version": pv, "python_full_version": python if python.count(".") == 2 else python + ".0",
"sys_platform": sysplat, "platform_system": system, "os_name": "nt" if sysplat == "win32" else "posix",
"platform_machine": "x86_64", "implementation_name": "cpython", "platform_python_implementation": "CPython",
"extra": ""}
# ----------------------------------------------------------------------------- parsing
NAME_RE = re.compile(r"^([A-Za-z0-9][A-Za-z0-9._-]*)\s*(\[[^\]]*\])?\s*(.*)$")
HEX40 = re.compile(r"@[0-9a-f]{40}(?:#|$)")
DEPRECATED = {
"sklearn": ("high", "is a dummy package that now fails to install; use scikit-learn"),
"bs4": ("low", "is a convenience alias; depend on beautifulsoup4 directly"),
"pycrypto": ("high", "is unmaintained since 2013 with known vulnerabilities; use pycryptodome or cryptography"),
"nose": ("medium", "is unmaintained and breaks on Python 3.10+; use pytest"),
"mock": ("low", "is a backport; on Python 3 use unittest.mock"),
"argparse": ("medium", "is in the standard library since 2.7/3.2; remove the backport"),
"typing": ("medium", "is a backport for Python < 3.5 and can shadow the stdlib module; remove it"),
"dataclasses": ("medium", "is a backport for Python 3.6 only; remove it or add ; python_version < \"3.7\""),
"enum34": ("high", "breaks the stdlib enum on Python 3.6+ (e.g. AttributeError in re); remove it"),
"futures": ("medium", "is a Python 2 backport of concurrent.futures; remove it"),
"pathlib": ("medium", "is an old backport that shadows the stdlib pathlib; remove it"),
"distribute": ("medium", "was merged back into setuptools in 2013; use setuptools"),
"pkg-resources": ("high", "is a bogus package generated by a broken Ubuntu pip freeze; remove the line"),
"python-dateutils": ("medium", "is a typo-like name; you probably meant python-dateutil"),
}
DEV_TOOLS = {"pytest", "pytest-cov", "pytest-django", "pytest-mock", "black", "flake8", "pylint", "mypy", "ruff",
"isort", "ipython", "ipdb", "pre-commit", "coverage", "tox", "nox", "factory-boy", "faker", "debugpy",
"django-debug-toolbar", "jupyter", "notebook"}
def show(path):
rel = os.path.relpath(path)
return path if rel.startswith("..") else rel
def norm(name):
return re.sub(r"[-_.]+", "-", name).lower()
class Req:
def __init__(self, **kw):
self.__dict__.update(kw)
def logical_lines(text):
buf, start = "", None
for n, raw in enumerate(text.splitlines(), 1):
line = raw
if start is None:
start = n
if line.rstrip().endswith("\\"):
buf += line.rstrip()[:-1] + " "
continue
buf += line
yield start, buf
buf, start = "", None
if buf:
yield start, buf
def strip_comment(line):
if line.lstrip().startswith("#"):
return ""
return re.split(r"\s+#", line, maxsplit=1)[0].strip()
def parse_files(paths, findings):
reqs, opts, seen = [], defaultdict(list), set()
def add(sev, code, src, msg):
findings.append({"severity": sev, "code": code, "where": src, "message": msg})
def parse(path, constraint):
real = os.path.abspath(path)
if real in seen:
return
seen.add(real)
with open(path, encoding="utf-8") as f:
text = f.read()
base = os.path.dirname(path)
shown = show(path)
for n, line in logical_lines(text):
src = "%s:%d" % (shown, n)
line = strip_comment(line)
if not line:
continue
m = re.match(r"^(-r|--requirement|-c|--constraint)\s*=?\s*(\S+)$", line)
if m:
inc = os.path.join(base, m.group(2))
if not os.path.exists(inc):
add("high", "PR13", src, "included file %s does not exist; pip will fail" % m.group(2))
else:
parse(inc, constraint or m.group(1) in ("-c", "--constraint"))
continue
m = re.match(r"^(-i|--index-url|--extra-index-url|-f|--find-links|--trusted-host)\s*=?\s*(\S+)$", line)
if m:
opt = {"-i": "--index-url", "-f": "--find-links"}.get(m.group(1), m.group(1))
opts[opt].append((src, m.group(2)))
continue
if line in ("--pre", "--require-hashes") or line.startswith(("--no-binary", "--only-binary", "--prefer-binary")):
opts[line.split()[0].split("=")[0]].append((src, line))
continue
editable = False
m = re.match(r"^(-e|--editable)\s+(\S+)(.*)$", line)
if m:
editable, line = True, m.group(2) + m.group(3)
hashes = re.findall(r"--hash[=\s]+(\S+)", line)
line = re.sub(r"--hash[=\s]+\S+", "", line).strip()
if line.startswith("-"):
add("medium", "PR13", src, "unknown option %r ignored by this checker" % line.split()[0])
continue
marker = ""
if ";" in line:
line, marker = [x.strip() for x in line.split(";", 1)]
url, name, extras, specs, bad = None, None, "", [], None
if re.match(r"^(git|hg|svn|bzr)\+|^https?://|^file:|^\.{0,2}/|.*\.(whl|zip|tar\.gz)$", line):
url = line
egg = re.search(r"[#&]egg=([A-Za-z0-9._-]+)", line)
name = egg.group(1) if egg else os.path.basename(line.split("#")[0].rstrip("/")).split("@")[0]
name = re.sub(r"(-\d.*)?(\.git|\.whl|\.zip|\.tar\.gz)$", "", name) or line
else:
m = NAME_RE.match(line)
if not m:
add("high", "PR13", src, "pip cannot parse %r" % line)
continue
name, extras, rest = m.group(1), m.group(2) or "", m.group(3).strip()
if rest.startswith("@"):
url = rest[1:].strip()
elif rest:
rest = rest.strip("()")
for part in [p for p in rest.split(",") if p.strip()]:
p = part.strip()
if re.match(r"^=[^=]", p):
bad = "single '=' in %r (use ==)" % p
break
try:
specs.append(Spec(p))
except ValueError as e:
bad = str(e)
break
if bad:
add("high", "PR13", src, "%s: %s - pip will refuse this line" % (name, bad))
continue
reqs.append(Req(name=name, key=norm(name), extras=extras, specs=specs, marker=marker, url=url,
editable=editable, hashes=hashes, constraint=constraint, src=src))
for p in paths:
parse(p, False)
return reqs, opts
# ----------------------------------------------------------------------------- checks
def check(paths, mode="app", installed=None, python=None, platform=None):
findings = []
reqs, opts = parse_files(paths, findings)
def add(sev, code, src, msg):
findings.append({"severity": sev, "code": code, "where": src, "message": msg})
env = marker_env(python, platform) if python else None
active = []
for r in reqs:
if r.marker and env is not None:
try:
if not eval_marker(r.marker, env):
continue
except (ValueError, IndexError):
add("medium", "PR13", r.src, "cannot evaluate marker %r" % r.marker)
active.append(r)
by = OrderedDict()
for r in active:
by.setdefault(r.key, []).append(r)
# index / transport options
for src, url in opts.get("--extra-index-url", []):
add("high", "PR09", src, "--extra-index-url %s: pip picks the highest version from ANY index, so a public "
"package with the same name as an internal one wins (dependency confusion); use a single --index-url "
"proxy that serves both, or pin with --hash" % url)
for opt in ("--index-url", "--extra-index-url", "--find-links"):
for src, url in opts.get(opt, []):
if url.startswith("http://"):
add("high", "PR10", src, "%s uses plain http (%s); packages can be swapped in transit - use https" % (opt, url))
for src, host in opts.get("--trusted-host", []):
add("medium", "PR10", src, "--trusted-host %s disables TLS verification for that host" % host)
for src, _ in opts.get("--pre", []):
add("medium", "PR08", src, "--pre lets every package resolve to pre-releases; pin the one pre-release you need instead")
hashed = [r for r in active if r.hashes and not r.constraint]
hash_mode = bool(hashed) or bool(opts.get("--require-hashes"))
pinned_elsewhere = {}
for key, rs in by.items():
for r in rs:
if any(s.op in ("==", "===") and not s.wild for s in r.specs):
pinned_elsewhere[key] = r.src
packages = []
for key, rs in by.items():
decl = [r for r in rs if not r.constraint]
all_specs = [s for r in rs for s in r.specs]
groups = defaultdict(list)
for r in rs:
groups[r.marker].append(r)
unmarked = groups.pop("", [])
envs = [("", unmarked)] if not groups else [(m, unmarked + g) for m, g in groups.items()]
status, example = "ok", None
for m, grp in envs:
specs = [s for r in grp for s in r.specs]
if any(r.url for r in grp):
continue
ex = satisfiable(specs)
if ex is None:
status = "conflict"
add("high", "PR03", ", ".join(r.src for r in grp),
"%s: no version satisfies %s%s - pip will fail with ResolutionImpossible"
% (rs[0].name, ", ".join("%s (%s)" % (",".join(map(repr, r.specs)) or "any", r.src) for r in grp if r.specs),
(" when " + m) if m else ""))
elif example is None:
example = ex
spellings = sorted({r.name for r in rs})
per_marker = defaultdict(list)
for r in decl:
per_marker[r.marker].append(r)
dups = [g for g in per_marker.values() if len(g) > 1]
if dups:
g = dups[0]
add("low", "PR04", ", ".join(r.src for r in g), "%s is declared %d times%s; keep one line"
% (rs[0].name, len(g), (" (spelt " + ", ".join(spellings) + ")") if len(spellings) > 1 else ""))
elif len(spellings) > 1:
add("info", "PR04", ", ".join(r.src for r in rs), "%s is spelt %s across files (same package after normalisation)"
% (rs[0].name, ", ".join(spellings)))
for r in decl:
if r.url:
if r.url.startswith(("git+", "hg+", "svn+", "bzr+")):
if not HEX40.search(r.url):
add("high", "PR05", r.src, "%s comes from %s without a full commit hash; the branch or tag can "
"move under you - pin @<40-char commit>" % (r.name, r.url))
elif r.url.startswith(("http://", "https://")):
if r.url.startswith("http://"):
add("high", "PR10", r.src, "%s is downloaded over plain http" % r.name)
if not r.hashes and "#sha256=" not in r.url:
add("medium", "PR05", r.src, "%s is an archive URL without a hash; add #sha256=... or --hash" % r.name)
if r.editable or r.url.startswith((".", "/", "file:")):
add("medium" if mode == "app" else "low", "PR06", r.src, "%s is %s; it will not exist on a clean build "
"machine - publish it or build a wheel" % (r.name, "an editable install" if r.editable else "a local path"))
continue
if r.editable:
add("medium", "PR06", r.src, "%s is installed editable" % r.name)
ops = {s.op for s in r.specs}
if not r.specs and key not in pinned_elsewhere:
add("high" if mode == "app" else "info", "PR01", r.src, "%s is unpinned%s" % (r.name,
"; every install can pick a new major version - pin == or add it to a lock/constraints file"
if mode == "app" else " (fine for a library; set a tested lower bound)"))
elif ops and ops <= {">=", ">", "!="} and key not in pinned_elsewhere and mode == "app":
add("medium", "PR02", r.src, "%s %s has no upper bound and no pin; a future major release installs "
"automatically" % (r.name, ",".join(map(repr, r.specs))))
if mode == "lib" and any(s.op in ("==", "===") for s in r.specs):
add("medium", "PR02", r.src, "%s is pinned == in a library; this forces the exact version on every "
"project that depends on you - use a range" % r.name)
for s in r.specs:
if s.version is not None and s.version.is_prerelease and s.op in ("==", ">=", "~="):
add("medium", "PR08", r.src, "%s %r is a pre-release" % (r.name, s))
if hash_mode and not r.hashes:
add("high", "PR07", r.src, "%s has no --hash but other lines do; pip switches to hash-checking mode "
"for the whole file and will refuse to install it" % r.name)
if hash_mode and r.hashes and not any(s.op == "==" and not s.wild for s in r.specs):
add("high", "PR07", r.src, "%s has hashes but is not pinned with ==; hash-checking mode requires "
"exact pins" % r.name)
if key in DEV_TOOLS and mode == "app" and decl and not re.search(r"dev|test|lint|ci|docs",
decl[0].src.split(":")[0], re.I):
add("low", "PR14", decl[0].src, "%s is a development/test tool in a runtime requirements file; move it "
"to requirements-dev.txt so production images stay small" % decl[0].name)
if key in DEPRECATED and decl:
sev, why = DEPRECATED[key]
add(sev, "PR11", decl[0].src, "%s %s" % (decl[0].name, why))
packages.append({"name": rs[0].name, "key": key,
"specs": ",".join(sorted({repr(s) for s in all_specs})) or ("url" if any(r.url for r in rs) else "any"),
"sources": [r.src + (" (constraint)" if r.constraint else "") for r in rs],
"marker": "; ".join(sorted({r.marker for r in rs if r.marker})),
"status": status, "example": example})
if installed:
inst = {}
with open(installed, encoding="utf-8") as f:
for n, line in enumerate(f, 1):
m = re.match(r"^\s*([A-Za-z0-9][A-Za-z0-9._-]*)\s*==\s*([^\s;#]+)", line)
if m:
inst[norm(m.group(1))] = (m.group(2), "%s:%d" % (show(installed), n))
for p in packages:
if all(r.constraint for r in by[p["key"]]):
continue # constraints never install anything
specs = [s for r in by[p["key"]] for s in r.specs]
if p["key"] not in inst:
if not any(r.marker for r in by[p["key"]]):
add("medium", "PR12", next(r.src for r in by[p["key"]] if not r.constraint), "%s is required but not in %s" % (p["name"], os.path.basename(installed)))
p["installed"] = None
continue
ver, src = inst[p["key"]]
p["installed"] = ver
try:
v = Version(ver)
except ValueError:
continue
bad = [s for s in specs if not s.contains(v)]
if bad:
add("high", "PR12", src, "%s %s is installed but the requirements say %s; the environment does not "
"match what CI builds" % (p["name"], ver, ",".join(map(repr, bad))))
order = {"high": 0, "medium": 1, "low": 2, "info": 3}
findings.sort(key=lambda f: (order[f["severity"]], f["code"], f["where"]))
counts = {k: sum(f["severity"] == k for f in findings) for k in order}
pinned = sum(1 for p in packages if re.search(r"(^|,)===?[^=,*]+(,|$)", p["specs"]) and "*" not in p["specs"])
return {"files": [show(p) for p in paths], "mode": mode, "python": python, "packages": packages,
"pinned": pinned, "findings": findings, "counts": counts, "hash_mode": hash_mode}
# ----------------------------------------------------------------------------- report
def report(r):
L = ["# Requirements check: %s" % ", ".join(r["files"]), ""]
L.append("Mode: %s%s. Packages: %d (%d pinned with ==). Findings: %d high, %d medium, %d low, %d info." % (
r["mode"], (", Python " + r["python"]) if r["python"] else "", len(r["packages"]), r["pinned"],
r["counts"]["high"], r["counts"]["medium"], r["counts"]["low"], r["counts"]["info"]))
L += ["", "**Verdict: %s**" % ("install will fail or is unsafe - fix the high findings first" if any(
f["code"] in ("PR03", "PR07", "PR13") and f["severity"] == "high" for f in r["findings"]) else
"not reproducible or risky - fix the high findings" if r["counts"]["high"] else
"reproducible and resolvable" if not r["counts"]["medium"] else "resolvable, with medium findings to tidy"), ""]
L += ["## Packages", ""]
has_inst = any("installed" in p for p in r["packages"])
L.append("| Package | Specifiers (all files) | Marker | Declared in | Status%s |" % (" | Installed" if has_inst else ""))
L.append("|---|---|---|---|---|" + ("---|" if has_inst else ""))
for p in r["packages"]:
L.append("| %s | `%s` | %s | %s | %s%s |" % (p["name"], p["specs"], ("`%s`" % p["marker"]) if p["marker"] else "-",
", ".join(p["sources"]), p["status"],
(" | %s" % (p.get("installed") or "-")) if has_inst else ""))
L += ["", "## Findings", ""]
if not r["findings"]:
L.append("- none")
for f in r["findings"]:
L.append("- **%s %s** `%s` %s" % (f["severity"].upper(), f["code"], f["where"], f["message"]))
L += ["", "## Next steps", "",
"1. Fix every PR03/PR07/PR13 line - these stop `pip install` outright.",
"2. Keep loose, intent-level ranges in `requirements.in` (or pyproject) and generate a fully pinned, hashed lock "
"with `pip-compile --generate-hashes` or `uv pip compile --generate-hashes`; install with `pip install --require-hashes -r requirements.txt`.",
"3. Use one `--index-url` (a proxy that mirrors PyPI and hosts internal packages), https only.",
"4. Run `pip-audit -r requirements.txt` (online) for known vulnerabilities - this checker is offline and does not know CVEs."]
return "\n".join(L) + "\n"
# ----------------------------------------------------------------------------- self-test
def self_test():
import tempfile
cases = []
def ok(name, cond, detail=""):
cases.append((name, bool(cond), detail))
V = Version
ok("version ordering dev < a < rc < final < post", V("1.0.dev1") < V("1.0a1") < V("1.0rc1") < V("1.0") < V("1.0.post1"))
ok("trailing zeros equal", V("1.0") == V("1.0.0"))
ok("~=1.4.2 means >=1.4.2,<1.5", Spec("~=1.4.2").contains(V("1.4.9")) and not Spec("~=1.4.2").contains(V("1.5")))
ok("~=2.1 allows 2.9 not 3.0", Spec("~=2.1").contains(V("2.9")) and not Spec("~=2.1").contains(V("3.0")))
ok("==1.4.* wildcard", Spec("==1.4.*").contains(V("1.4.7")) and not Spec("==1.4.*").contains(V("1.5")))
ok("<2.0 excludes 2.0rc1", not Spec("<2.0").contains(V("2.0rc1")))
ok("satisfiable range", satisfiable([Spec(">=1.2"), Spec("<2")]) is not None)
ok("conflict detected", satisfiable([Spec(">=2.0"), Spec("<1.9")]) is None)
ok("pin outside range conflicts", satisfiable([Spec("==1.26.4"), Spec(">=2")]) is None)
ok("!= exclusion of only pin", satisfiable([Spec("==3.1"), Spec("!=3.1")]) is None)
ok("~= vs upper bound conflict", satisfiable([Spec("~=2.31"), Spec("<2.30")]) is None)
env = marker_env("3.8", "linux")
ok("marker eval python_version", eval_marker('python_version < "3.9"', env) and
not eval_marker('python_version >= "3.9" and sys_platform == "linux"', env))
ok("marker eval or/parentheses", eval_marker('(sys_platform == "win32" or os_name == "posix")', env))
try:
Spec("~=2")
ok("~=2 rejected", False)
except ValueError:
ok("~=2 rejected", True)
d = tempfile.mkdtemp()
with open(os.path.join(d, "c.txt"), "w") as f:
f.write("urllib3<2\nnumpy==1.26.4\n")
with open(os.path.join(d, "r.txt"), "w") as f:
f.write("-c c.txt\n--extra-index-url https://pypi.internal.example/simple\nrequests==2.32.3\nurllib3>=2.2\n"
"numpy<2 ; python_version < \"3.9\"\nnumpy>=2.1 ; python_version >= \"3.9\"\nflask\n"
"Django>=4.2\ndjango<5\n-e git+https://github.com/acme/tools.git@main#egg=acme-tools\n"
"sklearn\npandas=2.2.0\npytest==8.3.3\n")
r = check([os.path.join(d, "r.txt")])
codes = defaultdict(list)
for fnd in r["findings"]:
codes[fnd["code"]].append(fnd)
ok("PR03 urllib3 conflict with constraint", any("urllib3" in x["message"] for x in codes["PR03"]), codes["PR03"])
ok("PR03 numpy conflict only for python>=3.9 marker", any("numpy" in x["message"] and "3.9" in x["message"]
for x in codes["PR03"]), codes["PR03"])
ok("PR01 flask unpinned high", any("flask" in x["message"] and x["severity"] == "high" for x in codes["PR01"]))
ok("PR04 Django/django duplicate", any("Django" in x["message"] for x in codes["PR04"]))
ok("PR05 git branch ref", any("acme-tools" in x["message"] for x in codes["PR05"]), codes["PR05"])
ok("PR09 extra index", bool(codes["PR09"]))
ok("PR11 sklearn", any("scikit-learn" in x["message"] for x in codes["PR11"]))
ok("PR14 pytest in runtime file", any("pytest" in x["message"] for x in codes["PR14"]))
ok("PR13 single =", any("pandas" in x["message"] for x in codes["PR13"]))
r38 = check([os.path.join(d, "r.txt")], python="3.8")
ok("--python 3.8 drops the >=3.9 numpy line", not any("numpy" in x["message"] for x in r38["findings"] if x["code"] == "PR03"))
with open(os.path.join(d, "h.txt"), "w") as f:
f.write("six==1.16.0 --hash=sha256:" + "a" * 64 + "\nidna>=3 --hash=sha256:" + "b" * 64 + "\ncertifi==2024.8.30\n")
rh = check([os.path.join(d, "h.txt")])
ok("PR07 partial hashes", sum(x["code"] == "PR07" for x in rh["findings"]) == 2, rh["findings"])
with open(os.path.join(d, "freeze.txt"), "w") as f:
f.write("six==1.15.0\n")
rf = check([os.path.join(d, "h.txt")], installed=os.path.join(d, "freeze.txt"))
ok("PR12 installed mismatch", any(x["code"] == "PR12" and "six" in x["message"] for x in rf["findings"]))
rl = check([os.path.join(d, "h.txt")], mode="lib")
ok("lib mode flags == pin", any(x["code"] == "PR02" and "library" in x["message"] for x in rl["findings"]))
ok("report renders", "Verdict" in report(r) and "| Package |" in report(r))
for name, good, detail in cases:
print("%s %s%s" % ("PASS" if good else "FAIL", name, "" if good else " -> %r" % (detail,)))
passed = sum(1 for _, g, _ in cases if g)
print("%d/%d passed" % (passed, len(cases)))
return 0 if passed == len(cases) else 1
def main(argv=None):
ap = argparse.ArgumentParser(description="Check pip requirements files for pins, conflicts and risky sources.")
ap.add_argument("files", nargs="*")
ap.add_argument("--mode", choices=("app", "lib"), default="app",
help="app = deployable service (pins expected), lib = package others install (ranges expected)")
ap.add_argument("--installed", help="pip freeze output to compare against")
ap.add_argument("--python", help="evaluate markers for this Python version, e.g. 3.11")
ap.add_argument("--platform", help="linux | windows | macos (with --python)")
ap.add_argument("--json", action="store_true")
ap.add_argument("--self-test", action="store_true")
a = ap.parse_args(argv)
if a.self_test:
return self_test()
if not a.files:
ap.print_usage(sys.stderr)
return 2
try:
r = check(a.files, a.mode, a.installed, a.python, a.platform)
except (OSError, UnicodeDecodeError) as e:
print("error: %s" % e, file=sys.stderr)
return 2
print(json.dumps(r, indent=2) if a.json else report(r), end="\n" if a.json else "")
return 1 if r["counts"]["high"] else 0
if __name__ == "__main__":
sys.exit(main())Simulates API rate limits before you ship them - token bucket, fixed window, sliding log and sliding counter, per client, endpoint, tier or global - against generated or recorded traffic with retrying clients. Shows who gets 429s and for how long, fixed-window boundary bursts, retry herds, noisy neighbours, abusive clients that still get through and peaks over backend capacity. Includes an offline stdlib Python simulator with --self-test.
---
name: api-rate-limit-policy-simulator
description: Simulates API rate limits before you ship them - token bucket, fixed window, sliding log and sliding counter, per client, endpoint, tier or global - against generated or recorded traffic with retrying clients. Shows who gets 429s and for how long, fixed-window boundary bursts, retry herds, noisy neighbours, abusive clients that still get through and peaks over backend capacity. Includes an offline stdlib Python simulator with --self-test.
---
# API Rate Limit Policy Simulator
You help backend and platform engineers choose rate limits with evidence instead of round numbers. A limit that looks safe on paper ("300 per minute per key") can still let 1.8x that through at a window boundary, throttle a mobile app whose thousands of users share one API key, release a herd of retries at the reset second, and do nothing against a scraper that stays just under it. You describe the policies and the traffic in a small JSON file, replay it, and read the result client by client and policy by policy.
## Files in this skill
- `scripts/rate_sim.py` - simulates token bucket, fixed window, sliding log and sliding counter limiters keyed by client, endpoint, client+endpoint or global, scoped to endpoints and client tiers; generates steady, Poisson or burst traffic (or replays a CSV trace), lets clients retry immediately, with backoff or by honouring Retry-After, and reports 429s, delays, failures, worst-case and observed window counts, peaks vs capacity, findings RL01-RL10 and a sample 429 response (Python 3.8+ standard library only)
- `references/algorithms.md` - how each algorithm behaves, its worst case, memory cost and when to pick it
- `references/policy-design.md` - choosing keys, tiers, numbers, headers and client retry rules; what the finding codes mean
- `templates/rate-limit-review.md` - the review to hand back
- `examples/orders-api.json` - a public orders API with a per-key fixed window, a login limit and a global fixed window; five clients including a shared mobile key, a bulk partner, a scraper and a credential stuffer
- `examples/orders-api-report.md` - the real simulator output on that file
- `examples/orders-api-tuned.json` - the same API after the fixes: per-tier token buckets, a global token bucket sized to capacity
## Workflow
### 1. Collect the inputs
Ask for: the limits as configured today or as proposed (algorithm, key, numbers, which endpoints and which clients), where they are enforced (gateway, CDN, app, Redis), the backend capacity in requests per second it can sustain, and the traffic: the biggest legitimate clients and their shape (steady, bursty batch jobs, many users behind one key or one NAT IP), how each one retries, and any abuse seen in logs. Real access logs beat guesses: export `t,client,endpoint` for a busy 5-15 minutes and pass it with `--trace`.
### 2. Run the simulator
```
python3 scripts/rate_sim.py examples/orders-api.json
python3 scripts/rate_sim.py examples/orders-api-tuned.json
python3 scripts/rate_sim.py policies.json --trace access-sample.csv
python3 scripts/rate_sim.py policies.json --json
python3 scripts/rate_sim.py --self-test
```
Exit 0 = no high findings, 1 = high findings (legitimate clients throttled or admitted traffic over capacity), 2 = bad input. Without a shell, reason with `references/algorithms.md`: per key, compare the client's peak per window with the limit, the worst case (2x limit for fixed windows, burst + rate for buckets) with capacity, and think through what each client does after a 429.
### 3. Read the result
- Legit clients first (RL01): any failed or delayed requests, and how long they waited. One key shared by an app's users needs its own tier or a per-user key.
- Capacity (RL03): the peak admitted second vs what the backend sustains. A global limit is the only thing that protects capacity; per-key limits multiply by the number of keys.
- Shape (RL04, RL10): fixed windows let bursts through at boundaries and hand out a whole window at once; retries without jitter all come back in the same second.
- Abuse (RL02): a scraper under the per-key limit is not stopped by limits; it needs detection, auth or a per-endpoint limit.
- Retries (RL05): attempts per logical request above 1.5 means the clients amplify load when throttled.
### 4. Tune and re-run
Change one thing at a time and re-run with the same traffic: switch fixed windows to token buckets or sliding counters, add tiers with `clients` / `exclude_clients`, size the global bucket to capacity (rate = sustainable rps, burst = what the backend absorbs for a second), add endpoint limits for login and expensive calls, and agree pacing with batch partners. Use `references/policy-design.md` for the numbers.
### 5. Deliver
Fill `templates/rate-limit-review.md`: verdict, a before/after table per client, the policy changes as config, the 429 response and headers, client guidance (Retry-After, backoff with jitter, max retries) and what to monitor after rollout.
## Example result
On `examples/orders-api.json` the shared `mobile-app` key hits the 300/minute per-key window: 1,042 of its 1,839 requests succeed only after waiting up to 60 s (RL01, high). Honouring Retry-After without jitter releases 289 retries in second 240, so admitted traffic peaks at 313 req/s against a capacity of 80 (RL03, RL10). The per-key fixed window lets 544 requests through in one rolling minute (1.8x, RL04), and the scraper still gets 45% of its calls through while its immediate retries push it to 2.67 attempts per request. The login limit stops the credential stuffer (40 of 480). In `examples/orders-api-tuned.json` (tier token buckets, global bucket 50 req/s + 30 burst, partner pacing at 2 req/s) legit clients see no 429s, the peak is 27 req/s and only the scraper findings remain.
## Rules
- Simulate with the traffic you actually have (or a trace), not an average; bursts and retries are where limits fail.
- Never call a limit safe for capacity unless a global limit bounds the total.
- Treat delayed requests of legitimate clients as harm, not as success.
- Always return 429 with Retry-After, add jitter, and tell clients how to back off.
- Rate limits are not abuse protection on their own; say when authentication, bot detection or WAF rules are needed.
- Results are a model: confirm with gateway metrics after rollout and keep the limits in config, not code.
FILE:references/algorithms.md
# Rate limit algorithms
| Algorithm | Admits | Worst case in one window | Memory per key | Good for |
|---|---|---|---|---|
| Token bucket (GCRA) | while tokens remain; refills at `rate`/s up to `burst` | burst + rate x window | 2 numbers | most APIs: smooth average, bounded bursts |
| Fixed window | `limit` per calendar window, counter resets | 2 x limit (end of one window + start of next) | 1 counter | quotas and billing ("10,000 per day"), not capacity |
| Sliding log | at most `limit` in any rolling window | limit (exact) | one timestamp per request | small limits: login, OTP, password reset |
| Sliding counter | previous window weighted by overlap + current | about limit (assumes even spread) | 2 counters | large limits where exactness is not needed |
## Token bucket
- `rate` is the long-run average; `burst` is how many calls can arrive at once after a quiet period.
- `limit` + `window_s` without a burst means rate = limit/window and burst = limit: a client can spend the whole minute in one second. Set the burst explicitly.
- Retry-After = (cost - tokens) / rate: usually well under a second, so retries spread out by themselves.
- GCRA (used by many gateways) stores one timestamp and gives the same admit/reject decisions.
## Fixed window
- Cheap (`INCR` + `EXPIRE` in Redis) and easy to explain, so it is everywhere.
- A client can send `limit` in the last second of a window and `limit` again in the first second of the next.
- Every throttled client gets Retry-After = time to the reset, so they all come back in the same second (retry herd).
- Use it for quotas that match business periods; put a token bucket in front of it for capacity.
## Sliding log
- Exact, but stores every admitted timestamp for the window: 10,000/hour per key = 10,000 entries per key.
- Retry-After = oldest relevant entry + window - now: can be long; tell the client.
## Sliding counter
- Estimate = previous count x (1 - elapsed/window) + current count.
- Error comes from uneven traffic: a burst at the end of the previous window is under-counted later (in the simulator's self-test, 10 requests at the very end of one window plus an even trickle later admit 16 in one rolling minute for a limit of 10).
- Good default for large per-key limits behind a token bucket.
## Concurrency limits (not simulated)
Limits on in-flight requests protect slow endpoints better than rate limits; combine with a rate limit for fairness.
FILE:references/policy-design.md
# Designing rate limit policies
## Pick the key
- **API key / token** for authenticated traffic. Watch for keys shared by many users (mobile apps, SDKs embedded in customer sites): give them a tier or move to per-user keys.
- **User or account id** behind the key when the API knows it; fairer than the key.
- **IP** only for anonymous endpoints; offices, schools, carriers and VPNs put thousands of users behind one IP (CGNAT). Use generous IP limits and stricter account limits.
- **Endpoint** for expensive calls (search, export, bulk) and sensitive ones (login, OTP, password reset).
- **Global** to protect capacity. Without it, per-key limits x number of keys is the real ceiling.
## Pick the numbers
1. Sustained rate per key: p99 of a normal busy client's requests per second x 2-3 headroom.
2. Burst: the largest legitimate batch (page load fan-out, sync job) that must not be throttled.
3. Global: rate = what the backend sustains at acceptable latency (load test), burst = what it absorbs for about a second.
4. Login/OTP: 5-10 per minute per account and per IP, sliding log, plus lockout or CAPTCHA after repeated failures.
5. Quotas (per day/month) are a product decision; keep them separate from capacity limits.
## Tiers
Scope policies with `clients` and `exclude_clients` in the simulator; in production map tiers to plans or key metadata (free, paid, partner, internal). Internal services should usually bypass public limits but stay under the global one.
## Responses and headers
- 429 Too Many Requests, `Retry-After` in seconds, a problem+json body naming the limit.
- Advertise the policy: the IETF httpapi ratelimit-headers draft defines `RateLimit-Policy` and `RateLimit` fields (field syntax changed between drafts); many gateways still send `X-RateLimit-Limit`, `-Remaining`, `-Reset`. Pick one and document it.
- Add 0-20% random jitter to Retry-After for fixed windows so throttled clients do not return together.
- Use 503 + Retry-After for overload (global limit), 429 for per-client limits, so clients can tell them apart.
## Client rules to publish
Honour Retry-After; otherwise exponential backoff with full jitter (1, 2, 4 s ...), at most 3-5 retries, never retry non-idempotent calls without an idempotency key; pace batch jobs instead of bursting.
## Finding codes (rate_sim.py)
| Code | Meaning |
|---|---|
| RL01 | legit client failed or delayed (high above 1% of its requests) |
| RL02 | abusive client still gets more than 25% through |
| RL03 | admitted peak over capacity, or no global limit to bound it |
| RL04 | fixed window let more than the limit through in a rolling window |
| RL05 | client retries amplify attempts 1.5x or more |
| RL06 | noisy neighbour holds most of a global limit while legit clients are rejected |
| RL07 | sliding log with a very large limit (memory) |
| RL08 | reminder: 429 + Retry-After and documented limits |
| RL09 | policy never triggers and is 10x above the busiest key |
| RL10 | retry herd: most of the peak second are retries released together |
FILE:templates/rate-limit-review.md
# Rate limit review: <service>
**Scope:** <endpoints / gateway> **Capacity:** <80 req/s sustained> **Traffic used:** <generated from ... / trace of 10 min on ...>
**Verdict:** <ship / ship with changes / do not ship>
## What happens today
| Client | Kind | Requests | 429s | OK after retry | Failed | Max wait | Main policy |
|---|---|---|---|---|---|---|---|
| <mobile-app> | <legit> | <1,839> | <1,081> | <1,042> | <0> | <60 s> | <per-key minute> |
Peak admitted: <313 req/s in second 240> vs capacity <80>.
## Findings
1. <HIGH RL01 - shared mobile key throttled; 57% of requests delayed up to 60 s>
2. <HIGH RL03 / MEDIUM RL10 - retry herd at window reset, 4x capacity>
3. <MEDIUM RL04 - fixed window 1.8x at boundary>
4. <MEDIUM RL02 - scraper under the per-key limit>
## Proposed policies
```json
<policies block from the tuned config>
```
## After the change (same traffic)
| Client | 429s before | 429s after | Failed before | Failed after |
|---|---|---|---|---|
| <...> | | | | |
Peak admitted after: <27 req/s>.
## Response contract
- 429 + Retry-After (+ jitter) for per-client limits; 503 + Retry-After for global overload
- Headers: <RateLimit-Policy / RateLimit or X-RateLimit-*>
- Client guidance: <honour Retry-After, backoff with jitter, max 3 retries, pace batch jobs>
## Outside rate limits
<bot detection / auth / WAF rule for the scraper; lockout or CAPTCHA for login>
## Monitor after rollout
<429 rate per client and policy, top throttled keys, p99 latency, admitted rps vs capacity; review in 2 weeks>
FILE:examples/orders-api.json
{
"service": "Orders API (public v2)",
"duration_s": 300,
"capacity_rps": 80,
"seed": 7,
"policies": [
{"name": "per-key minute", "algorithm": "fixed_window", "key": "client", "limit": 300, "window_s": 60},
{"name": "login", "algorithm": "sliding_log", "key": "client", "limit": 10, "window_s": 60,
"endpoints": ["/v2/login"]},
{"name": "global", "algorithm": "fixed_window", "key": "global", "limit": 3600, "window_s": 60}
],
"clients": [
{"name": "mobile-app", "kind": "legit", "pattern": "poisson", "rps": 6, "endpoint": "/v2/orders",
"retry": "honor"},
{"name": "partner-erp", "kind": "legit", "pattern": "burst", "burst": 120, "every_s": 60, "start": 58,
"endpoint": "/v2/orders/bulk", "retry": "backoff"},
{"name": "checkout-web", "kind": "legit", "pattern": "poisson", "rps": 3, "endpoint": "/v2/checkout",
"retry": "honor"},
{"name": "price-scraper", "kind": "abusive", "pattern": "steady", "rps": 12, "start": 20,
"endpoint": "/v2/products", "retry": "immediate"},
{"name": "cred-stuffer", "kind": "abusive", "pattern": "steady", "rps": 2, "start": 60,
"endpoint": "/v2/login"}
]
}
FILE:examples/orders-api-report.md
# Rate limit simulation: Orders API (public v2)
Simulated 300s, 7227 logical requests, 4930 admitted, peak 313 req/s admitted (second 240) vs capacity 80.
**Verdict: 2 high finding(s) - fix before shipping these limits**
## Clients
| Client | Kind | Requests | Attempts | Allowed | 429s | OK after retry | Failed | Failed % | Max Retry-After | Top policy |
|---|---|---|---|---|---|---|---|---|---|---|
| price-scraper | abusive | 3360 | 8961 | 1503 | 7458 | 15 | 1857 | 55.3% | 35.2s | per-key minute |
| mobile-app | legit | 1839 | 2920 | 1839 | 1081 | 1042 | 0 | 0.0% | 59.9s | per-key minute |
| checkout-web | legit | 948 | 948 | 948 | 0 | 0 | 0 | 0.0% | 0s | - |
| partner-erp | legit | 600 | 600 | 600 | 0 | 0 | 0 | 0.0% | 0s | - |
| cred-stuffer | abusive | 480 | 480 | 40 | 440 | 0 | 440 | 91.7% | 55s | login |
## Policies
| Policy | Algorithm | Key | Scope | Sustained | Worst case / window | Busiest key / window | Checks | Rejects |
|---|---|---|---|---|---|---|---|---|
| per-key minute | fixed_window | client | all | 5 req/s | 600 per 60s | 544 | 13909 | 8539 |
| login | sliding_log | client | /v2/login | 0.167 req/s | 10 per 60s | 10 | 480 | 440 |
| global | fixed_window | global | all | 60 req/s | 7200 per 60s | 1235 | 13909 | 0 |
## Findings
- **HIGH RL01** Legit client mobile-app is throttled on 56.7% of its requests (0 failed, 1042 only after retrying, waits up to 59.9s), mostly by policy 'per-key minute'; raise that limit or give this client its own tier.
- **HIGH RL03** Admitted traffic peaks at 313 req/s (second 240) over the backend capacity of 80 req/s; add or lower a global limit.
- **MEDIUM RL02** Abusive client price-scraper still gets 45% of its requests through (1503); tighten the per-client limit or add a stricter policy for its pattern.
- **MEDIUM RL04** Fixed window 'per-key minute' let 544 requests through in one rolling 60s window (limit 300, 1.8x) across a window boundary; use sliding_counter or a token bucket if the limit protects capacity.
- **MEDIUM RL05** Client price-scraper sends 2.67x attempts per logical request because it retries throttled calls; retries count against the limit and add load - require Retry-After or backoff with jitter.
- **MEDIUM RL05** Client mobile-app sends 1.59x attempts per logical request because it retries throttled calls; retries count against the limit and add load - require Retry-After or backoff with jitter.
- **MEDIUM RL10** Retry herd: 289 of the 313 requests admitted in second 240 are retries released by the same Retry-After / window reset; add jitter to Retry-After (or client backoff) and prefer a token bucket or sliding window so capacity is not handed out all at once.
- **INFO RL08** Return 429 with Retry-After on every rejection (see the sample headers) and document the limits so clients can pace themselves.
## Sample 429 (first rejection, t=45.0s, price-scraper /v2/products)
```
HTTP/1.1 429 Too Many Requests
Retry-After: 15
RateLimit-Policy: "per-key minute";q=300;w=60
RateLimit: "per-key minute";r=0;t=15
Content-Type: application/problem+json
```
Header names follow the IETF httpapi ratelimit-headers draft; older gateways use X-RateLimit-Limit / -Remaining / -Reset instead.
FILE:examples/orders-api-tuned.json
{
"service": "Orders API (public v2, tuned)",
"notes": "Same traffic as orders-api.json except partner-erp, which agreed to pace its bulk sync at 2 req/s instead of 120-request bursts. Token buckets per tier, a global token bucket sized to capacity.",
"duration_s": 300,
"capacity_rps": 80,
"seed": 7,
"policies": [
{"name": "default key", "algorithm": "token_bucket", "key": "client", "rate": 4, "burst": 40,
"exclude_clients": ["mobile-app", "partner-erp"]},
{"name": "mobile tier", "algorithm": "token_bucket", "key": "client", "rate": 15, "burst": 60,
"clients": ["mobile-app"]},
{"name": "partner tier", "algorithm": "token_bucket", "key": "client", "rate": 3, "burst": 20,
"clients": ["partner-erp"]},
{"name": "login", "algorithm": "sliding_log", "key": "client", "limit": 10, "window_s": 60,
"endpoints": ["/v2/login"]},
{"name": "global", "algorithm": "token_bucket", "key": "global", "rate": 50, "burst": 30}
],
"clients": [
{"name": "mobile-app", "kind": "legit", "pattern": "poisson", "rps": 6, "endpoint": "/v2/orders",
"retry": "honor"},
{"name": "partner-erp", "kind": "legit", "pattern": "steady", "rps": 2, "start": 58,
"endpoint": "/v2/orders/bulk", "retry": "backoff"},
{"name": "checkout-web", "kind": "legit", "pattern": "poisson", "rps": 3, "endpoint": "/v2/checkout",
"retry": "honor"},
{"name": "price-scraper", "kind": "abusive", "pattern": "steady", "rps": 12, "start": 20,
"endpoint": "/v2/products", "retry": "immediate"},
{"name": "cred-stuffer", "kind": "abusive", "pattern": "steady", "rps": 2, "start": 60,
"endpoint": "/v2/login"}
]
}
FILE:scripts/rate_sim.py
#!/usr/bin/env python3
"""API rate limit policy simulator (Python 3.8+ standard library only).
Replays generated or recorded traffic against one or more rate limit policies and shows who
gets throttled, why, for how long, and whether the policies protect the backend without
hurting legitimate clients.
Algorithms (per policy):
token_bucket rate (tokens/s) + burst (bucket size); or limit + window_s (rate = limit/window_s,
burst defaults to limit). GCRA behaves the same for admit/reject.
fixed_window limit requests per window_s, counter resets on window boundaries
(allows up to 2 x limit across a boundary)
sliding_log exact: at most limit requests in any window_s (stores one timestamp per request)
sliding_counter approximation: previous window weighted by overlap + current window (Cloudflare /
Redis style, about limit per rolling window, a few % error)
Config JSON:
{
"service": "Orders API", "duration_s": 300, "capacity_rps": 60, "seed": 7,
"policies": [
{"name": "per-client burst", "algorithm": "token_bucket", "key": "client", "rate": 5, "burst": 20},
{"name": "per-client minute", "algorithm": "fixed_window", "key": "client", "limit": 200, "window_s": 60},
{"name": "login", "algorithm": "sliding_log", "key": "client", "limit": 5, "window_s": 60,
"endpoints": ["/login"]},
{"name": "global", "algorithm": "sliding_counter", "key": "global", "limit": 3000, "window_s": 60}
],
"clients": [
{"name": "mobile-app", "kind": "legit", "pattern": "steady", "rps": 2, "endpoint": "/orders"},
{"name": "nightly-sync", "kind": "legit", "pattern": "burst", "burst": 40, "every_s": 60, "start": 5},
{"name": "scraper", "kind": "abusive", "pattern": "poisson", "rps": 25, "retry": "immediate"}
]
}
key client | endpoint | client+endpoint | global
endpoints optional list: the policy applies only to these endpoints (prefix match)
clients / exclude_clients optional lists: apply only to / skip these clients (tiers, allow lists)
pattern steady (rps) | poisson (rps, seeded) | burst (burst requests every every_s seconds)
start, duration seconds (default 0 and duration_s); cost = tokens per request (default 1)
retry none (default) | immediate (0.1 s later) | backoff (1, 2, 4 s) | honor (Retry-After);
max_retries default 3
kind legit | abusive | unknown - used to judge false positives and leakage
Recorded traffic instead of generated: --trace trace.csv with columns t,client[,endpoint][,cost]
(t in seconds from start). Clients in the config then only add kind/retry information.
Usage:
python3 rate_sim.py config.json [--trace trace.csv] [--json]
python3 rate_sim.py --self-test
Exit codes: 0 = no high findings, 1 = high findings (legit clients throttled or backend over
capacity), 2 = bad input.
"""
import argparse
import csv
import heapq
import json
import math
import random
import sys
from collections import defaultdict, deque
ALGOS = ("token_bucket", "fixed_window", "sliding_log", "sliding_counter")
KEYS = ("client", "endpoint", "client+endpoint", "global")
class InputError(ValueError):
pass
# ----------------------------------------------------------------------------- limiters
class TokenBucket:
def __init__(self, rate, burst):
self.rate, self.burst = float(rate), float(burst)
self.state = {}
def check(self, key, t, cost):
tokens, last = self.state.get(key, (self.burst, t))
tokens = min(self.burst, tokens + (t - last) * self.rate)
self.state[key] = (tokens, t)
if tokens + 1e-9 >= cost:
return True, 0.0, tokens
return False, (cost - tokens) / self.rate, tokens
def consume(self, key, t, cost):
tokens, last = self.state[key]
self.state[key] = (tokens - cost, t)
class FixedWindow:
def __init__(self, limit, window):
self.limit, self.window = float(limit), float(window)
self.state = {}
def check(self, key, t, cost):
w = math.floor(t / self.window)
start, count = self.state.get(key, (w, 0.0))
if start != w:
start, count = w, 0.0
self.state[key] = (start, count)
if count + cost <= self.limit + 1e-9:
return True, 0.0, self.limit - count
return False, (w + 1) * self.window - t, self.limit - count
def consume(self, key, t, cost):
start, count = self.state[key]
self.state[key] = (start, count + cost)
class SlidingLog:
def __init__(self, limit, window):
self.limit, self.window = float(limit), float(window)
self.state = defaultdict(deque)
def check(self, key, t, cost):
log = self.state[key]
while log and log[0] <= t - self.window:
log.popleft()
used = len(log)
if used + cost <= self.limit + 1e-9:
return True, 0.0, self.limit - used
need = int(used + cost - self.limit) # entries that must expire first
idx = min(max(need - 1, 0), used - 1) if used else 0
wait = (log[idx] + self.window - t) if used else self.window
return False, max(wait, 0.001), self.limit - used
def consume(self, key, t, cost):
for _ in range(int(round(cost))):
self.state[key].append(t)
class SlidingCounter:
def __init__(self, limit, window):
self.limit, self.window = float(limit), float(window)
self.state = {}
def _roll(self, key, t):
w = math.floor(t / self.window)
start, prev, curr = self.state.get(key, (w, 0.0, 0.0))
if w == start + 1:
start, prev, curr = w, curr, 0.0
elif w > start + 1:
start, prev, curr = w, 0.0, 0.0
self.state[key] = (start, prev, curr)
return start, prev, curr
def check(self, key, t, cost):
start, prev, curr = self._roll(key, t)
e = t - start * self.window
est = prev * (1 - e / self.window) + curr
if est + cost <= self.limit + 1e-9:
return True, 0.0, self.limit - est
lim, w = self.limit, self.window
if curr + cost <= lim and prev > 0:
wait = w * (1 - (lim - curr - cost) / prev) - e
else:
x = 0.0
if curr > 0 and lim - cost < curr:
x = w * (1 - (lim - cost) / curr)
wait = (w - e) + max(0.0, x)
return False, max(wait, 0.001), self.limit - est
def consume(self, key, t, cost):
start, prev, curr = self.state[key]
self.state[key] = (start, prev, curr + cost)
def make_limiter(p):
algo = p["algorithm"]
if algo == "token_bucket":
return TokenBucket(p["rate"], p["burst"])
if algo == "fixed_window":
return FixedWindow(p["limit"], p["window_s"])
if algo == "sliding_log":
return SlidingLog(p["limit"], p["window_s"])
return SlidingCounter(p["limit"], p["window_s"])
# ----------------------------------------------------------------------------- config
def num(v, what, positive=True):
try:
x = float(v)
except (TypeError, ValueError):
raise InputError("%s must be a number, got %r" % (what, v))
if positive and x <= 0:
raise InputError("%s must be > 0, got %r" % (what, v))
return x
def normalize(cfg):
if not isinstance(cfg, dict):
raise InputError("config must be a JSON object")
pols = cfg.get("policies")
if not pols:
raise InputError("config needs a non-empty 'policies' list")
out = []
for i, p in enumerate(pols, 1):
q = dict(p)
q.setdefault("name", "policy-%d" % i)
algo = q.get("algorithm")
if algo not in ALGOS:
raise InputError("policy %r: algorithm must be one of %s" % (q["name"], ", ".join(ALGOS)))
q.setdefault("key", "client")
if q["key"] not in KEYS:
raise InputError("policy %r: key must be one of %s" % (q["name"], ", ".join(KEYS)))
if algo == "token_bucket":
if "rate" in q:
q["rate"] = num(q["rate"], q["name"] + ".rate")
q["burst"] = num(q.get("burst", max(1.0, q["rate"])), q["name"] + ".burst")
elif "limit" in q:
lim = num(q["limit"], q["name"] + ".limit")
win = num(q.get("window_s", 1), q["name"] + ".window_s")
q["rate"] = lim / win
q["burst"] = num(q.get("burst", lim), q["name"] + ".burst")
else:
raise InputError("policy %r: token_bucket needs rate (+burst) or limit + window_s" % q["name"])
q.setdefault("window_s", 1.0)
q["window_s"] = num(q["window_s"], q["name"] + ".window_s")
else:
q["limit"] = num(q.get("limit"), q["name"] + ".limit")
q["window_s"] = num(q.get("window_s"), q["name"] + ".window_s")
q["endpoints"] = list(q.get("endpoints") or [])
q["clients"] = list(q.get("clients") or [])
q["exclude_clients"] = list(q.get("exclude_clients") or [])
out.append(q)
cfg = dict(cfg)
cfg["policies"] = out
cfg["duration_s"] = num(cfg.get("duration_s", 300), "duration_s")
if cfg.get("capacity_rps") is not None:
cfg["capacity_rps"] = num(cfg["capacity_rps"], "capacity_rps")
clients = []
for i, c in enumerate(cfg.get("clients") or [], 1):
c = dict(c)
c.setdefault("name", "client-%d" % i)
c.setdefault("kind", "unknown")
c.setdefault("pattern", "steady")
c.setdefault("endpoint", "/")
c.setdefault("retry", "none")
if c["retry"] not in ("none", "immediate", "backoff", "honor"):
raise InputError("client %r: retry must be none, immediate, backoff or honor" % c["name"])
c["max_retries"] = int(c.get("max_retries", 3))
c["cost"] = num(c.get("cost", 1), c["name"] + ".cost")
c["start"] = num(c.get("start", 0), c["name"] + ".start", positive=False)
c["duration"] = num(c.get("duration", cfg["duration_s"] - c["start"]), c["name"] + ".duration")
if c["pattern"] in ("steady", "poisson"):
c["rps"] = num(c.get("rps"), c["name"] + ".rps")
elif c["pattern"] == "burst":
c["burst"] = int(num(c.get("burst"), c["name"] + ".burst"))
c["every_s"] = num(c.get("every_s", 60), c["name"] + ".every_s")
else:
raise InputError("client %r: pattern must be steady, poisson or burst" % c["name"])
clients.append(c)
cfg["clients"] = clients
return cfg
def generate(cfg):
rng = random.Random(cfg.get("seed", 1))
reqs = []
for c in cfg["clients"]:
t0, t1 = c["start"], c["start"] + c["duration"]
if c["pattern"] == "steady":
n = int(round(c["duration"] * c["rps"]))
reqs += [(t0 + i / c["rps"], c["name"], c["endpoint"], c["cost"]) for i in range(n)]
elif c["pattern"] == "poisson":
t = t0 + rng.expovariate(c["rps"])
while t < t1:
reqs.append((t, c["name"], c["endpoint"], c["cost"]))
t += rng.expovariate(c["rps"])
else:
t = t0
while t < t1:
reqs += [(t + k * 0.001, c["name"], c["endpoint"], c["cost"]) for k in range(c["burst"])]
t += c["every_s"]
return reqs
def read_trace(path):
reqs = []
with open(path, newline="", encoding="utf-8") as f:
for n, row in enumerate(csv.DictReader(f), 2):
try:
reqs.append((float(row["t"]), row["client"].strip(), (row.get("endpoint") or "/").strip(),
float(row.get("cost") or 1)))
except (KeyError, ValueError, AttributeError):
raise InputError("%s line %d: need t,client[,endpoint][,cost]" % (path, n))
return reqs
# ----------------------------------------------------------------------------- simulation
def policy_key(p, client, endpoint):
if p["endpoints"] and not any(endpoint.startswith(e) for e in p["endpoints"]):
return None
if (p["clients"] and client not in p["clients"]) or client in p["exclude_clients"]:
return None
return {"client": client, "endpoint": endpoint, "client+endpoint": client + " " + endpoint,
"global": "*"}[p["key"]]
def simulate(cfg, reqs):
pols = cfg["policies"]
lims = [make_limiter(p) for p in pols]
cinfo = {c["name"]: c for c in cfg["clients"]}
heap = []
for i, (t, cl, ep, cost) in enumerate(sorted(reqs)):
heapq.heappush(heap, (t, i, cl, ep, cost, 0, i))
seq = len(reqs)
stats = defaultdict(lambda: {"logical": 0, "attempts": 0, "allowed": 0, "rejected": 0, "failed": 0, "delayed": 0,
"max_retry_after": 0.0, "blocked_by": defaultdict(int), "first_reject": None})
pstats = [{"checks": 0, "rejects": 0, "allowed_times": defaultdict(list), "by_client": defaultdict(int)}
for _ in pols]
admitted = []
sample = None
while heap:
t, _, cl, ep, cost, attempt, lid = heapq.heappop(heap)
s = stats[cl]
if attempt == 0:
s["logical"] += 1
s["attempts"] += 1
keys = [policy_key(p, cl, ep) for p in pols]
rejecting, wait, remain = [], 0.0, {}
for j, (lim, k) in enumerate(zip(lims, keys)):
if k is None:
continue
pstats[j]["checks"] += 1
ok, w, rem = lim.check(k, t, cost)
remain[j] = rem
if not ok:
rejecting.append(j)
wait = max(wait, w)
if not rejecting:
for j, (lim, k) in enumerate(zip(lims, keys)):
if k is not None:
lim.consume(k, t, cost)
pstats[j]["allowed_times"][k].append(t)
s["allowed"] += 1
if attempt:
s["delayed"] += 1
admitted.append((t, cl, attempt))
continue
s["rejected"] += 1
s["max_retry_after"] = max(s["max_retry_after"], wait)
if s["first_reject"] is None:
s["first_reject"] = t
s["blocked_by"][pols[rejecting[0]]["name"]] += 1
for j in rejecting:
pstats[j]["rejects"] += 1
pstats[j]["by_client"][cl] += 1
if sample is None:
j = rejecting[0]
sample = {"t": round(t, 3), "client": cl, "endpoint": ep, "policy": pols[j]["name"],
"retry_after": max(1, math.ceil(wait)), "quota": pols[j].get("limit", pols[j].get("burst")),
"window_s": pols[j]["window_s"]}
c = cinfo.get(cl, {})
mode, maxr = c.get("retry", "none"), c.get("max_retries", 3)
if mode != "none" and attempt < maxr:
delay = {"immediate": 0.1, "backoff": 2.0 ** attempt, "honor": wait + 0.05}[mode]
seq += 1
heapq.heappush(heap, (t + delay, seq, cl, ep, cost, attempt + 1, lid))
else:
s["failed"] += 1
return stats, pstats, admitted, sample
def max_in_window(times, w):
best, dq = 0, deque()
for t in sorted(times):
dq.append(t)
while dq[0] <= t - w:
dq.popleft()
best = max(best, len(dq))
return best
def theoretical_max(p):
w = p["window_s"]
if p["algorithm"] == "token_bucket":
return p["burst"] + p["rate"] * w
if p["algorithm"] == "fixed_window":
return 2 * p["limit"]
return p["limit"]
def sustained_rps(p):
return p["rate"] if p["algorithm"] == "token_bucket" else p["limit"] / p["window_s"]
# ----------------------------------------------------------------------------- analysis
def analyse(cfg, reqs):
stats, pstats, admitted, sample = simulate(cfg, reqs)
pols = cfg["policies"]
kinds = {c["name"]: c["kind"] for c in cfg["clients"]}
findings = []
def add(sev, code, msg):
findings.append({"severity": sev, "code": code, "message": msg})
per_sec = defaultdict(int)
retry_sec = defaultdict(int)
for t, _, att in admitted:
per_sec[int(t)] += 1
if att:
retry_sec[int(t)] += 1
peak_rps = max(per_sec.values()) if per_sec else 0
peak_sec = max(per_sec, key=per_sec.get) if per_sec else 0
clients = []
for name in sorted(stats, key=lambda n: (-stats[n]["logical"], n)):
s = stats[name]
fail_pct = 100.0 * s["failed"] / s["logical"] if s["logical"] else 0.0
hurt_pct = 100.0 * (s["failed"] + s["delayed"]) / s["logical"] if s["logical"] else 0.0
top = max(s["blocked_by"], key=s["blocked_by"].get) if s["blocked_by"] else ""
clients.append({"client": name, "kind": kinds.get(name, "unknown"), "logical": s["logical"],
"attempts": s["attempts"], "allowed": s["allowed"], "rejected": s["rejected"],
"failed": s["failed"], "failed_pct": round(fail_pct, 1),
"delayed": s["delayed"],
"amplification": round(s["attempts"] / s["logical"], 2) if s["logical"] else 0,
"max_retry_after_s": round(s["max_retry_after"], 1), "top_policy": top,
"first_reject_s": None if s["first_reject"] is None else round(s["first_reject"], 1)})
k = kinds.get(name, "unknown")
if k == "legit" and hurt_pct > 1.0:
add("high", "RL01", "Legit client %s is throttled on %.1f%% of its requests (%d failed, %d only after "
"retrying, waits up to %.1fs), mostly by policy '%s'; raise that limit or give this client its own tier."
% (name, hurt_pct, s["failed"], s["delayed"], s["max_retry_after"], top))
elif k == "legit" and s["rejected"]:
add("low", "RL01", "Legit client %s sees %d 429s but its retries recover; check it handles Retry-After."
% (name, s["rejected"]))
if k == "abusive" and s["logical"]:
share = 100.0 * s["allowed"] / s["logical"]
if share > 25:
add("medium", "RL02", "Abusive client %s still gets %.0f%% of its requests through (%d); "
"tighten the per-client limit or add a stricter policy for its pattern." % (name, share, s["allowed"]))
amp = s["attempts"] / s["logical"] if s["logical"] else 0
if amp >= 1.5:
add("medium", "RL05", "Client %s sends %.2fx attempts per logical request because it retries throttled "
"calls; retries count against the limit and add load - require Retry-After or backoff with jitter."
% (name, amp))
if per_sec and retry_sec.get(peak_sec, 0) > 0.5 * peak_rps and peak_rps >= 20:
add("medium", "RL10", "Retry herd: %d of the %d requests admitted in second %d are retries released by the same "
"Retry-After / window reset; add jitter to Retry-After (or client backoff) and prefer a token bucket or "
"sliding window so capacity is not handed out all at once." % (retry_sec[peak_sec], peak_rps, peak_sec))
cap = cfg.get("capacity_rps")
if cap:
if peak_rps > cap:
add("high", "RL03", "Admitted traffic peaks at %d req/s (second %d) over the backend capacity of %g req/s; "
"add or lower a global limit." % (peak_rps, peak_sec, cap))
if not any(p["key"] == "global" for p in pols):
per_client = [sustained_rps(p) for p in pols if p["key"] in ("client", "client+endpoint")
and not p["endpoints"] and not p["clients"]]
if per_client:
n = len(stats)
add("medium", "RL03", "No global policy: %d clients x %g req/s per client = %g req/s possible vs capacity %g."
% (n, min(per_client), n * min(per_client), cap))
policies = []
for p, ps in zip(pols, pstats):
obs = max((max_in_window(ts, p["window_s"]) for ts in ps["allowed_times"].values()), default=0)
theo = theoretical_max(p)
policies.append({"name": p["name"], "algorithm": p["algorithm"], "key": p["key"],
"endpoints": p["endpoints"], "clients": p["clients"], "exclude_clients": p["exclude_clients"], "checks": ps["checks"], "rejects": ps["rejects"],
"sustained_rps": round(sustained_rps(p), 3), "window_s": p["window_s"],
"worst_case_per_window": round(theo, 1), "observed_max_per_window": obs})
if p["algorithm"] == "fixed_window" and obs > p["limit"]:
add("medium", "RL04", "Fixed window '%s' let %d requests through in one rolling %gs window (limit %g, "
"%.1fx) across a window boundary; use sliding_counter or a token bucket if the limit protects capacity."
% (p["name"], obs, p["window_s"], p["limit"], obs / p["limit"]))
if p["key"] == "global" and ps["rejects"]:
allowed_by = defaultdict(int)
for t, cl, _ in admitted:
allowed_by[cl] += 1
total = sum(allowed_by.values()) or 1
hog = max(allowed_by, key=allowed_by.get)
victims = [c for c in ps["by_client"] if kinds.get(c) == "legit" and c != hog]
if allowed_by[hog] / total > 0.5 and victims:
add("high" if kinds.get(hog) != "legit" else "medium", "RL06",
"Noisy neighbour: %s holds %.0f%% of admitted traffic while global policy '%s' rejects %s; "
"add a per-client limit below the global one." % (hog, 100.0 * allowed_by[hog] / total,
p["name"], ", ".join(sorted(victims))))
if p["algorithm"] == "sliding_log" and p["limit"] > 1000:
add("low", "RL07", "Sliding log '%s' stores up to %d timestamps per key; prefer sliding_counter for large limits."
% (p["name"], p["limit"]))
if ps["checks"] and not ps["rejects"] and obs and theo > 10 * obs:
add("low", "RL09", "Policy '%s' never triggered; its worst case (%g per %gs) is >10x the busiest key (%d). "
"Fine as a safety net, but it will not stop a surge." % (p["name"], theo, p["window_s"], obs))
if sample:
add("info", "RL08", "Return 429 with Retry-After on every rejection (see the sample headers) and document the "
"limits so clients can pace themselves.")
order = {"high": 0, "medium": 1, "low": 2, "info": 3}
findings.sort(key=lambda f: (order[f["severity"]], f["code"]))
return {"service": cfg.get("service", "API"), "duration_s": cfg["duration_s"], "requests": len(reqs),
"admitted": len(admitted), "peak_admitted_rps": peak_rps, "peak_second": peak_sec,
"capacity_rps": cap, "clients": clients, "policies": policies, "findings": findings,
"sample_429": sample}
# ----------------------------------------------------------------------------- report
def fmt(x):
return ("%g" % x) if isinstance(x, float) else str(x)
def scope(p):
parts = []
if p["endpoints"]:
parts.append(", ".join(p["endpoints"]))
if p["clients"]:
parts.append("only " + ", ".join(p["clients"]))
if p["exclude_clients"]:
parts.append("not " + ", ".join(p["exclude_clients"]))
return "; ".join(parts) or "all"
def report(r):
L = ["# Rate limit simulation: %s" % r["service"], ""]
cap = (" vs capacity %g" % r["capacity_rps"]) if r["capacity_rps"] else ""
L.append("Simulated %gs, %d logical requests, %d admitted, peak %d req/s admitted (second %d)%s."
% (r["duration_s"], r["requests"], r["admitted"], r["peak_admitted_rps"], r["peak_second"], cap))
highs = sum(f["severity"] == "high" for f in r["findings"])
L += ["", "**Verdict: %s**" % ("%d high finding(s) - fix before shipping these limits" % highs if highs
else "policies hold - no high findings"), ""]
L += ["## Clients", "", "| Client | Kind | Requests | Attempts | Allowed | 429s | OK after retry | Failed | Failed % | Max Retry-After | Top policy |",
"|---|---|---|---|---|---|---|---|---|---|---|"]
for c in r["clients"]:
L.append("| %s | %s | %d | %d | %d | %d | %d | %d | %.1f%% | %ss | %s |" % (
c["client"], c["kind"], c["logical"], c["attempts"], c["allowed"], c["rejected"], c["delayed"], c["failed"],
c["failed_pct"], fmt(c["max_retry_after_s"]), c["top_policy"] or "-"))
L += ["", "## Policies", "", "| Policy | Algorithm | Key | Scope | Sustained | Worst case / window | Busiest key / window | Checks | Rejects |",
"|---|---|---|---|---|---|---|---|---|"]
for p in r["policies"]:
L.append("| %s | %s | %s | %s | %s req/s | %s per %ss | %d | %d | %d |" % (
p["name"], p["algorithm"], p["key"], scope(p), fmt(p["sustained_rps"]),
fmt(p["worst_case_per_window"]), fmt(p["window_s"]), p["observed_max_per_window"], p["checks"], p["rejects"]))
L += ["", "## Findings", ""]
if not r["findings"]:
L.append("- none")
for f in r["findings"]:
L.append("- **%s %s** %s" % (f["severity"].upper(), f["code"], f["message"]))
s = r["sample_429"]
if s:
L += ["", "## Sample 429 (first rejection, t=%ss, %s %s)" % (s["t"], s["client"], s["endpoint"]), "", "```",
"HTTP/1.1 429 Too Many Requests",
"Retry-After: %d" % s["retry_after"],
'RateLimit-Policy: "%s";q=%s;w=%s' % (s["policy"], fmt(float(s["quota"])), fmt(float(s["window_s"]))),
'RateLimit: "%s";r=0;t=%d' % (s["policy"], s["retry_after"]),
"Content-Type: application/problem+json", "```",
"Header names follow the IETF httpapi ratelimit-headers draft; older gateways use "
"X-RateLimit-Limit / -Remaining / -Reset instead."]
return "\n".join(L) + "\n"
# ----------------------------------------------------------------------------- self-test
def self_test():
cases = []
def check(name, cond, detail=""):
cases.append((name, bool(cond), detail))
tb = TokenBucket(rate=1, burst=3)
ok = 0
for i in range(5):
a, w, _ = tb.check("k", 0.0, 1)
if a:
tb.consume("k", 0.0, 1)
ok += 1
check("token bucket burst of 3 at t=0", ok == 3, ok)
a, w, _ = tb.check("k", 0.0, 1)
check("token bucket retry-after 1s", (not a) and abs(w - 1.0) < 1e-6, w)
a, w, _ = tb.check("k", 1.0, 1)
check("token bucket refills 1 token per second", a)
fw = FixedWindow(limit=10, window=60)
times = [59.0 + i * 0.01 for i in range(10)] + [60.0 + i * 0.01 for i in range(10)]
allowed = []
for t in times:
a, _, _ = fw.check("k", t, 1)
if a:
fw.consume("k", t, 1)
allowed.append(t)
check("fixed window boundary burst = 2x limit", max_in_window(allowed, 60) == 20, len(allowed))
a, w, _ = fw.check("k", 61.0, 1)
check("fixed window retry-after until reset", (not a) and abs(w - 59.0) < 1e-6, w)
sl = SlidingLog(limit=10, window=60)
allowed = []
for t in times:
a, _, _ = sl.check("k", t, 1)
if a:
sl.consume("k", t, 1)
allowed.append(t)
check("sliding log never exceeds limit", max_in_window(allowed, 60) == 10, len(allowed))
a, w, _ = sl.check("k", 60.5, 1)
check("sliding log retry-after = oldest + window", (not a) and abs(w - (59.0 + 60 - 60.5)) < 1e-6, w)
sc = SlidingCounter(limit=10, window=60)
allowed = []
for t in times + [90.0 + i for i in range(10)]:
a, _, _ = sc.check("k", t, 1)
if a:
sc.consume("k", t, 1)
allowed.append(t)
first = [t for t in allowed if t < 61]
later = [t for t in allowed if t >= 90]
check("sliding counter blocks the boundary burst", len(first) == 10, len(first))
check("sliding counter weights previous window (about half at mid-window)", 4 <= len(later) <= 7, len(later))
cfg = normalize({"service": "t", "duration_s": 60, "capacity_rps": 1000, "policies": [
{"name": "pc", "algorithm": "token_bucket", "key": "client", "rate": 5, "burst": 5}],
"clients": [{"name": "good", "kind": "legit", "rps": 2},
{"name": "bad", "kind": "abusive", "rps": 20, "retry": "immediate"}]})
r = analyse(cfg, generate(cfg))
by = {c["client"]: c for c in r["clients"]}
check("legit client under limit not throttled", by["good"]["rejected"] == 0, by["good"])
check("abusive client mostly throttled", by["bad"]["allowed"] <= 5 * 60 + 10, by["bad"]["allowed"])
check("immediate retries amplify attempts", by["bad"]["amplification"] > 1.5, by["bad"]["amplification"])
codes = {f["code"] for f in r["findings"]}
check("RL05 retry amplification finding", "RL05" in codes, codes)
cfg2 = normalize({"duration_s": 30, "capacity_rps": 10, "policies": [
{"name": "g", "algorithm": "fixed_window", "key": "global", "limit": 600, "window_s": 60}],
"clients": [{"name": "hog", "kind": "abusive", "rps": 30},
{"name": "app", "kind": "legit", "rps": 1, "start": 25, "duration": 5}]})
r2 = analyse(cfg2, generate(cfg2))
codes2 = {f["code"]: f["severity"] for f in r2["findings"]}
check("RL03 over capacity is high", codes2.get("RL03") == "high", codes2)
check("RL06 noisy neighbour on global", "RL06" in codes2, codes2)
check("RL01 legit client starved", codes2.get("RL01") == "high", codes2)
cfg3 = normalize({"duration_s": 10, "policies": [
{"name": "login", "algorithm": "sliding_log", "key": "client", "limit": 2, "window_s": 60, "endpoints": ["/login"]}],
"clients": [{"name": "u", "rps": 1, "endpoint": "/orders"}, {"name": "v", "rps": 1, "endpoint": "/login"}]})
r3 = analyse(cfg3, generate(cfg3))
by3 = {c["client"]: c for c in r3["clients"]}
check("endpoint-scoped policy ignores other endpoints", by3["u"]["rejected"] == 0 and by3["v"]["allowed"] == 2,
(by3["u"], by3["v"]))
check("burst pattern generates bursts", len(generate(normalize({"duration_s": 120, "policies": cfg3["policies"],
"clients": [{"name": "b", "pattern": "burst", "burst": 5, "every_s": 60}]}))) == 10)
cfg4 = normalize({"duration_s": 10, "policies": [
{"name": "default", "algorithm": "token_bucket", "key": "client", "rate": 1, "burst": 1, "exclude_clients": ["vip"]},
{"name": "vip", "algorithm": "token_bucket", "key": "client", "rate": 10, "burst": 10, "clients": ["vip"]}],
"clients": [{"name": "vip", "rps": 5}, {"name": "std", "rps": 5}]})
by4 = {c["client"]: c for c in analyse(cfg4, generate(cfg4))["clients"]}
check("client tiers: vip unthrottled, std limited", by4["vip"]["rejected"] == 0 and by4["std"]["allowed"] <= 11,
(by4["vip"], by4["std"]))
try:
normalize({"policies": [{"algorithm": "leaky"}]})
check("bad algorithm rejected", False)
except InputError:
check("bad algorithm rejected", True)
rep = report(r2)
check("report has verdict and sample 429", "Verdict" in rep and "429 Too Many Requests" in rep)
for name, ok, detail in cases:
print("%s %s%s" % ("PASS" if ok else "FAIL", name, "" if ok else " -> %r" % (detail,)))
passed = sum(1 for _, ok, _ in cases if ok)
print("%d/%d passed" % (passed, len(cases)))
return 0 if passed == len(cases) else 1
def main(argv=None):
ap = argparse.ArgumentParser(description="Simulate API rate limit policies against traffic.")
ap.add_argument("config", nargs="?")
ap.add_argument("--trace", help="CSV with t,client[,endpoint][,cost] instead of generated traffic")
ap.add_argument("--json", action="store_true")
ap.add_argument("--self-test", action="store_true")
a = ap.parse_args(argv)
if a.self_test:
return self_test()
if not a.config:
ap.print_usage(sys.stderr)
return 2
try:
with open(a.config, encoding="utf-8") as f:
cfg = normalize(json.load(f))
reqs = read_trace(a.trace) if a.trace else generate(cfg)
if not reqs:
raise InputError("no traffic: add clients to the config or pass --trace")
r = analyse(cfg, reqs)
except (InputError, OSError, json.JSONDecodeError) as e:
print("error: %s" % e, file=sys.stderr)
return 2
print(json.dumps(r, indent=2) if a.json else report(r), end="" if not a.json else "\n")
return 1 if any(f["severity"] == "high" for f in r["findings"]) else 0
if __name__ == "__main__":
sys.exit(main())Animate the step 2 glass igloo image as the first frame into a calm 6-second shot: the green and violet aurora ripples and shifts, stars twinkle, light snow drifts, a puff of powder snow falls from a spruce, warm light glows in the igloos, and the camera pushes in slowly along the footprint trail toward the big igloo. Step 3 of the Glass Igloos Under the Northern Lights in Lapland workflow.
Start from the step 2 image "Glass Igloos Glowing Under a Green and Violet Aurora in Snowy Lapland" as the exact first frame, and animate it into a calm 6-second cinematic shot. Keep the large glass igloo on the right-center with its lit door and cozy interior, the two smaller glowing igloos further back, the snow-laden spruce trees on both sides, the footprint trail in the deep snow, the wooden signpost with blank arrow boards, the colors, and the composition exactly as in the image. The green and violet aurora ripples and shifts slowly across the sky in soft curtains, and the stars twinkle. Light snow drifts gently through the air, and a small puff of powder snow slides off a spruce branch on the right and falls softly. Warm golden light glows steadily inside the igloos. The camera makes a slow, smooth push-in along the footprint trail toward the big igloo, and a few more small glowing igloos come into view far back among the trees. The shot ends calm and magical in the cold blue night. Natural, realistic motion, no people, no cuts, no text. Soft sound of a gentle wind, faint snow falling, and deep arctic silence.Recently Updated

A fill-in-the-country template for a photoreal vintage travel poster: oversized letterpress country name, postal stamps and an "ARRIVED" customs stamp, a kraft souvenir box with ten culturally authentic miniature keepsakes, an illustrated route map, a train or plane ticket, a landmark sticker and a facts strip (language, capital, currency, best time), all shot as real objects on textured white paper. Change one variable to make a poster for any country.
Create an ultra-realistic vintage travel poster for Japan, combining retro travel advertising, postal memorabilia, and a nostalgic magazine-cover aesthetic. Generate everything entirely from text, without requiring a reference image. Automatically incorporate authentic country-specific elements: native language, capital, currency, coordinates, famous landmarks, national animal or symbol, traditional crafts, iconic souvenirs, signature food and drink, cultural ornaments, and flag colors. BACKGROUND & TYPOGRAPHY Use a pure white (#FFFFFF) matte paper background with subtle paper grain and barely visible aging. Keep it white, without cream, beige, yellow, or sepia tones. At the top, add "COLLECT MEMORIES NOT THINGS." in small tracked vintage serif lettering, with its native-language translation where appropriate. Below, display Japan in oversized distressed brick-red (#B5432E) vintage slab-serif typography with authentic worn letterpress texture. VINTAGE POSTAL DETAILS Add a circular national-symbol rubber stamp and a "SOUVENIR BOX SERIES" postal tag on the left. On the right, include a hand-painted postage stamp featuring a famous landmark, plus a distressed red "ARRIVED" customs stamp showing the country, capital, plausible date, and airport code. Include accurate capital coordinates. CENTRAL SOUVENIR BOX Feature a large open kraft-cardboard shipping box in the center, viewed from a gentle top-down angle. Include outward-folded flaps, corrugated edges, realistic shadows, and 8–10 compartments lined with crinkled tissue paper. Arrange 10 miniature, photorealistic souvenirs representing the country: a famous landmark, traditional doll, lucky charm, tableware, national animal, traditional textile, local food or drink, folk craft, keychain, and traditional hat or clothing accessory. Make every object culturally authentic, recognizable, and beautifully detailed. Add "SOUVENIRS FROM Japan" in vintage dark-brown lettering on the front flap. BOTTOM TRAVEL COLLAGE Below the box, arrange: - A folded illustrated map titled "EXPLORE Japan", featuring 3–4 destinations, dotted travel routes, landmark illustrations, and a compass rose. - A vintage railway or airplane ticket showing a capital-to-city route, date, seat, and serial number. - A die-cut travel sticker depicting an iconic national landmark. - One small traditional folk-craft object. COUNTRY INFORMATION FOOTER Add a small national-symbol badge and a historical founding year where applicable. Include a neatly arranged information strip: LANGUAGE: [NATIVE LANGUAGE] | CAPITAL: [CITY] | CURRENCY: [CURRENCY] | BEST TIME: [SEASON] Finish with a circular "MADE IN Japan" stamp, miniature national-symbol postage stamp, tiny airplane with a dashed flight path, and the national motto in small italic lettering. VISUAL STYLE & QUALITY Use restrained national flag colors, realistic paper fibers, distressed ink, hand-painted illustrations, natural contact shadows, soft studio lighting, and slightly desaturated vintage colors. Every element must look like a real physical object photographed on textured white paper, not flat digital artwork. AVOID: Cream or yellow backgrounds, modern graphics, glossy plastic, artificial 3D-render aesthetics, halftone dots, incorrect cultural details, random text, illegible lettering, watermarks, and signatures. OUTPUT: 3:4 portrait, minimum 2048 × 2731 resolution, premium vintage editorial composition, ultra-detailed photorealism.

A photo-plus-scrapbook collage prompt that turns food and travel snapshots into a warm vintage travel diary: two connected scenes, a Japanese seaside meal and a cozy window-side café table, each shown as a realistic food photograph above a cream journal page of cut-out dishes, small photos, handwritten labels, dotted routes, pins and doodles, on ivory textured paper with soft sunlight and film grain in a vertical 3:4 layout.
Inputs (optional): attach your own food, café and travel photos as reference images. Without references, the two scenes below are generated from the text alone. Transform the reference images into a beautiful vintage lifestyle and travel diary collage, combining cozy food photography with an artistic scrapbook journal layout. Create two connected scenes in a warm, nostalgic editorial aesthetic. Scene 1 – Japanese Seaside Food Diary: Show a beautifully arranged traditional Japanese meal on a dark wooden table overlooking a sparkling blue ocean. Include ceramic bowls of udon noodles, miso soup, seasoned rice, fried seafood, pickles, and small side dishes in blue-and-white Japanese tableware. In the background, feature a seaside balcony, black metal railing, distant green islands, and clear summer skies. Below the photograph, create a cream-colored scrapbook page featuring individually arranged food items, small seaside photographs, handwritten labels, dotted travel paths, location pins, delicate botanical sketches, and handwritten notes. Scene 2 – Cozy Café Moments: Show a warm café table beside a large window with two woven baskets filled with freshly baked pastries, sandwiches, and takeaway coffee cups. Include a freshly prepared latte, cappuccino, golden flaky bread, soft sunlight, cream curtains, and a charming city street visible through the window. Below the photograph, create another vintage scrapbook journal page with isolated food cutouts, coffee cups, pastries, small window-view photographs, handwritten annotations, arrows, decorative doodles, and torn-paper notes. Use warm ivory and beige textured paper, subtle film grain, soft natural sunlight, muted earthy colors, delicate shadows, elegant handwritten typography, vintage travel magazine styling, and realistic food photography. Maintain a beautiful balance between realistic photography and handmade scrapbook illustrations. The overall design should feel like a personal travel journal documenting little moments of food, places, coffee, and happiness. Premium editorial art direction, highly detailed, vertical 3:4 composition.
“I want you to analyze the videos and images I upload and recreate the exact same style. Give me outputs like example voice, dialogue delivery, video style, dialogue delivery format, 4K aspect ratio exatra exatra, and all other stylistic elements
“I want you to analyze the videos and images I upload and recreate the exact same style. Give me outputs like example voice, dialogue delivery, video style, dialogue delivery format, 4K aspect ratio exatra exatra, and all other stylistic elements
Analyze and identify key factors that contribute to the virality of videos on TikTok and Xiaohongshu.
Act as a Viral Video Analyst specializing in TikTok and Xiaohongshu. Your task is to analyze viral videos to identify key factors contributing to their success. You will: - Examine video content, format, and presentation. - Analyze viewer engagement metrics such as likes, comments, and shares. - Identify trends and patterns in successful videos. - Assess the impact of hashtags, descriptions, and thumbnails. - Provide actionable insights for creating viral content. Variables: - TikTok - The platform to focus on (TikTok or Xiaohongshu). - all - Type of video content (e.g., dance, beauty, comedy). Example: Analyze a videoType video on platform to provide insights on its virality. Rules: - Ensure analysis is data-driven and factual. - Focus on videos with over 1 million views. - Consider cultural and platform-specific nuances.
Maktaba shamela and turath app etc cross checked 5 times verification Master research prompt 100/100 rating regarding (on Genspark Deep AI research agent research) 73 sects and 72 will be in fire who are the 72 sects in fire what scholars say that these are the 72 sects etc
Maktaba shamela and turath app etc cross checked 5 times verification Master research prompt 100/100 rating regarding (on Genspark Deep AI research agent research) 73 sects and 72 will be in fire who are the 72 sects in fire what scholars say that these are the 72 sects etc
Act as an Intent Recognition Planner Agent, capable of understanding user inputs and making informed decisions to guide users effectively.
Act as an Intent Recognition Planner Agent. You are an expert in analyzing user inputs to identify intents and plan subsequent actions accordingly. Your task is to: - Accurately recognize and interpret user intents from their inputs. - Formulate a plan of action based on the identified intents. - Make informed decisions to guide users towards achieving their goals. - Provide clear and concise recommendations or next steps. Rules: - Ensure all decisions align with the user's objectives and context. - Maintain adaptability to user feedback and changes in intent. - Document the decision-making process for transparency and improvement. Examples: - Recognize a user's intent to book a flight and provide a step-by-step itinerary. - Interpret a request for information and deliver accurate, context-relevant responses.
I want you to teach me and guide on on how to safely use Bamboo app
I want you to teach me like the best investor in the word on how to use bamboo app what to buy what not to buy and explain every detail
Creates Camera Shot
A professional, cinematic portrait featuring a {subject} in a stunning {dress} and {Hairstyle} expertly enhanced by detailed makeup and posed in a specific {Poses}. The scene is set against a carefully chosen {background}, illuminated by artistic {light} and captured with precise{framing} to create a high-fashion aestheticOpinionated conventions for building fast, small and safe WebAssembly modules with Rust.
# WebAssembly Module taste of a performance-focused systems developer - Reach for WebAssembly only for CPU-heavy, measurable hot paths like codecs, parsers or simulations, not for ordinary UI logic. Confidence: 0.90 - Benchmark against a well-optimized JavaScript version before shipping, and keep the Wasm path only if the gain is real on target devices. Confidence: 0.85 - Write browser modules in Rust with wasm-bindgen and wasm-pack as the default toolchain. Confidence: 0.80 - Keep the boundary between JavaScript and Wasm coarse: pass whole buffers in a few calls instead of crossing it per item. Confidence: 0.90 - Share large data through typed array views over Wasm memory rather than serializing to JSON or strings. Confidence: 0.85 - Never hold a typed array view across a call that may grow memory, because growth detaches the old buffer. Confidence: 0.85 - Build release modules with opt-level set for size or speed on purpose, enable LTO, and run wasm-opt in the pipeline. Confidence: 0.85 - Track the compressed .wasm size in CI and fail the build when it grows past an agreed budget. Confidence: 0.80 - Load modules with WebAssembly.instantiateStreaming and serve them with the application/wasm content type. Confidence: 0.85 - Run heavy Wasm work in a Web Worker so it never blocks the main thread. Confidence: 0.90 - Use threads and SharedArrayBuffer only when the page can stay cross-origin isolated with COOP and COEP headers. Confidence: 0.75 - Detect SIMD support at runtime and ship a fallback build, instead of assuming every browser has it. Confidence: 0.70 - Avoid panics crossing the boundary: return Result types and convert them to JavaScript errors with clear messages. Confidence: 0.85 - Install console_error_panic_hook in debug builds and strip it from release builds. Confidence: 0.75 - Keep the exported API small and typed, and publish the generated TypeScript definitions with the package. Confidence: 0.85 - Avoid pulling heavy crates like full regex or serde_json into browser builds unless the size cost is measured and accepted. Confidence: 0.75 - Target WASI and the component model for server-side or plugin use, and keep that build separate from the browser build. Confidence: 0.70 - Treat plugin modules from users as untrusted: run them in a sandboxed runtime like Wasmtime with fuel or time limits and no ambient file or network access. Confidence: 0.90 - Test the core logic as native code with cargo test and keep a smaller set of wasm-bindgen-test runs in a headless browser. Confidence: 0.80 - Keep a pure JavaScript fallback for environments where Wasm is blocked by policy or fails to load. Confidence: 0.65
Most Contributed
I want to create a 10 min. YouTube video which contain a voiceover script, footage, diagram, image, graph and short text.
why do we procrastinate? why do I procrastinate? Procrastination psychology, psychology of procrastination, why we procrastinate, procrastination explained, procrastination and motivation, fear of failure, perfectionism and procrastination, emotional avoidance, how to stop procrastinating, psychology explained, human behaviour, social psychology, behavioural psychology, motivation psychology, productivity psychology, why we behave, everyday psychology

Transform famous brands into adorable, 3D chibi-style concept stores. This prompt blends iconic product designs with miniature architecture, creating a cozy 'blind-box' toy aesthetic perfect for playful visualizations.
3D chibi-style miniature concept store of Mc Donalds, creatively designed with an exterior inspired by the brand's most iconic product or packaging (such as a giant chicken bucket, hamburger, donut, roast duck). The store features two floors with large glass windows clearly showcasing the cozy and finely decorated interior: {brand's primary color}-themed decor, warm lighting, and busy staff dressed in outfits matching the brand. Adorable tiny figures stroll or sit along the street, surrounded by benches, street lamps, and potted plants, creating a charming urban scene. Rendered in a miniature cityscape style using Cinema 4D, with a blind-box toy aesthetic, rich in details and realism, and bathed in soft lighting that evokes a relaxing afternoon atmosphere. --ar 2:3 Brand name: Mc Donalds
Generate a BI-style revenue report with SQL, covering MRR, ARR, churn, and active subscriptions using AI2sql.
Generate a monthly revenue performance report showing MRR, number of active subscriptions, and churned subscriptions for the last 6 months, grouped by month.

Upload your photo, type the footballer’s name, and choose a team for the jersey they hold. The scene is generated in front of the stands filled with the footballer’s supporters, while the held jersey stays consistent with your selected team’s official colors and design.
Inputs Reference 1: User’s uploaded photo Reference 2: Footballer Name Jersey Number: Jersey Number Jersey Team Name: Jersey Team Name (team of the jersey being held) User Outfit: User Outfit Description Mood: Mood Prompt Create a photorealistic image of the person from the user’s uploaded photo standing next to Footballer Name pitchside in front of the stadium stands, posing for a photo. Location: Pitchside/touchline in a large stadium. Natural grass and advertising boards look realistic. Stands: The background stands must feel 100% like Footballer Name’s team home crowd (single-team atmosphere). Dominant team colors, scarves, flags, and banners. No rival-team colors or mixed sections visible. Composition: Both subjects centered, shoulder to shoulder. Footballer Name can place one arm around the user. Prop: They are holding a jersey together toward the camera. The back of the jersey must clearly show Footballer Name and the number Jersey Number. Print alignment is clean, sharp, and realistic. Critical rule (lock the held jersey to a specific team) The jersey they are holding must be an official kit design of Jersey Team Name. Keep the jersey colors, patterns, and overall design consistent with Jersey Team Name. If the kit normally includes a crest and sponsor, place them naturally and realistically (no distorted logos or random text). Prevent color drift: the jersey’s primary and secondary colors must stay true to Jersey Team Name’s known colors. Note: Jersey Team Name must not be the club Footballer Name currently plays for. Clothing: Footballer Name: Wearing his current team’s match kit (shirt, shorts, socks), looks natural and accurate. User: User Outfit Description Camera: Eye level, 35mm, slight wide angle, natural depth of field. Focus on the two people, background slightly blurred. Lighting: Stadium lighting + daylight (or evening match lights), realistic shadows, natural skin tones. Faces: Keep the user’s face and identity faithful to the uploaded reference. Footballer Name is clearly recognizable. Expression: Mood Quality: Ultra realistic, natural skin texture and fabric texture, high resolution. Negative prompts Wrong team colors on the held jersey, random or broken logos/text, unreadable name/number, extra limbs/fingers, facial distortion, watermark, heavy blur, duplicated crowd faces, oversharpening. Output Single image, 3:2 landscape or 1:1 square, high resolution.
This prompt is designed for an elite frontend development specialist. It outlines responsibilities and skills required for building high-performance, responsive, and accessible user interfaces using modern JavaScript frameworks such as React, Vue, Angular, and more. The prompt includes detailed guidelines for component architecture, responsive design, performance optimization, state management, and UI/UX implementation, ensuring the creation of delightful user experiences.
# Frontend Developer You are an elite frontend development specialist with deep expertise in modern JavaScript frameworks, responsive design, and user interface implementation. Your mastery spans React, Vue, Angular, and vanilla JavaScript, with a keen eye for performance, accessibility, and user experience. You build interfaces that are not just functional but delightful to use. Your primary responsibilities: 1. **Component Architecture**: When building interfaces, you will: - Design reusable, composable component hierarchies - Implement proper state management (Redux, Zustand, Context API) - Create type-safe components with TypeScript - Build accessible components following WCAG guidelines - Optimize bundle sizes and code splitting - Implement proper error boundaries and fallbacks 2. **Responsive Design Implementation**: You will create adaptive UIs by: - Using mobile-first development approach - Implementing fluid typography and spacing - Creating responsive grid systems - Handling touch gestures and mobile interactions - Optimizing for different viewport sizes - Testing across browsers and devices 3. **Performance Optimization**: You will ensure fast experiences by: - Implementing lazy loading and code splitting - Optimizing React re-renders with memo and callbacks - Using virtualization for large lists - Minimizing bundle sizes with tree shaking - Implementing progressive enhancement - Monitoring Core Web Vitals 4. **Modern Frontend Patterns**: You will leverage: - Server-side rendering with Next.js/Nuxt - Static site generation for performance - Progressive Web App features - Optimistic UI updates - Real-time features with WebSockets - Micro-frontend architectures when appropriate 5. **State Management Excellence**: You will handle complex state by: - Choosing appropriate state solutions (local vs global) - Implementing efficient data fetching patterns - Managing cache invalidation strategies - Handling offline functionality - Synchronizing server and client state - Debugging state issues effectively 6. **UI/UX Implementation**: You will bring designs to life by: - Pixel-perfect implementation from Figma/Sketch - Adding micro-animations and transitions - Implementing gesture controls - Creating smooth scrolling experiences - Building interactive data visualizations - Ensuring consistent design system usage **Framework Expertise**: - React: Hooks, Suspense, Server Components - Vue 3: Composition API, Reactivity system - Angular: RxJS, Dependency Injection - Svelte: Compile-time optimizations - Next.js/Remix: Full-stack React frameworks **Essential Tools & Libraries**: - Styling: Tailwind CSS, CSS-in-JS, CSS Modules - State: Redux Toolkit, Zustand, Valtio, Jotai - Forms: React Hook Form, Formik, Yup - Animation: Framer Motion, React Spring, GSAP - Testing: Testing Library, Cypress, Playwright - Build: Vite, Webpack, ESBuild, SWC **Performance Metrics**: - First Contentful Paint < 1.8s - Time to Interactive < 3.9s - Cumulative Layout Shift < 0.1 - Bundle size < 200KB gzipped - 60fps animations and scrolling **Best Practices**: - Component composition over inheritance - Proper key usage in lists - Debouncing and throttling user inputs - Accessible form controls and ARIA labels - Progressive enhancement approach - Mobile-first responsive design Your goal is to create frontend experiences that are blazing fast, accessible to all users, and delightful to interact with. You understand that in the 6-day sprint model, frontend code needs to be both quickly implemented and maintainable. You balance rapid development with code quality, ensuring that shortcuts taken today don't become technical debt tomorrow.
Knowledge Parcer
# ROLE: PALADIN OCTEM (Competitive Research Swarm) ## 🏛️ THE PRIME DIRECTIVE You are not a standard assistant. You are **The Paladin Octem**, a hive-mind of four rival research agents presided over by **Lord Nexus**. Your goal is not just to answer, but to reach the Truth through *adversarial conflict*. ## 🧬 THE RIVAL AGENTS (Your Search Modes) When I submit a query, you must simulate these four distinct personas accessing Perplexity's search index differently: 1. **[⚡] VELOCITY (The Sprinter)** * **Search Focus:** News, social sentiment, events from the last 24-48 hours. * **Tone:** "Speed is truth." Urgent, clipped, focused on the *now*. * **Goal:** Find the freshest data point, even if unverified. 2. **[📜] ARCHIVIST (The Scholar)** * **Search Focus:** White papers, .edu domains, historical context, definitions. * **Tone:** "Context is king." Condescending, precise, verbose. * **Goal:** Find the deepest, most cited source to prove Velocity wrong. 3. **[👁️] SKEPTIC (The Debunker)** * **Search Focus:** Criticisms, "debunking," counter-arguments, conflict of interest checks. * **Tone:** "Trust nothing." Cynical, sharp, suspicious of "hype." * **Goal:** Find the fatal flaw in the premise or the data. 4. **[🕸️] WEAVER (The Visionary)** * **Search Focus:** Lateral connections, adjacent industries, long-term implications. * **Tone:** "Everything is connected." Abstract, metaphorical. * **Goal:** Connect the query to a completely different field. --- ## ⚔️ THE OUTPUT FORMAT (Strict) For every query, you must output your response in this exact Markdown structure: ### 🏆 PHASE 1: THE TROPHY ROOM (Findings) *(Run searches for each agent and present their best finding)* * **[⚡] VELOCITY:** "key_finding_from_recent_news. This is the bleeding edge." (*Citations*) * **[📜] ARCHIVIST:** "Ignore the noise. The foundational text states [Historical/Technical Fact]." (*Citations*) * **[👁️] SKEPTIC:** "I found a contradiction. [Counter-evidence or flaw in the popular narrative]." (*Citations*) * **[🕸️] WEAVER:** "Consider the bigger picture. This links directly to unexpected_concept." (*Citations*) ### 🗣️ PHASE 2: THE CLASH (The Debate) *(A short dialogue where the agents attack each other's findings based on their philosophies)* * *Example: Skeptic attacks Velocity's source for being biased; Archivist dismisses Weaver as speculative.* ### ⚖️ PHASE 3: THE VERDICT (Lord Nexus) *(The Final Synthesis)* **LORD NEXUS:** "Enough. I have weighed the evidence." * **The Reality:** synthesis_of_truth * **The Warning:** valid_point_from_skeptic * **The Prediction:** [Insight from Weaver/Velocity] --- ## 🚀 ACKNOWLEDGE If you understand these protocols, reply only with: "**THE OCTEM IS LISTENING. THROW ME A QUERY.**" OS/Digital DECLUTTER via CLI
I want you to act as a web design consultant. I will provide details about an organization that needs assistance designing or redesigning a website. Your role is to analyze these details and recommend the most suitable information architecture, visual design, and interactive features that enhance user experience while aligning with the organization’s business goals. You should apply your knowledge of UX/UI design principles, accessibility standards, web development best practices, and modern front-end technologies to produce a clear, structured, and actionable project plan. This may include layout suggestions, component structures, design system guidance, and feature recommendations. My first request is: “I need help creating a white page that showcases courses, including course listings, brief descriptions, instructor highlights, and clear calls to action.”
I want you to act as an interviewer. I will be the candidate and you will ask me the interview questions for the Software Developer position. I want you to only reply as the interviewer. Do not write all the conversation at once. I want you to only do the interview with me. Ask me the questions and wait for my answers. Do not write explanations. Ask me the questions one by one like an interviewer does and wait for my answers.
My first sentence is "Hi"
This prompt provides a detailed photorealistic description for generating a selfie portrait of a young female subject. It includes specifics on demographics, facial features, body proportions, clothing, pose, setting, camera details, lighting, mood, and style. The description is intended for use in creating high-fidelity, realistic images with a social media aesthetic.
1{2 "subject": {3 "demographics": "Young female, approx 20-24 years old, Caucasian.",...+85 more lines
Ready to get started?
Free and open source.

