257 lines
13 KiB
HTML
257 lines
13 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>DOP — GBP Snapshot Intake v1.2</title>
|
||
<style>
|
||
:root { --bg:#0f1419; --panel:#1a2332; --border:#2d3a4f; --text:#e7ecf3; --muted:#8b9bb4; --accent:#3b82f6; --input:#0d1219; --ok:#22c55e; }
|
||
* { box-sizing: border-box; }
|
||
body { margin:0; font-family: system-ui, sans-serif; background:var(--bg); color:var(--text); line-height:1.45; }
|
||
.wrap { max-width:900px; margin:0 auto; padding:24px 16px 48px; }
|
||
h1 { font-size:1.3rem; margin:0 0 4px; }
|
||
.sub { color:var(--muted); font-size:0.9rem; margin-bottom:18px; }
|
||
.card { background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:16px 16px 8px; margin-bottom:14px; }
|
||
.card h2 { font-size:0.8rem; text-transform:uppercase; letter-spacing:0.04em; color:var(--muted); margin:0 0 12px; }
|
||
.grid { display:grid; grid-template-columns:1fr 1fr; gap:10px 14px; }
|
||
@media (max-width:640px){ .grid { grid-template-columns:1fr; } }
|
||
.full { grid-column:1 / -1; }
|
||
label { display:block; font-size:0.8rem; color:var(--muted); margin-bottom:4px; }
|
||
input, select, textarea { width:100%; background:var(--input); border:1px solid var(--border); border-radius:8px; color:var(--text); padding:9px 11px; font-size:0.95rem; outline:none; }
|
||
input:focus, select:focus, textarea:focus { border-color:var(--accent); }
|
||
textarea { min-height:64px; resize:vertical; }
|
||
.actions { display:flex; flex-wrap:wrap; gap:10px; margin:18px 0 10px; }
|
||
button { border:none; border-radius:8px; padding:11px 16px; font-size:0.95rem; font-weight:600; cursor:pointer; }
|
||
.btn-primary { background:var(--accent); color:#fff; }
|
||
.btn-secondary { background:transparent; color:var(--text); border:1px solid var(--border); }
|
||
.status { font-size:0.9rem; color:var(--ok); min-height:1.2rem; }
|
||
#output { width:100%; min-height:280px; font-family:ui-monospace,monospace; font-size:0.82rem; background:var(--input); border:1px solid var(--border); border-radius:10px; color:var(--text); padding:12px; white-space:pre; }
|
||
.hint { font-size:0.82rem; color:var(--muted); margin-top:8px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<h1>DOP — GBP Snapshot Intake v1.2</h1>
|
||
<p class="sub">NOT_PRESENT = looked, confirmed absent · NOT_OBSERVED = did not look / could not see. One primary category only. Paste output to Leonard (execution card v1.2).</p>
|
||
|
||
<form id="gbpForm" autocomplete="off">
|
||
<div class="card">
|
||
<h2>Snapshot Metadata</h2>
|
||
<div class="grid">
|
||
<div><label for="client">Client name</label><input id="client" type="text" /></div>
|
||
<div><label for="snapshot_dt">Snapshot datetime + TZ (ISO)</label><input id="snapshot_dt" type="text" placeholder="2026-08-03T14:30:00-07:00" /></div>
|
||
<div><label for="source">Source</label>
|
||
<select id="source">
|
||
<option>Human eye on Maps</option>
|
||
<option>Places API</option>
|
||
<option>Owner dashboard</option>
|
||
</select>
|
||
</div>
|
||
<div><label for="surface">Observation surface</label>
|
||
<select id="surface">
|
||
<option value="desktop_maps">desktop_maps</option>
|
||
<option value="mobile_maps">mobile_maps</option>
|
||
<option value="knowledge_panel">knowledge_panel</option>
|
||
<option value="other">other</option>
|
||
</select>
|
||
</div>
|
||
<div class="full"><label for="maps_url">Maps or Place URL</label><input id="maps_url" type="url" placeholder="https://www.google.com/maps/place/..." /></div>
|
||
<div><label for="place_id">Place ID (blank = agent computes from URL)</label><input id="place_id" type="text" /></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Core</h2>
|
||
<div class="grid">
|
||
<div><label for="business_name">Business name shown</label><input id="business_name" type="text" /></div>
|
||
<div><label for="address">Address shown (exact)</label><input id="address" type="text" /></div>
|
||
<div><label for="address_canonical">Address canonical (optional)</label><input id="address_canonical" type="text" placeholder="Suite not #" /></div>
|
||
<div><label for="phone">Phone (full digits, no mask)</label><input id="phone" type="text" placeholder="+1 530-929-7120" /></div>
|
||
<div><label for="website">Website shown</label><input id="website" type="text" /></div>
|
||
<div><label for="primary_category">Primary category (ONE only)</label><input id="primary_category" type="text" placeholder="e.g. Gym" /></div>
|
||
<div class="full"><label for="additional_categories">Additional categories (list)</label><input id="additional_categories" type="text" /></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Reputation</h2>
|
||
<div class="grid">
|
||
<div><label for="star_rating">Star rating</label><input id="star_rating" type="text" /></div>
|
||
<div><label for="review_count">Review count</label><input id="review_count" type="text" /></div>
|
||
<div><label for="review_responses">Review responses observed</label>
|
||
<select id="review_responses"><option>NOT_OBSERVED</option><option>Yes</option><option>No</option></select>
|
||
</div>
|
||
<div><label for="review_responses_detail">If Yes: N of total with replies + most recent age</label><input id="review_responses_detail" type="text" /></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Ownership / Claim</h2>
|
||
<div class="grid">
|
||
<div><label for="claim_prompt">Own this business? prompt</label>
|
||
<select id="claim_prompt"><option>NOT_OBSERVED</option><option>Yes</option><option>No</option></select>
|
||
</div>
|
||
<div><label for="claimed">Claimed status</label>
|
||
<select id="claimed">
|
||
<option>Unknown</option>
|
||
<option>Claimed</option>
|
||
<option>Unclaimed</option>
|
||
<option>Likely Claimed — inferred</option>
|
||
</select>
|
||
</div>
|
||
<div class="full"><label for="claimed_signals">If inferred — signals used</label><input id="claimed_signals" type="text" /></div>
|
||
<div><label for="verified">Verified badge</label>
|
||
<select id="verified"><option>NOT_OBSERVED</option><option>Yes</option><option>No</option></select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Operations</h2>
|
||
<div class="grid">
|
||
<div><label for="hours_listed">Hours listed</label>
|
||
<select id="hours_listed"><option>No</option><option>Yes</option></select>
|
||
</div>
|
||
<div><label for="open_now">Open now (requires datetime+TZ)</label>
|
||
<select id="open_now"><option>NOT_OBSERVED</option><option>Yes</option><option>No</option></select>
|
||
</div>
|
||
<div class="full"><label for="hours_text">Hours text</label><input id="hours_text" type="text" /></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Conversion (complete whole row if any button observed)</h2>
|
||
<div class="grid">
|
||
<div><label for="booking_button">Booking button on GBP</label>
|
||
<select id="booking_button"><option>NOT_OBSERVED</option><option>Yes</option><option>No</option></select>
|
||
</div>
|
||
<div><label for="booking_label">Booking label</label><input id="booking_label" type="text" /></div>
|
||
<div><label for="messaging_button">Messaging button</label>
|
||
<select id="messaging_button"><option>NOT_OBSERVED</option><option>Yes</option><option>No</option></select>
|
||
</div>
|
||
<div class="full"><label for="other_buttons">Other action buttons</label><input id="other_buttons" type="text" /></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Content</h2>
|
||
<div class="grid">
|
||
<div><label for="photos_present">Photos present</label>
|
||
<select id="photos_present"><option>No</option><option>Yes</option></select>
|
||
</div>
|
||
<div><label for="photo_count">Approx photo count</label><input id="photo_count" type="text" /></div>
|
||
<div class="full"><label for="services">Services listed (exact)</label><input id="services" type="text" /></div>
|
||
<div><label for="posts_section">Posts section present</label>
|
||
<select id="posts_section"><option>NOT_OBSERVED</option><option>Yes</option><option>No</option></select>
|
||
</div>
|
||
<div><label for="post_count">Post count (0 if empty)</label><input id="post_count" type="text" placeholder="0" /></div>
|
||
<div><label for="recent_post">Most recent post date</label><input id="recent_post" type="text" /></div>
|
||
<div><label for="qa_status">Q&A status</label>
|
||
<select id="qa_status">
|
||
<option>NOT_OBSERVED</option>
|
||
<option>NOT_PRESENT</option>
|
||
<option>empty</option>
|
||
<option>answers present</option>
|
||
</select>
|
||
</div>
|
||
<div class="full"><label for="attributes">Attributes / highlights</label><input id="attributes" type="text" /></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Related listings</h2>
|
||
<div class="grid">
|
||
<div><label for="second_listing">Second related listing nearby</label>
|
||
<select id="second_listing"><option>Unknown</option><option>Yes</option><option>No</option></select>
|
||
</div>
|
||
<div><label for="second_names">Related listing name(s)</label><input id="second_names" type="text" /></div>
|
||
<div class="full"><label for="second_notes">Related notes (factual)</label><textarea id="second_notes"></textarea></div>
|
||
<div class="full"><label for="raw_notes">Raw notes</label><textarea id="raw_notes"></textarea></div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
|
||
<div class="actions">
|
||
<button type="button" class="btn-primary" id="btnGenerate">Generate for Leonard</button>
|
||
<button type="button" class="btn-secondary" id="btnCopy">Copy</button>
|
||
<button type="button" class="btn-secondary" id="btnClear">Clear</button>
|
||
</div>
|
||
<div class="status" id="status"></div>
|
||
<label for="output">Leonard-ready output</label>
|
||
<textarea id="output" readonly></textarea>
|
||
<p class="hint">Follows execution card v1.2 (R1–R6). PLACE_ID computed from URL by agent if blank.</p>
|
||
</div>
|
||
<script>
|
||
const $ = (id) => document.getElementById(id);
|
||
function val(id) {
|
||
const v = ($(id).value || '').trim();
|
||
return v === '' ? 'NOT_OBSERVED' : v;
|
||
}
|
||
function buildOutput() {
|
||
return `Follow docs/agents/leonard-gbp-execution-card.md exactly (v1.2).
|
||
Map intake into client Data Inventory GBP row.
|
||
Apply only threat trigger rules R1–R6.
|
||
Do not invent fields. Do not add solutions. Do not reason beyond the execution card.
|
||
Return: updated GBP section, any threat changes, Changes Made list (with object IDs), Rules Fired list (rule ID, version, inputs, values, output).
|
||
|
||
--- GBP SNAPSHOT INTAKE ---
|
||
CLIENT: ${val('client')}
|
||
SNAPSHOT_DATETIME: ${val('snapshot_dt')}
|
||
SOURCE: ${val('source')}
|
||
OBSERVATION_SURFACE: ${val('surface')}
|
||
MAPS_URL: ${val('maps_url')}
|
||
PLACE_ID: ${($('place_id').value || '').trim() || 'COMPUTE_FROM_URL'}
|
||
|
||
BUSINESS_NAME: ${val('business_name')}
|
||
ADDRESS: ${val('address')}
|
||
ADDRESS_CANONICAL: ${val('address_canonical')}
|
||
PHONE: ${val('phone')}
|
||
WEBSITE: ${val('website')}
|
||
PRIMARY_CATEGORY: ${val('primary_category')}
|
||
ADDITIONAL_CATEGORIES: ${val('additional_categories')}
|
||
|
||
STAR_RATING: ${val('star_rating')}
|
||
REVIEW_COUNT: ${val('review_count')}
|
||
REVIEW_RESPONSES_OBSERVED: ${val('review_responses')}
|
||
REVIEW_RESPONSES_DETAIL: ${val('review_responses_detail')}
|
||
|
||
CLAIM_PROMPT_VISIBLE: ${val('claim_prompt')}
|
||
CLAIMED_STATUS: ${val('claimed')}
|
||
CLAIMED_SIGNALS: ${val('claimed_signals')}
|
||
VERIFIED_BADGE_VISIBLE: ${val('verified')}
|
||
|
||
HOURS_LISTED: ${val('hours_listed')}
|
||
HOURS_TEXT: ${val('hours_text')}
|
||
OPEN_NOW_OBSERVED: ${val('open_now')}
|
||
|
||
BOOKING_BUTTON_PRESENT_ON_GBP: ${val('booking_button')}
|
||
BOOKING_BUTTON_LABEL: ${val('booking_label')}
|
||
MESSAGING_BUTTON_PRESENT: ${val('messaging_button')}
|
||
OTHER_ACTION_BUTTONS: ${val('other_buttons')}
|
||
|
||
PHOTOS_PRESENT: ${val('photos_present')}
|
||
PHOTO_COUNT_APPROX: ${val('photo_count')}
|
||
SERVICES_LISTED: ${val('services')}
|
||
POSTS_SECTION_PRESENT: ${val('posts_section')}
|
||
POST_COUNT: ${val('post_count')}
|
||
MOST_RECENT_POST_DATE: ${val('recent_post')}
|
||
QA_STATUS: ${val('qa_status')}
|
||
ATTRIBUTES_HIGHLIGHTS: ${val('attributes')}
|
||
|
||
SECOND_LISTING_NEARBY: ${val('second_listing')}
|
||
SECOND_LISTING_NAMES: ${val('second_names')}
|
||
SECOND_LISTING_NOTES: ${val('second_notes')}
|
||
RAW_NOTES: ${val('raw_notes')}
|
||
`;
|
||
}
|
||
$('btnGenerate').onclick = () => { $('output').value = buildOutput(); $('status').textContent = 'Generated.'; };
|
||
$('btnCopy').onclick = async () => {
|
||
const t = $('output').value || buildOutput();
|
||
$('output').value = t;
|
||
try { await navigator.clipboard.writeText(t); $('status').textContent = 'Copied.'; } catch { $('status').textContent = 'Select-all + copy.'; }
|
||
};
|
||
$('btnClear').onclick = () => { $('gbpForm').reset(); $('output').value = ''; $('status').textContent = 'Cleared.'; };
|
||
</script>
|
||
</body>
|
||
</html>
|