Spaces:
Running
Running
Download engine.mjs from Hugdrigo/hubcard-inspector-pro: direct link, hf CLI and curl.
- Browser
- Download file 12.2 kB
-
https://huggingface.co/spaces/Hugdrigo/hubcard-inspector-pro/resolve/main/engine.mjs
- Command line
-
hf download hf://spaces/Hugdrigo/hubcard-inspector-pro/engine.mjs
-
curl -L -o engine.mjs https://huggingface.co/spaces/Hugdrigo/hubcard-inspector-pro/resolve/main/engine.mjs
12.2 kB
| /** | |
| * HubCard Inspector Pro v2.0 | |
| * Deterministic signals about documentation presence, never certification. | |
| * This module has no DOM access and runs in browsers and Node for testing. | |
| */ | |
| export const VERSION='2.0.0'; | |
| const colors=['red','yellow','green','blue','indigo','purple','pink','gray']; | |
| export const HF_SPACE_COLORS=colors; | |
| // Rules are based on documentation practices, not claims of scientific truth. | |
| // Each group weights sum to 100 and the presence check is intentionally conservative. | |
| export const RULES={ | |
| model:[ | |
| ['license','License metadata',8,'meta',['license'],'Declare a clear redistribution license in YAML metadata.','identity'], | |
| ['language','Language metadata',5,'meta',['language'],'Identify supported languages.','identity'], | |
| ['task','Task or library metadata',7,'meta',['pipeline_tag','library_name'],'Describe the ML task or library.','identity'], | |
| ['overview','Model description',10,'section',['model details','model description','overview','about the model','introduction'],'Describe the architecture, author and purpose.','transparency'], | |
| ['intended','Intended uses',10,'section',['intended use','intended uses','direct use','use cases','applications'],'Document suitable uses.','transparency'], | |
| ['limitations','Limitations and out-of-scope',12,'section',['limitations','out-of-scope','out of scope','risks and limitations','limitations and bias'],'Document misuse risks and limitations.','risks'], | |
| ['data','Training data provenance',10,'section',['training data','datasets','data sources','dataset','data provenance'],'Identify provenance and conditions of training data.','reproducibility'], | |
| ['method','Training methodology',10,'section',['training procedure','training details','methodology','training','implementation details','architecture'],'Document how the model was made.','reproducibility'], | |
| ['evaluation','Evaluation and test design',12,'section',['evaluation','results','benchmark','metrics','testing data'],'Report testing data, metrics and known gaps.','reproducibility'], | |
| ['usage','Usage or inference example',8,'section',['how to use','usage','examples','usage example','getting started','inference'],'Provide a reproducible usage example.','utility'], | |
| ['ethics','Ethics and bias',5,'section',['ethical considerations','responsible use','bias','privacy','societal impact'],'Document foreseeable harm and ethical limits.','risks'], | |
| ['version','Version or maintenance',3,'section',['versions','version','changelog','provenance','maintenance','citation'],'Explain maintenance and provenance.','identity'] | |
| ], | |
| dataset:[ | |
| ['license','License metadata',10,'meta',['license'],'Document permission to redistribute the dataset.','identity'], | |
| ['language','Language metadata',5,'meta',['language'],'State languages where relevant.','identity'], | |
| ['task','Task metadata',5,'meta',['task_categories','task_ids'],'Identify intended data tasks.','identity'], | |
| ['description','Dataset description',10,'section',['dataset description','description','overview','about the dataset'],'Explain the data content and collection scope.','transparency'], | |
| ['source','Data source or provenance',12,'section',['source data','source','provenance','dataset creation','data sources'],'Document where data originated.','reproducibility'], | |
| ['processing','Curation methodology',10,'section',['data processing','methodology','curation','collection process','preprocessing'],'Describe sampling, cleanup and labeling.','reproducibility'], | |
| ['schema','Features and schema',8,'section',['features','schema','fields','data fields','columns'],'Document each field and type.','utility'], | |
| ['splits','Dataset size and splits',8,'section',['dataset splits','splits','data splits','data instances','size'],'Specify sample counts and partitions.','utility'], | |
| ['uses','Intended uses',10,'section',['intended use','intended uses','use cases','supported tasks'],'Explain intended and excluded use.','transparency'], | |
| ['limits','Limitations and bias',12,'section',['limitations','bias','risks','known limitations','dataset limitations'],'State biases, missingness and failure modes.','risks'], | |
| ['examples','Representative examples',5,'section',['examples','sample records','sample','data instances'],'Show representative records without leaking PII.','utility'], | |
| ['privacy','Privacy and sensitive content',5,'section',['privacy','sensitive data','personal data','pii','consent'],'Document privacy risks and permissions.','risks'] | |
| ], | |
| space:[ | |
| ['license','License metadata',8,'meta',['license'],'Declare a software license.','identity'], | |
| ['title','Title and description',8,'meta',['title','short_description'],'Add readable metadata for discovery.','identity'], | |
| ['purpose','Problem and purpose',12,'section',['purpose','problem','what it does','overview','about'],'Explain the actual problem solved.','transparency'], | |
| ['usage','How to use',12,'section',['how to use','usage','instructions','getting started'],'Provide simple step-by-step instructions.','utility'], | |
| ['example','Example or demo',8,'section',['examples','example','demo','try it'],'Provide a real end-to-end walkthrough.','utility'], | |
| ['architecture','Technology and architecture',10,'section',['architecture','technology','technologies','implementation','how it works'],'Explain the main components and runtime.','reproducibility'], | |
| ['limits','Limitations',12,'section',['limitations','known issues','caveats','constraints'],'Describe what the application cannot do.','risks'], | |
| ['privacy','Privacy and security',10,'section',['privacy','security','data handling'],'State whether user inputs leave the browser.','risks'], | |
| ['sources','Attribution and data sources',8,'section',['source attribution','sources','data sources','attribution','credits','references'],'Credit upstream code, models and datasets.','reproducibility'], | |
| ['maintenance','Maintenance and version',6,'section',['maintenance','version','status','roadmap','changelog'],'Describe support, status and versioning.','identity'], | |
| ['responsible','Responsible-use guidance',6,'section',['responsible use','responsible ai','ethics','risks','safety'],'Explain consequential-use risks.','risks'] | |
| ] | |
| }; | |
| function stripQuotes(v){return String(v??'').trim().replace(/^(['"])(.*)\1$/,'$2');} | |
| export function parseFrontmatter(readme){ | |
| const src=String(readme??'').replace(/^\uFEFF/,''); | |
| const match=/^---\s*\r?\n([\s\S]*?)\r?\n---(?:\s*\r?\n|$)/.exec(src); | |
| if(!match) return {}; | |
| const out={};let active=null; | |
| for(const raw of match[1].split(/\r?\n/)){ | |
| if(/^\s*#/.test(raw)||!raw.trim())continue; | |
| const key=/^([a-zA-Z_][\w-]*)\s*:\s*(.*)$/.exec(raw); | |
| if(key){ active=key[1];const val=key[2].trim(); | |
| if(!val){out[active]=[];continue;} | |
| if(/^\[.*\]$/.test(val))out[active]=val.slice(1,-1).split(',').map(stripQuotes).filter(Boolean); | |
| else out[active]=stripQuotes(val.split(/\s+#/)[0]); | |
| }else if(active && /^\s*-\s+/.test(raw)){ | |
| if(!Array.isArray(out[active]))out[active]=[]; | |
| out[active].push(stripQuotes(raw.trim().slice(2))); | |
| } | |
| } | |
| return out; | |
| } | |
| export function headings(readme){ | |
| return String(readme??'').split(/\r?\n/).map((line,index)=>({line:line.trim(),number:index+1})) | |
| .filter(item=>/^#{1,6}\s+\S/.test(item.line)).map(item=>({...item,heading:item.line.replace(/^#{1,6}\s+/,'').replace(/\s+#+$/,'').trim()})); | |
| } | |
| function normalized(v){return String(v).normalize('NFD').replace(/[\u0300-\u036f]/g,'').toLowerCase().replace(/[^a-z0-9]+/g,' ').trim();} | |
| function headingHit(hs,patterns){ | |
| const ps=patterns.map(normalized); | |
| return hs.find(x=>ps.some(p=>normalized(x.heading)===p || normalized(x.heading).startsWith(p+' ') || normalized(x.heading).endsWith(' '+p))); | |
| } | |
| function nonempty(v){return v!==undefined && v!==null && (!Array.isArray(v) || v.length>0) && String(v).trim()!=='';} | |
| export function auditCard(type,readme,externalMetadata={},repoName='Untitled card'){ | |
| if(!RULES[type])throw new Error('Unsupported repository type'); | |
| const m={...externalMetadata,...parseFrontmatter(readme)}; | |
| const hs=headings(readme); | |
| const checks=RULES[type].map(([id,label,points,kind,targets,remedy,category])=>{ | |
| let evidence='',pass=false; | |
| if(kind==='meta'){ | |
| const key=targets.find(k=>nonempty(m[k])); | |
| if(key){pass=true;evidence=`Metadata field: ${key}`;} | |
| }else{ | |
| const item=headingHit(hs,targets); | |
| if(item){pass=true;evidence=`Heading at line ${item.number}: ${item.heading}`;} | |
| } | |
| return {id,label,points,earned:pass?points:0,pass,category,remedy,evidence,headingSuggestion:targets[0]}; | |
| }); | |
| const score=checks.reduce((s,c)=>s+c.earned,0); | |
| const total=checks.reduce((s,c)=>s+c.points,0); | |
| const missing=checks.filter(c=>!c.pass); | |
| return {type,repoName,score,possible:total,checks,missing,passed:checks.length-missing.length,totalChecks:checks.length,metadata:m, | |
| disclaimer:'Heuristic documentation coverage only. Not scientific validation, compliance, fairness or safety certification.'}; | |
| } | |
| export function buildRemediation(result){ | |
| const lines=['## Suggested documentation sections','', '> Replace placeholders with verified facts. Do not publish unsupported claims.','']; | |
| const seen=new Set(); | |
| for(const c of result.missing){ | |
| if(['license','language','task','title'].includes(c.id)){ | |
| lines.push(`- **${c.label}:** ${c.remedy} Add truthful YAML metadata.`,` `);continue; | |
| } | |
| const heading=c.headingSuggestion.replace(/\b\w/g,k=>k.toUpperCase()); | |
| if(seen.has(heading))continue; | |
| seen.add(heading); | |
| lines.push(`### ${heading}`,'',`[To be completed with verified information: ${c.remedy}]`,''); | |
| } | |
| return lines.join('\n'); | |
| } | |
| export function buildReport(results){ | |
| const lines=['# HubCard Inspector Pro - Documentation Audit','',`Report created: ${new Date().toISOString()}`,'', '**Interpretation:** Scores are heuristic presence checks, not content verification or safety certifications.','']; | |
| for(const r of results){ | |
| lines.push(`## ${r.repoName} (${r.type})`,'',`Documentation coverage: **${r.score}/${r.possible}**. Signals detected: ${r.passed}/${r.totalChecks}.`,'','| Signal | Points | Evidence |','|---|---:|---|'); | |
| for(const c of r.checks)lines.push(`| ${c.label.replace(/\|/g,'/')} | ${c.earned}/${c.points} | ${(c.evidence||'Not detected').replace(/\|/g,'/')} |`); | |
| lines.push('','### Improvement priorities'); | |
| if(!r.missing.length)lines.push('- No missing signals under this rubric. Verify substantive quality separately.'); | |
| for(const c of r.missing)lines.push(`- **${c.label} (${c.points} pts):** ${c.remedy}`); | |
| lines.push(''); | |
| } | |
| return lines.join('\n'); | |
| } | |
| export function csvReport(results){ | |
| const q=v=>'"'+String(v??'').replace(/"/g,'""')+'"'; | |
| return [['repository','type','score','signal','points_earned','points_possible','evidence','recommendation'].map(q).join(',')] | |
| .concat(results.flatMap(r=>r.checks.map(c=>[r.repoName,r.type,r.score,c.label,c.earned,c.points,c.evidence,c.pass?'':c.remedy].map(q).join(',')))).join('\n'); | |
| } | |
| export function parseRepoLine(raw,defaultType='model'){ | |
| let s=String(raw).trim(),type=defaultType; | |
| const prefix=/^(model|dataset|space)\s*:\s*/i.exec(s); | |
| if(prefix){type=prefix[1].toLowerCase();s=s.slice(prefix[0].length).trim();} | |
| s=s.replace(/^https?:\/\/(?:www\.)?huggingface\.co\//i,''); | |
| if(s.startsWith('datasets/')){type='dataset';s=s.slice(9);} | |
| else if(s.startsWith('spaces/')){type='space';s=s.slice(7);} | |
| s=s.split(/[?#]/)[0].replace(/\/(?:tree|blob|resolve|raw)\/.*$/,'').replace(/\/$/,''); | |
| if(!/^[\w.-]+\/[\w.-]+$/.test(s))throw new Error(`Invalid repository: ${raw}`); | |
| return {type,id:s}; | |
| } | |
| export function validateSpaceReadme(text){ | |
| const m=parseFrontmatter(text),errors=[]; | |
| if(m.sdk!=='static')errors.push('sdk must be static'); | |
| for(const k of ['colorFrom','colorTo'])if(m[k]&&!colors.includes(m[k]))errors.push(`${k} is not permitted`); | |
| if(!m.short_description||m.short_description.length>60)errors.push('short_description must be 1–60 characters'); | |
| if(!m.app_file)errors.push('app_file is missing'); | |
| return {valid:errors.length===0,errors,metadata:m}; | |
| } | |