Skip to content

Context Assembly

How user-provided data becomes AI prompts. This is the core intelligence layer — every AI feature is only as good as the context it receives.

The Big Picture

                           ┌─────────────────────────────────────────┐
                           │           7 AI FEATURES                 │
                           │                                         │
                           │  Chat  Focus  Health  PDF   Persona     │
                           │  Panel Card   Dots    Import Generator  │
                           │  Per-Marker AI   Correlation AI         │
                           └────────────┬────────────────────────────┘


                              ┌──────────────────┐
                              │  callClaudeAPI() │  ← single entry point
                              │     api.js       │
                              └────────┬─────────┘

                    ┌──────────┬───────┴────────┬──────────┐
                    ▼          ▼                ▼          ▼
               Anthropic   OpenRouter       Venice     Ollama
               Messages    (OpenAI-compat)  (OpenAI)   /api/chat
               API + SSE   via shared       via shared  newline-
                           helper           helper      delimited

Every AI call passes the same shape: { system, messages, maxTokens, onStream? }. The caller assembles the prompt; the router just delivers it.

Data Sources

Everything the AI can know about the user comes from these sources:

┌─────────────────────────────────────────────────────────────────────┐
│                        USER DATA SOURCES                            │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  PROFILE                    CONTEXT CARDS (9)      LAB DATA         │
│  ├─ sex                     ├─ Health Goals         ├─ entries[]    │
│  ├─ DOB (→ age)             ├─ Medical Conditions   ├─ dates[]      │
│  └─ location/latitude       ├─ Diet                 ├─ marker values│
│                             ├─ Exercise             ├─ ref ranges   │
│  PERSONA                    ├─ Sleep & Rest         ├─ optimal ranges│
│  ├─ personality ID          ├─ Light & Circadian    ├─ phase ranges │
│  ├─ promptText              ├─ Stress               ├─ custom markers│
│  └─ evidenceBased flag      ├─ Love Life            └─ flagged results│
│                             └─ Environment                          │
│  CHAT                                                               │
│  ├─ user message            OTHER                                   │
│  └─ last 10 history msgs    ├─ Interpretive Lens                   │
│                             ├─ Context Notes                        │
│  MARKER SCHEMA              ├─ Menstrual Cycle                      │
│  ├─ MARKER_SCHEMA           ├─ Supplements                          │
│  └─ customMarkers           └─ User Notes                           │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

buildLabContext() — The Central Serializer

buildLabContext() in chat.js is the single function that converts all user data into a plain-text block. It is used by Chat and Health Dots. The Focus Card uses the lighter buildFocusContext() instead.

Output Structure

Sections are ordered by priority — the AI sees "what are you trying to solve?" first, then "what do the numbers say?", then medical/lifestyle context. This exploits primacy bias in LLMs.

Lab data for current profile (sex: female, age: 34, unit system: SI, today: 2026-02-24,
  dates: Jan 15, Feb 20, Mar 10):

## Health Goals (Things to Solve)          ←── 1. GOALS (what to optimize)
### Major Priority
- Optimize thyroid function
### Mild Priority
- Improve sleep quality

## Interpretive Lens                       ←── 2. LENS (analytical framework)
Quantum biology, functional medicine paradigm

Note: status labels below use reference ranges.

## Biochemistry                            ←── 3. LAB VALUES (the data)
- Glucose: Jan 15: 5.2, Feb 20: 5.0, Mar 10: 4.9 mmol/L (ref: 3.9–5.6, status: normal)
- Creatinine: Jan 15: 72, Feb 20: 70 µmol/L (ref: 53–97, status: normal)

## Hormones
- Estradiol: Jan 15: 180 [follicular, ref 77–921], Feb 20: 95 [luteal, ref 65–380] pmol/L
  ↑ phase-aware: each value shows its cycle phase + phase-specific ref range

## Flagged Results (Latest)                ←── 4. FLAGS (quick-scan summary)
- Ferritin: 12 µg/L (LOW, range: 15–200)

## User Notes                              ←── 5. NOTES (temporal context)
- Jan 10: Started new thyroid medication
- Feb 15: Feeling much better energy-wise

## Medical Conditions / Diagnoses          ←── 6. MEDICAL CONTEXT
- Hashimoto's (major, since 2020)
Notes: On levothyroxine 50mcg

## Supplements & Medications               ←── 7. SUPPLEMENTS
- Vitamin D3 (5000 IU) [supplement]: Jan 1 → ongoing
- Magnesium glycinate (400mg) [supplement]: Jan 15 → ongoing

## Menstrual Cycle                         ←── 8. CYCLE (female only)
Profile: 28-day cycle (5-day period), regular, moderate flow.
Recent periods: Jan 3-Jan 7 (moderate) [Cramps, Fatigue], ...
Blood draw cycle context:
- Jan 15: Day 13 (follicular phase)
- Feb 20: Day 18 (luteal phase)
Next optimal blood draw window: Mar 3-5

## Diet                                    ←── 9-15. LIFESTYLE CARDS
Type: Mediterranean. Pattern: 3 meals.
Breakfast (07:00): eggs, avocado
Lunch (12:30): salad with chicken
Dinner (19:00): salmon, vegetables

## Exercise & Movement
Frequency: 4x/week. Types: weights, yoga. Intensity: moderate.

## Sleep & Rest
Duration: 7-8h. Quality: good. Room temp: 65°F.
Environment: blackout curtains, grounding sheet.

## Light & Circadian
Morning light: 15min. UV exposure: moderate. Latitude: 40-50°N.

## Stress
Level: moderate. Sources: work, finances.

## Love Life & Sexual Health
Status: partnered. Relationship quality: good. Libido: normal.

## Environment
Water: filtered. EMF: wifi router. Home lighting: mixed.

## Additional Context Notes                ←── 16. FREETEXT NOTES
Started cold plunges in January

Empty-Card Guards

Cards with no meaningful content are omitted entirely (not sent as empty sections). Each card checks for actual data:

  • diagnoses: conditions array not empty OR note has text
  • diet: type set OR any meal content OR note
  • exercise: frequency set OR types array not empty OR note
  • sleepRest: duration set OR quality set OR issues not empty OR note
  • stress: level set OR sources not empty OR note
  • loveLife: status set OR libido set OR concerns not empty OR note
  • environment: setting set OR water set OR air not empty OR note

Data Flow Diagram

importedData (localStorage)

         ├─── .healthGoals ────────────────────────────────┐  ← FIRST
         ├─── .interpretiveLens ───────────────────────────┤
         │                                                 │
         └─── .entries ──┐                                 │
                         ▼                                 │
                  getActiveData()                          │
                    │                                      │
                    ├─── categories[].markers[]             │
                    │     ├─ values[] (per date)            │
                    │     ├─ refMin/refMax                  │
                    │     ├─ phaseRefRanges[] (female)      │
                    │     └─ phaseLabels[] (female)         │
                    │                                      │
                    └─── getAllFlaggedMarkers() ────────────┤

         ├─── .notes ──────────────────────────────────────┤
         ├─── .diagnoses ──────────────────────────────────┤
         ├─── .supplements ────────────────────────────────┤  ← MEDICAL
         ├─── .menstrualCycle ─── + helper functions ──────┤
         │     ├─ getCyclePhase()                          │
         │     ├─ getBloodDrawPhases()                     │
         │     ├─ getNextBestDrawDate()                    │
         │     ├─ detectPerimenopausePattern()             │
         │     └─ detectCycleIronAlerts()                  │
         ├─── .diet ───────────────────────────────────────┤
         ├─── .exercise ───────────────────────────────────┤
         ├─── .sleepRest ──────────────────────────────────┤  ← LIFESTYLE
         ├─── .lightCircadian ─── + getLatitudeFromLocation()
         ├─── .stress ─────────────────────────────────────┤
         ├─── .loveLife ───────────────────────────────────┤
         ├─── .environment ────────────────────────────────┤
         └─── .contextNotes ───────────────────────────────┤  ← LAST


                                                  buildLabContext()


                                                  plain text block

Prompt Composition Per Feature

1. Chat Panel (sendChatMessage)

The most complex composition — layers 4 components. Lab context is placed before personality so the AI processes data first, then adopts the persona:

┌──────────────────────────────────────────────────────┐
│                    SYSTEM PROMPT                      │
│                                                      │
│  ┌────────────────────────────────────────────────┐  │
│  │ CHAT_SYSTEM_PROMPT (constants.js)              │  │
│  │ Priority-tiered structure:                     │  │
│  │                                                │  │
│  │ ## Core Rules                                  │  │
│  │  • NOT a doctor disclaimer                     │  │
│  │  • Reference specific values/dates             │  │
│  │  • Format with markdown                        │  │
│  │                                                │  │
│  │ ## Priority Context (apply when present)       │  │
│  │  • Health goals → prioritize by severity       │  │
│  │  • Interpretive lens → frame through experts   │  │
│  │  • Medical conditions → interpret accordingly  │  │
│  │  • Supplements → correlate start/stop dates    │  │
│  │  • Menstrual cycle → phase-aware hormones      │  │
│  │  • Notes → medication changes, symptoms        │  │
│  │                                                │  │
│  │ ## Lifestyle Context (apply when present)      │  │
│  │  • Diet, exercise, sleep, light, stress,       │  │
│  │    relationships, environment                  │  │
│  │  • Cross-cutting: cortisol/HPA axis note       │  │
│  │                                                │  │
│  │ ## Style                                       │  │
│  │  • Accessible, concise, redirect off-topic     │  │
│  └────────────────────────────────────────────────┘  │
│                       +                              │
│  ┌────────────────────────────────────────────────┐  │
│  │ "Current lab data:\n"                          │  │
│  │ buildLabContext()  ← entire serialized context │  │
│  └────────────────────────────────────────────────┘  │
│                       +                              │
│  ┌────────────────────────────────────────────────┐  │
│  │ PERSONALITY LAYER (after data)                 │  │
│  │  • Default: (nothing added)                    │  │
│  │  • Dr. House: personality.promptAddition       │  │
│  │  • Custom: "Persona: {promptText}"             │  │
│  │    + evidence-based disclaimer if opted in     │  │
│  └────────────────────────────────────────────────┘  │
│                       +                              │
│  ┌────────────────────────────────────────────────┐  │
│  │ SEARCH INSTRUCTION (optional)                  │  │
│  │  "After your response, output SEARCH_TERMS:    │  │
│  │   followed by 2-3 medical search terms"        │  │
│  │  (only when OpenAlex sources enabled)          │  │
│  └────────────────────────────────────────────────┘  │
│                                                      │
├──────────────────────────────────────────────────────┤
│                    MESSAGES                           │
│                                                      │
│  Last 10 chat history entries (role + content)       │
│  + current user message                              │
│                                                      │
├──────────────────────────────────────────────────────┤
│  maxTokens: 4096  │  streaming: yes                  │
└──────────────────────────────────────────────────────┘

2. Focus Card (loadFocusCard)

Uses the lightweight buildFocusContext() (~200-400 tokens) instead of full buildLabContext() (~2000-8000 tokens). Health-goals-aware system prompt:

┌──────────────────────────────────────────────────────┐
│  SYSTEM (if health goals exist):                     │
│  "You are a blood work analyst. Respond with ONE     │
│   sentence, max 40 words. If the patient has health  │
│   goals listed, connect your finding to their most   │
│   relevant goal. Name the single most actionable     │
│   marker finding, its direction, and why it matters. │
│   No preamble, no disclaimer."                       │
│                                                      │
│  SYSTEM (no health goals):                           │
│  "...Name the single most important marker finding,  │
│   its direction (rising/falling/high/low), and       │
│   briefly why it matters clinically..."              │
├──────────────────────────────────────────────────────┤
│  USER: buildFocusContext()                           │
│    • Profile: sex, age, today                        │
│    • Major health goals (if any)                     │
│    • Flagged markers (latest values + ranges)        │
│    • Notable changes >20% from previous              │
│    ~200-400 tokens total                             │
├──────────────────────────────────────────────────────┤
│  maxTokens: 100  │  streaming: no  │  timeout: 15s   │
├──────────────────────────────────────────────────────┤
│  CACHE: fingerprint = hash(entries + sex + DOB +      │
│         all 9 cards + lens + notes + cycle + supps)   │
└──────────────────────────────────────────────────────┘

3. Context Card Health Dots (loadContextHealthDots)

Requests structured JSON for only the stale (changed) cards. JSON.parse is wrapped in try-catch — on malformed AI responses, stale cards get gray dots while cached good data is preserved:

┌──────────────────────────────────────────────────────┐
│  SYSTEM: "Based on this person's lab data and        │
│  profile context, assess each profile area.           │
│  Return ONLY valid JSON with these keys..."           │
│                                                      │
│  { "diet": {"dot":"...","tip":"..."},                │
│    "stress": {"dot":"...","tip":"..."} }    ← only   │
│                                               stale  │
│  Dot: green/yellow/red/gray                  cards   │
│  Tip: max 12 words, reference specific markers       │
├──────────────────────────────────────────────────────┤
│  USER: buildLabContext()                              │
├──────────────────────────────────────────────────────┤
│  maxTokens: 500  │  streaming: no  │  timeout: 20s   │
├──────────────────────────────────────────────────────┤
│  CACHE: per-card fingerprint via getCardFingerprint() │
│         only stale cards re-fetched                   │
└──────────────────────────────────────────────────────┘

4. PDF Import (parseLabPDFWithAI)

Completely different context — no user profile, just schema + raw PDF. Filename is included in the user message for multi-file disambiguation:

┌──────────────────────────────────────────────────────┐
│  SYSTEM: "You are a lab report data extraction       │
│  assistant..."                                        │
│                                                      │
│  + JSON.stringify(buildMarkerReference())             │
│    { "biochemistry.glucose": { name, unit, ref },    │
│      "hormones.testosterone": { ... },               │
│      ... all known + custom markers }                 │
│                                                      │
│  + extraction rules (date format, value parsing,     │
│    WBC differential, custom marker suggestions)       │
│    WBC rule at position 3 (most error-prone)         │
│                                                      │
│  + expected JSON output schema                        │
├──────────────────────────────────────────────────────┤
│  USER: "Extract all biomarker results from this      │
│  lab report (file: report.pdf):\n\n" + pdfText       │
├──────────────────────────────────────────────────────┤
│  maxTokens: 8192  │  streaming: no                   │
└──────────────────────────────────────────────────────┘

5. Persona Generator (generateCustomPersonality)

Standalone creative prompt — no lab data involved:

┌──────────────────────────────────────────────────────┐
│  SYSTEM: "You are a persona designer for Get Based.  │
│  Create a thorough persona covering:"                 │
│    1. Identity & Background                           │
│    2. Communication Style                             │
│    3. Medical & Health Philosophy                     │
│    4. Analytical Approach                             │
│    5. Lifestyle & Optimization Lens                   │
│    6. Character & Personality                         │
│    7. Signature Recommendations                       │
│  "400-500 words. No disclaimers."                     │
├──────────────────────────────────────────────────────┤
│  USER: "Create a comprehensive persona for: {name}"  │
├──────────────────────────────────────────────────────┤
│  maxTokens: 2048  │  streaming: yes (into textarea)  │
└──────────────────────────────────────────────────────┘

6. Per-Marker AI (askAIAboutMarker)

Not a separate API call — builds a user message and injects it into the chat panel. Uses effective (phase-aware) reference ranges and includes trend direction when 2+ values exist:

askAIAboutMarker("hormones.estradiol")


  "Tell me about my Estradiol results.
   Values: Jan 15: 180 pmol/L (follicular phase, ref 77–921),
           Feb 20: 95 pmol/L (luteal phase, ref 65–380).
   Reference range: 77–921 pmol/L. Optimal range: 200–400.
   Current status: normal.
   Trend: down 47.2% from previous.
   Note: reference ranges shown are phase-specific.
   What does this mean and should I be concerned?"


  openChatPanel(prompt) → sendChatMessage() → full Chat composition

Caching Strategy

Each AI feature has independent caching to avoid redundant API calls:

FeatureCache KeyFingerprint InputsInvalidation
Focus Cardlabcharts-{profile}-focusCardentries + sex + DOB + 9 cards + lens + notes + cycle + suppsAny data change
Health Dotslabcharts-{profile}-contextHealthPer-card: lab data + card data + sex + DOBOnly changed cards re-fetched
Chatlabcharts-{profile}-chat-t_{id}N/A (conversation history)Never invalidated, 50-thread cap
PDF ImportN/AN/ANo caching
PersonaN/AN/ANo caching (user saves manually)

Token Budget

FeaturemaxTokensTypical Context Size
Chat4096~2,000–8,000 tokens (scales with data)
Focus Card100~200–400 tokens (lightweight context)
Health Dots500~2,000–8,000 tokens (full context)
PDF Import8192~1,000–3,000 tokens (marker schema)
Persona Generator2048~400 tokens (fixed)

Key Design Decisions

  1. Two serializers: buildLabContext() for full context (Chat, Health Dots) and buildFocusContext() for slim context (Focus Card, ~200-400 tokens). The focus card only needs flagged markers and notable changes for a 40-word response.

  2. Priority-ordered sections: buildLabContext() outputs sections in priority order — goals and lens first, then lab values and flags, then medical context, then lifestyle cards. This exploits LLM primacy bias so the most important context gets the most attention.

  3. Data before persona: In Chat, lab context is placed before the personality layer in the system prompt. This ensures the AI processes the medical data first, then adopts the persona style.

  4. Priority-tiered system prompt: CHAT_SYSTEM_PROMPT uses a 4-tier structure (Core Rules → Priority Context → Lifestyle Context → Style) instead of a flat bullet list. Health goals and interpretive lens are at the top of Priority Context.

  5. Empty-card guards: Cards with no meaningful content are completely omitted rather than sent as empty sections. Each card has a specific content check (not just truthiness).

  6. Phase-aware values: For female profiles with cycle data, estradiol and progesterone values include per-date phase labels and phase-specific reference ranges inline.

  7. Effective ranges: askAIAboutMarker() uses effective (phase-aware) reference ranges, not the static schema ranges. Also includes trend direction with percentage change.

  8. Robust JSON parsing: loadContextHealthDots() wraps the AI response JSON.parse in try-catch. On malformed responses, stale cards get gray dots while cached good data is preserved.

Prompt Improvement Methodology

Versioning

Context assembly changes follow the scheme YYYY-MM (e.g., 2026-02). Each version is documented in the changelog below.

Evaluation Criteria

When evaluating context assembly changes, assess these 5 dimensions:

  1. Completeness — Does the AI receive all relevant user data? Are any fields missing from the context?
  2. Primacy positioning — Are the most important sections (goals, lens, lab values) at the top where LLMs pay most attention?
  3. Signal-to-noise — Are empty cards omitted? Is the context lean and relevant, or padded with empty sections?
  4. Specificity — Do values include proper units, reference ranges, phase context, and trend direction?
  5. Token efficiency — Is the context appropriately sized for each feature's needs? (Focus card: ~200-400 tokens, not ~8000)

Testing Methodology

Changes are verified through 4 layers:

  1. Source inspectiontest-audit.js assertions read source code and verify structural properties (function existence, string patterns, section ordering)
  2. DOM verification — Browser tests check that rendered output includes expected elements
  3. Manual console checkwindow.buildLabContext() in browser console to inspect actual output
  4. Cross-feature check — Verify that Chat, Focus Card, and Health Dots all receive appropriate context

Changelog Format

Each entry documents:

  • Version identifier (YYYY-MM)
  • Changes made (grouped by category)
  • Files modified
  • Rationale for non-obvious decisions

Context Assembly Changelog

2026-02 — Priority Reordering + Enriched Context

Enriched header

  • Added age (computed from DOB), current date (ISO), and unit system label to buildLabContext() header
  • Consolidated 3 inline date formatters into single fmtDate helper

Section reordering (primacy bias optimization)

  • Health Goals → Interpretive Lens → Lab Values → Flagged Results → User Notes → Medical Conditions → Supplements → Menstrual Cycle → Diet → Exercise → Sleep → Light → Stress → Love Life → Environment → Context Notes
  • Rationale: AI sees "what to solve" first, then data, then medical context, then lifestyle

Empty-card guards

  • All 7 lifestyle/medical cards check for actual content (not just object truthiness)
  • Prevents sending ## Diet\n with no content when user has an empty diet card object

System prompt restructure (CHAT_SYSTEM_PROMPT)

  • Restructured from flat 20-bullet list to 4 priority tiers (Core Rules → Priority Context → Lifestyle Context → Style)
  • Promoted health goals + interpretive lens to top of Priority Context
  • Consolidated 4 separate cortisol/HPA mentions into one cross-cutting note
  • Removed duplicate creatinine/urea from exercise section (already in diet)

Chat prompt order

  • Moved personality layer after lab data (was before): SYSTEM_PROMPT + lab data + personality + search
  • Rationale: AI should process data first, then adopt persona style

Focus card optimization (buildFocusContext)

  • New lightweight serializer: ~200-400 tokens vs ~2000-8000 from buildLabContext()
  • Includes: profile (sex, age, today), major health goals, flagged markers, notable changes >20%
  • Health-goals-aware system prompt: connects findings to goals when present

Per-marker AI improvements (askAIAboutMarker)

  • Uses effective (phase-aware) reference ranges instead of static schema ranges
  • Adds trend direction with percentage change when 2+ values exist

PDF import fixes (parseLabPDFWithAI)

  • Moved WBC differential rule from position 6 to position 3 (most error-prone extraction rule)
  • Added filename to user message for multi-file disambiguation

Robustness

  • JSON.parse try-catch guard in loadContextHealthDots() — malformed AI responses degrade gracefully (gray dots on stale cards, cached good data preserved)

Files modified: js/chat.js, js/constants.js, js/views.js, js/context-cards.js, js/pdf-import.js, service-worker.js (v50→v51), test-audit.js, CLAUDE.md

2026-02b — Staleness Signals + Absent Field Awareness + Gate Broadening

Staleness signals (buildLabContext)

  • Global: When most recent lab results are >90 days old, inserts explicit NOTE: line with date and approximate months since last test
  • Per-category: After each category's markers, if that category's latest data is >90 days old, appends ⚠ Last tested ~N months ago line — catches stale categories even when other data is recent (e.g., old fatty acids alongside fresh CBC)
  • System prompt instructs AI to recommend retesting stale categories and discuss what similar/changed results would suggest

Focus card staleness (buildFocusContext)

  • Added last labs <date> to compact header so focus card can caveat stale data

System prompt additions (CHAT_SYSTEM_PROMPT)

  • Core Rules: instruction to note when data age affects analysis relevance
  • Lifestyle Context: two bullets teaching AI that missing fields = user didn't provide (not assumed default), and missing sections = user hasn't filled that area

Auto-gating with hasCardContent() (v53)

  • Replaced 7 hand-written card gates with generic hasCardContent(obj) from js/utils.js
  • Returns true if any field has content: strings non-empty, arrays non-empty, note field trimmed
  • Cards using auto-gate: diagnoses, diet, exercise, sleep, stress, loveLife, environment
  • Light & Circadian keeps custom lc || autoLat gate (external latitude injection)
  • Eliminates bug class: new fields added to any card are automatically included in AI context without manual gate updates

Previously broadened empty-card gates (v52, now superseded by auto-gating)

  • Diet: added restrictions, pattern, snacks to gate (user who only sets restrictions was silently dropped)
  • Exercise: added intensity, dailyMovement to gate
  • Sleep: added schedule, roomTemp, environment, practices to gate
  • Love Life: added relationship, satisfaction, frequency, orgasm to gate
  • Environment: added climate, waterConcerns, emf, emfMitigation, homeLight, toxins, building to gate

Files modified: js/utils.js, js/chat.js, js/constants.js, js/views.js, js/changelog.js, service-worker.js (v52→v53), test-audit.js, test-changelog.js, CLAUDE.md

Source Files

FileKey Functions
js/utils.jshasCardContent() — generic empty-card gate for context assembly
js/constants.jsCHAT_SYSTEM_PROMPT — priority-tiered system prompt
js/chat.jsbuildLabContext() — central serializer (full context)
js/chat.jssendChatMessage() — chat prompt composition
js/chat.jsaskAIAboutMarker() — per-marker prompt (effective ranges + trend)
js/chat.jsgenerateCustomPersonality() — persona generator prompt
js/views.jsbuildFocusContext() — lightweight focus card context (~200-400 tokens)
js/views.jsloadFocusCard() — focus card prompt (health-goals-aware)
js/context-cards.jsloadContextHealthDots() — health dots prompt (JSON.parse guarded)
js/pdf-import.jsparseLabPDFWithAI() — PDF import prompt (filename included)
js/pdf-import.jsbuildMarkerReference() — marker schema serializer
js/api.jscallClaudeAPI() — provider router
js/data.jsgetActiveData() — data pipeline (feeds buildLabContext)

Released under the GPL-3.0 License.