Inspiration
In 2025, YouTube logged about 2.5 billion Content ID copyright claims. Creators disputed only 0.51% of them. Yet of the disputes that were resolved, 67.4% went the creator's way.
So creators who push back usually win, but almost nobody pushes back. We think the reason is fear. Every step raises the stakes. A dispute can turn into a takedown and a strike. Three strikes in 90 days can end a channel. A counter-notice is a legal statement made under penalty of perjury.
Before one of us became a researcher, they worked as a paralegal. Most of the job was checking whether documents actually backed up what a client wanted to say. Creators facing a claim need exactly that check, and nobody gives it to them.
What it does
NoticeGuard checks whether a creator's own documents support the statement they are about to make, at the step they are about to take.
- Upload the claim notice, licence, receipt, licence terms and any emails from the licensor (.txt, .md, .pdf or .eml).
- Anchor. Every fact must come with its exact quote, file and line. If there's no quote, there's no fact.
- Check. Fixed, versioned rules test each part of the statement the creator would make. The AI does not decide.
- Report. One of three results: Evidence ready, Evidence gap or Needs an adviser. The creator also sees their options, lowest risk first.
NoticeGuard gives evidence, not legal advice. It never tells anyone to file.
Our demo case. Maya, a student animator, gets a claim on a monetised video. The licensor's website now says her licence covers non-monetised use only, so she thinks she's lost. NoticeGuard finds three things in her own documents:
- Her certificate is version 2, and clause 4.1 allows "monetised video on social platforms".
- The terms in force on her purchase date govern, so the newer terms don't apply to her.
- The claimant, Northline Rights, administers claims for her own licensor.
Result: Evidence ready for a dispute. Her lowest-risk first step is to ask the licensor to release the claim.
How we built it
- Fact finding. A language model reads each document and wraps the phrases that matter in tags, inside the original text. Code then strips the tags and checks that what is left is the document, byte for byte. If it is not, the whole extraction for that document is rejected and retried once with the differences shown. The model never types a value and never decides anything.
- Parsing in code. Dates (day-first), licence versions, order numbers and category labels are derived in code from the tagged phrase, so a fact can always be traced to an exact quote, file and line.
- Repeated judgement. Wording that needs interpretation, such as whether "monetised video on social platforms" covers a monetised ClipStream channel, is asked $n = 3$ times at temperature $T = 0.7$. A permission counts only when all three runs agree. If they disagree about a permission, the result is Needs an adviser. A clear permission is only defeated by a clear exclusion, and any disagreement is shown in the evidence chain rather than smoothed over.
- Versioned rules. A deterministic rules engine (rules $R_0$ to $R_{10}$, rules version 0.1.0, defined in
rules.yaml) maps confirmed facts to each part of the statement the creator would sign, decides the verdict and ranks the routes lowest risk first. The rules see only the fact table, the chosen step and the structured "about you" fields. The free-text Notes box never reaches them, so a leading question gets the same answer as a neutral one. - Evidence view. Every sentence on screen clicks through Document → Quote → Fact → Rule → Status. For PDFs, the quote is located by character offset in the extracted text and highlighted in place.
- Draft with a post-check. A draft is prepared only when evidence is ready, only from confirmed facts, and is re-checked so it cannot contain a date, ID, clause or name that is not in the fact table. There is no Submit button. Nothing is filed.
- Stack. Python 3.12, FastAPI, Pydantic v2, pypdf, python-dateutil, PyYAML and SQLite on the back end. Gemini 3.8 Flash through the google-genai SDK for tagging and the repeated interpretation calls. Vanilla HTML, CSS and JavaScript with the Source Serif 4 typeface, no build step. Docker behind Caddy on a Hetzner server. Tests in pytest and node:test. Built with Claude Code, with the Impeccable design skill for the interface. See the separate Tech Stack document for the full list.
All demo documents are synthetic. Glasswork Audio, Northline Rights, ClipStream and "Glasslight" do not exist. The model responses for the demo cases are cached and committed, so the recorded demo plays back identically and works offline. New uploads call the model live.
Tech stack
Language models and AI services
| Component | Provider / access | Used for | Notes |
|---|---|---|---|
| Gemini 3.8 Flash | Google, google-genai Python SDK, API key |
Runtime extraction (span tagging) and the three interpretation runs per case | Low thinking budget with extra output allowance; temperature $T = 0$ for tagging, $T = 0.7$ for interpretation. Every call cached by content hash under cache/llm. |
| Claude Sonnet | Anthropic, via the Claude Code CLI (claude -p), sandboxed |
Historical benchmark only ($8$ cases $\times$ $3$ runs $\times$ $2$ prompts $\times$ $2$ systems) and the earlier demo cache | Run in an empty working directory with tools disabled and a plain system prompt. Not used by the deployed runtime. |
| Anthropic Messages API | anthropic Python SDK |
Optional provider, selectable by configuration | Not used for the demo or the deployed site. |
| Claude Code | Anthropic | Development assistant used to build the codebase and documentation | Disclosed per the AI-use rule. |
| Impeccable (Claude Code design skill) | Third-party skill | Interface design direction, critique and review | Design tokens recorded in DESIGN.md and .impeccable/design.json. |
Back end
| Technology | Version | Role |
|---|---|---|
| Python | 3.12 | Application language |
| FastAPI | $\geq$ 0.115 | HTTP API (/api/cases, /api/demo, /api/benchmark) and static file serving |
| Uvicorn | $\geq$ 0.30 | ASGI server |
| Pydantic | v2 ($\geq$ 2.7) | Data models for documents, facts, rule results, case results |
| python-multipart | $\geq$ 0.0.9 | Multi-file upload handling |
| pypdf | $\geq$ 4.2 | Text extraction from selectable-text PDF certificates (no OCR) |
| python-dateutil | $\geq$ 2.9 | Day-first date parsing in code |
| PyYAML | $\geq$ 6.0 | Rules definition file app/rules/rules.yaml (rules version 0.1.0) |
| python-dotenv | $\geq$ 1.0 | Configuration from .env |
| httpx | $\geq$ 0.27 | HTTP client used by the SDKs and tests |
SQLite (stdlib sqlite3) |
Python 3.12 | Case state store, data/cases.db |
Standard library: email, hashlib, json, re, uuid |
Python 3.12 | EML parsing, SHA-256 document fingerprints and cache keys, JSON state, normalisation |
Front end
| Technology | Role |
|---|---|
| HTML5, CSS3, vanilla JavaScript (ES2020+) | Single-page interface with no build step and no framework: static/index.html, app.js, workspace.js, styles.css, workspace.css, documents.css |
| Source Serif 4 (SIL Open Font License), self-hosted woff2 | Display and body typeface |
| Liberation Serif and Liberation Mono (SIL OFL) | Typefaces for the rendered document view |
| Inline SVG icon sprite | Status icons, so no status is conveyed by colour alone |
Hosting and delivery
| Technology | Role |
|---|---|
Docker, python:3.12-slim image, Docker Compose |
Container build and run; committed LLM cache copied into the image so demos work offline |
| Caddy (shared reverse proxy) with automatic Let's Encrypt TLS | HTTPS for noticeguard.ruleandrecord.com and noticeguard.primafacie.eu |
| Hetzner VPS (Linux) | Hosting |
rsync, ssh, bash (deploy/publish.sh) |
Deployment script |
Testing and benchmarking
| Technology | Role |
|---|---|
| pytest $\geq$ 8.0 | $69$ tests: quote round-trip, rules $R_0$ to $R_{10}$, determinism, golden demo cases from cache, provider behaviour, baseline draft detector |
Node.js 24 built-in test runner (node:test) |
$10$ front-end workspace tests |
bench/run_benchmark.py, bench/baseline.py, bench/compare_providers.py (own code) |
Rubric-consistency and leading-prompt benchmark; plain-model baseline; Gemini-versus-cached-Claude migration check |
data/benchmark/generate_variants.py (own code) |
Generates the $8$ variant benchmark cases from the synthetic document sets |
Data
- All documents, companies and people are synthetic: ClipStream, Glasswork Audio, Northline Rights, Lumen Vale, "Glasslight", Maya Ortiz, Leo Marsh. No real platform, company or personal data is used.
- Model responses for the demo cases are cached and committed (
cache/llm), so the demo runs offline and identically on each playback. New uploads call Gemini live. - The benchmark rubric and expected outcomes were written by the team. Raw baseline outputs are committed unedited under
bench/results/raw.
Not used
No low-code platform, no vector database, no retrieval-augmented generation, no fine-tuning, no OCR, no analytics or tracking scripts, no third-party JavaScript. At runtime, external network calls go only to the Gemini API.
Challenges we ran into
- Chatbots are good at reading licences. When we tested a general chatbot on Maya's licence, it read the clauses correctly. Then it handed the checking back: "find your original order confirmation", "check whether that video is monetised". That changed how we see our edge. It is not reading the licence. It is doing the checking against the creator's own documents and showing the proof.
- Licence versions and dates. The same track can sit under different terms depending on the purchase date. Maya bought under v2 in March 2025. The licensor's website now shows v3, which excludes monetised use on the Standard tier. Getting "which version governs" right needed a rule that reads the purchase date, the version on the receipt and certificate, and the governing-terms clause in both the certificate and the current terms.
- A clear permission versus an unclear restriction. The first real run sent Maya, who should be ready, to an adviser. The model had tagged "Pro licence additionally permits broadcast use" as a restriction, and one of three runs called it unclear. That forced us to state a precedence rule we now stand behind: a clear permission is only defeated by a clear exclusion, and the disagreement is shown rather than hidden.
- Evidence without advice. Every label and message had to report what the documents show without telling anyone what to do. "Confirmed by document", never "Verified". "Your evidence is ready for a dispute", never "you should dispute".
- Keeping the comparison honest. Our first draft compared NoticeGuard with a chatbot that had been given fewer documents. We made the comparison fair: the plain-model baseline receives the same documents and the same two prompts, and its outputs are committed unedited.
Accomplishments that we're proud of
- A working prototype where every fact on screen traces back to an exact quote, file and line, and the golden tests assert that.
- A separate, versioned rules engine, so the AI finds facts but never decides the result. The same facts always produce the same verdict.
- An honest Needs an adviser result, which we treat as a feature, not a failure.
- A demo story a non-lawyer can follow in under a minute: the same creator and the same track, bought before and after a terms change, give different evidence and a different result.
- The Leo demo works end to end. A counter-notice is an evidence gap until the licensor's email is added live, and the "what changed" panel names the email as the fact that flipped rule $R_4$.
- Benchmark (run earlier with Claude Sonnet via the Claude CLI, before the runtime moved to Gemini 3.8 Flash): on 8 generated cases, 3 runs each, with the model cache off:
- NoticeGuard matched the rubric in $47/48$ runs and never produced a draft for a case that should have been a gap or an adviser referral.
- The same model used as a plain chatbot matched the rubric in $10/24$ neutral runs ($41.7\%$) and $5/24$ leading runs ($20.8\%$), and drafted when it should not have in $14/48$ runs.
- The post-check caught a real defect during the build: a quote shortener cut a word in half, and the draft was withheld rather than shown.
What we learned
- The real risk is the statement a creator makes, not the claim itself. So check the statement, part by part.
- Trust comes from being able to trace every answer back to its source, not from a confident answer.
- Legal, engineering and security perspectives change the design. Each of our team members caught problems the others missed.
What's next for NoticeGuard
First 90 days after LexHack:
- Test NoticeGuard on 20 real creator claims.
- Run pilots with 2 university IP or digital-rights clinics.
- Launch a free hosted beta for creators.
Security hardening: checking file types by content rather than extension, size limits, parser timeouts, isolated parsing, no document storage, and resistance to instructions hidden inside uploaded files.
After that: music libraries, which already know which licence each buyer holds, then other platforms with DMCA takedown processes. NoticeGuard will always be free for creators.
Our ask: the LexHack Builders Fellowship. Legal mentorship would help us pressure-test our rules, deployment support would take the prototype live, and clinic introductions would reach our first real users.
Log in or sign up for Devpost to join the conversation.