BizSpring.ai

지식센터 / 레퍼런스

STD-PARTNER-JSONLD-FILL v1.0

# 📐 STD-PARTNER-JSONLD-FILL v1.0 — 사이트 풍부 schema 적재 표준 > Track : `T-PARTNER-JSONLD-FILL` > 작성 : 2026-05-10 KST | 검증 사례: bizspring.co.kr 70 URL > 적용

핵심 요약

    # 📐 STD-PARTNER-JSONLD-FILL v1.0 — 사이트 풍부 schema 적재 표준

    > Track : T-PARTNER-JSONLD-FILL

    > 작성 : 2026-05-10 KST | 검증 사례: bizspring.co.kr 70 URL

    > 적용 : 모든 GEOcare 등록 사이트 (PREMIUM_GEO 파트너 사이트 포함)

    > 짝 표준 : STD-USER-FLOW v1.0, STD-SITE-GEO-FLOW v1.1, STD-DEPLOY v1.1, STD-EDGE-01

    ---

    0. 적용 시점

    다음 중 하나라도 해당 시 본 표준 적용:

    1. 모델 — 3 SSOT (UPSERT 통일)

    ┌──────────────────────────────────────────────────────────────┐
    │ ① gp_geoex_site_jsonld    (UNIQUE site_domain)               │
    │   - Organization + WebSite (도메인 단위, 1행)                  │
    │   - 메인 페이지 정보 흡수 가능                                  │
    ├──────────────────────────────────────────────────────────────┤
    │ ② gp_geoex_brand_jsonld   (UNIQUE url_prefix)                │
    │   - Brand × N (멀티브랜드 사이트는 여러 행)                      │
    │   - URL prefix longest match로 결정                            │
    ├──────────────────────────────────────────────────────────────┤
    │ ③ gp_geoex_eeat_analysis  (UNIQUE site+url, 기존 테이블)       │
    │   - 신규 컬럼 3개:                                              │
    │     • generated_jsonld (jsonb)        ← 페이지 schema array     │
    │     • jsonld_schema_types (text[])                             │
    │     • jsonld_generated_at (timestamptz)                        │
    │   - WebPage / FAQPage / Article 등 모든 page-level             │
    └──────────────────────────────────────────────────────────────┘
    
    [송출 EF]
    EF v5 (geobh-jsonld) → RPC v3 (get_jsonld_by_url_v3)
      → 3 SSOT 병합 → @graph 단일 응답 (?pretty=1 옵션)
    
    [배제]
    gp_geoex_jsonld_log
      - Read-only archive (이력 테이블)
      - 신규 INSERT 금지 (가비지 발생 패턴)
      - dup_ratio 2.43 vs eeat_analysis 1.00 (UPSERT 모범)

    2. 적용 절차 (신규 사이트)

    2-1. siteGEO 적재 (1회, 도메인 단위)

    INSERT INTO gp_geoex_site_jsonld (site_domain, organization_schema, website_schema, source_kpi, created_by)
    VALUES (
      'https://example.com',
      jsonb_build_object(
        '@id', 'https://example.com/#organization',
        '@type', 'Organization',
        'name', '브랜드명',
        'alternateName', jsonb_build_array('alias1', 'alias2'),
        'url', 'https://example.com',
        'description', '...',
        'knowsAbout', jsonb_build_array('핵심키워드1', '핵심키워드2'),
        'makesOffer', jsonb_build_array(
          jsonb_build_object('@type', 'Offer', 'itemOffered', jsonb_build_object('@type', 'Service', 'name', '제품1'))
        )
      ),
      jsonb_build_object(
        '@id', 'https://example.com/#website',
        '@type', 'WebSite',
        'url', 'https://example.com',
        'name', '...',
        'inLanguage', 'ko-KR',
        'publisher', jsonb_build_object('@id', 'https://example.com/#organization'),
        'potentialAction', jsonb_build_object(
          '@type', 'SearchAction',
          'target', jsonb_build_object('@type', 'EntryPoint', 'urlTemplate', 'https://example.com/search?q={search_term_string}'),
          'query-input', 'required name=search_term_string'
        )
      ),
      'manual',
      'OPS or Claude'
    )
    ON CONFLICT (site_domain) DO UPDATE 
      SET organization_schema = EXCLUDED.organization_schema,
          website_schema = EXCLUDED.website_schema;

    2-2. brandGEO 적재 (브랜드별 1회)

    INSERT INTO gp_geoex_brand_jsonld (site_domain, brand_settings_id, url_prefix, brand_name, brand_schema, source_kpi)
    SELECT 
      '<host>',
      bs.id,
      bs.site_domain,
      bs.brand_name,
      jsonb_build_object(
        '@id', rtrim(bs.site_domain, '/') || '/#brand',
        '@type', 'Brand',
        'name', bs.brand_name,
        'alternateName', COALESCE(to_jsonb(bs.brand_aliases), '[]'::jsonb),
        'parentOrganization', jsonb_build_object('@id', '<host>/#organization'),
        'description', '...',
        'knowsAbout', COALESCE(to_jsonb(bs.brand_keywords), '[]'::jsonb),
        'makesOffer', CASE 
          WHEN bs.product_names IS NOT NULL AND array_length(bs.product_names, 1) > 0 
          THEN (SELECT jsonb_agg(jsonb_build_object('@type', 'Offer', 'itemOffered', jsonb_build_object('@type', 'Service', 'name', p))) FROM unnest(bs.product_names) p)
          ELSE '[]'::jsonb
        END
      ),
      'manual'
    FROM gp_geoex_brand_settings bs
    WHERE bs.site_domain LIKE '<host>%'
    ON CONFLICT (url_prefix) DO UPDATE
      SET brand_schema = EXCLUDED.brand_schema,
          brand_name = EXCLUDED.brand_name;

    2-3. URL-specific UPSERT (eeat_analysis 활용, 일괄)

    WITH urls AS (
      SELECT DISTINCT ON (ee.url) 
        ee.id, ee.url, ee.site_domain,
        ua.title, ua.page_type, ua.total_score
      FROM gp_geoex_eeat_analysis ee
      LEFT JOIN gp_geoex_url_analysis ua 
        ON ua.url = ee.url AND ua.site_domain LIKE '<host>%'
      WHERE ee.site_domain LIKE '<host>%'
      ORDER BY ee.url, ua.total_score DESC NULLS LAST
    ),
    url_brand AS (
      SELECT u.*, br.brand_schema, br.url_prefix
      FROM urls u
      LEFT JOIN LATERAL (
        SELECT brand_schema, url_prefix
        FROM gp_geoex_brand_jsonld
        WHERE (u.url LIKE url_prefix || '%') OR (u.url = rtrim(url_prefix, '/'))
        ORDER BY length(url_prefix) DESC LIMIT 1
      ) br ON true
    )
    UPDATE gp_geoex_eeat_analysis ee
    SET 
      generated_jsonld = jsonb_build_array(
        jsonb_build_object(
          '@id', ub.url || '#webpage',
          '@type', 'WebPage',
          'url', ub.url,
          'name', COALESCE(NULLIF(ub.title, ''), '<host_name>'),
          'description', COALESCE(ub.brand_schema->>'description', '<fallback>'),
          'isPartOf', jsonb_build_object('@id', '<host>/#website'),
          'about', jsonb_build_object('@id', COALESCE(ub.brand_schema->>'@id', '<host>/#organization')),
          'inLanguage', 'ko-KR'
        )
      ),
      jsonld_schema_types = ARRAY['WebPage'],
      jsonld_generated_at = NOW()
    FROM url_brand ub
    WHERE ee.id = ub.id;

    3. 검증 4단계

    단계검증SQL/명령
    13 SSOT 적재 카운트SELECT COUNT FROM site_jsonld / brand_jsonld / eeat_analysis WHERE generated_jsonld IS NOT NULL
    2RPC v3 호출 (5+ URL 샘플)SELECT * FROM get_jsonld_by_url_v3('<sample_url>')
    3EF v5 직접 호출curl "schema.geocare.ai/functions/v1/geobh-jsonld?url=<url>&pretty=1"
    4사이트 view-source (PHP 캐시 만료 후)브라우저 view-source — <script type="application/ld+json"> 풍부 schema

    4. 환각 방지 정책 (G-02 ABSOLUTE RULES)

    본 표준 적용 시 다음 금지:

    본 표준은 WebPage 기본 schema만 일괄 자동 적재. 페이지 단위 풍부 schema(FAQPage/Article 등)는 별 트랙 (사람 또는 LLM 검수 후).

    5. 변경 시 영향 (Cross-Room)

    본 표준 변경 시:

    6. 검증 사례

    사이트적재검증
    bizspring.co.krsite (1) + brand (3) + eeat_analysis (69 URL)✅ EF v5 6 시나리오 ALL PASS (5/10)

    7. RPC v3 정의 본문 (재현 가능)

    CREATE OR REPLACE FUNCTION get_jsonld_by_url_v3(p_url TEXT)
    RETURNS TABLE (
      cached BOOLEAN,
      data JSONB
    ) AS $$
    DECLARE
      v_url_norm TEXT;
      v_site_domain TEXT;
      v_org JSONB;
      v_website JSONB;
      v_brand JSONB;
      v_page JSONB;
      v_graph JSONB;
    BEGIN
      v_url_norm := CASE 
        WHEN p_url ~ '^https?://[^/]+/?$' THEN p_url
        ELSE rtrim(p_url, '/')
      END;
      v_site_domain := substring(p_url FROM '^(https?://[^/]+)');
      
      SELECT organization_schema, website_schema 
        INTO v_org, v_website
      FROM gp_geoex_site_jsonld 
      WHERE site_domain = v_site_domain LIMIT 1;
      
      SELECT brand_schema INTO v_brand
      FROM gp_geoex_brand_jsonld
      WHERE (p_url LIKE url_prefix || '%') OR (p_url = rtrim(url_prefix, '/'))
      ORDER BY length(url_prefix) DESC LIMIT 1;
      
      SELECT generated_jsonld INTO v_page
      FROM gp_geoex_eeat_analysis
      WHERE url IN (p_url, v_url_norm, v_url_norm || '/')
        AND generated_jsonld IS NOT NULL
        AND jsonb_typeof(generated_jsonld) = 'array'
      ORDER BY jsonld_generated_at DESC NULLS LAST LIMIT 1;
      
      v_graph := '[]'::jsonb;
      IF v_org IS NOT NULL THEN v_graph := v_graph || jsonb_build_array(v_org); END IF;
      IF v_website IS NOT NULL THEN v_graph := v_graph || jsonb_build_array(v_website); END IF;
      IF v_brand IS NOT NULL THEN v_graph := v_graph || jsonb_build_array(v_brand); END IF;
      IF v_page IS NOT NULL THEN v_graph := v_graph || v_page; END IF;
      
      IF jsonb_typeof(v_graph) = 'array' AND v_graph != '[]'::jsonb THEN
        RETURN QUERY SELECT 
          (v_page IS NOT NULL),
          jsonb_build_array(jsonb_build_object(
            '@context', 'https://schema.org',
            '@graph', v_graph
          ));
      END IF;
    END;
    $$ LANGUAGE plpgsql STABLE;
    
    GRANT EXECUTE ON FUNCTION get_jsonld_by_url_v3(TEXT) TO anon, authenticated, service_role;

    8. EF v5 관련

    9. 적용 워크플로우 (신규 사이트별)

    1. 사이트가 GEOcare 등록 + EEAT 분석 완료 확인
    2. brand_settings 등록 확인 (멀티브랜드면 N행)
    3. site_jsonld INSERT (도메인 1회)
    4. brand_jsonld INSERT (브랜드별)
    5. eeat_analysis UPSERT (URL 일괄)
    6. 검증 4단계 완료
    7. (사이트 측) v2.10 PHP 박혀 있으면 캐시 만료 1h 후 라이브
       또는 즉시: 캐시 디렉터리 클리어 + view-source 재호출

    10. 변경 이력

    일자버전변경
    2026-05-10v1.0초판. T-BIZSPRING-JSONLD-FILL 검증 사례 기반. 3 SSOT UPSERT 모델. EF v5 / RPC v3.

    다른 표현: Markdown · JSON · 최종 갱신 2026-08-24