Rewrite frontend types and HTTP client in src.
Export names match the leftover pages. Billing HTTP wrappers stay so gateway seams are not restyled here.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,533 @@
|
||||
/** Frontend mirrors of Periscope backend contracts (`periscopex.models` + HTTP). */
|
||||
|
||||
export type FindingStatus = "ERROR" | "WARNING" | "INFO";
|
||||
export type FindingClass = "RULE" | "RISK" | "REVIEW" | "INFO";
|
||||
export type DatasheetProvenance = "MANDATORY" | "RECOMMENDED" | "TYPICAL" | "EXAMPLE";
|
||||
export type EvidenceStatus = "SUFFICIENT" | "INSUFFICIENT";
|
||||
export type FindingReviewState = "open" | "false_positive" | "accepted" | "wontfix";
|
||||
|
||||
export interface Finding {
|
||||
finding_id: string | null;
|
||||
designator: string;
|
||||
mpn: string;
|
||||
aspect: string | null;
|
||||
finding: string;
|
||||
why: string;
|
||||
source_page: number | null;
|
||||
source_quote?: string;
|
||||
source_designator?: string | null;
|
||||
status: FindingStatus;
|
||||
recommendation?: string;
|
||||
reference: string;
|
||||
source?: string | null;
|
||||
net?: string | null;
|
||||
pins?: string[];
|
||||
rule_id?: string | null;
|
||||
cad_sheet?: string | null;
|
||||
cad_uuid?: string | null;
|
||||
variant?: string | null;
|
||||
facts?: string;
|
||||
requirement?: string;
|
||||
inference?: string;
|
||||
provenance?: DatasheetProvenance | null;
|
||||
finding_class?: FindingClass | null;
|
||||
confidence?: number | null;
|
||||
evidence_status?: EvidenceStatus | null;
|
||||
calculation?: string;
|
||||
assumptions?: string[];
|
||||
action?: string;
|
||||
decision_id?: string | null;
|
||||
suppressed?: boolean;
|
||||
}
|
||||
|
||||
export interface FindingComment {
|
||||
comment_id: string;
|
||||
finding_id: string;
|
||||
user_id: string;
|
||||
user_name: string;
|
||||
text: string;
|
||||
mentions: string[];
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface FindingReview {
|
||||
state: FindingReviewState;
|
||||
reason: string;
|
||||
user_id: string;
|
||||
user_name: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface ReportRelease {
|
||||
sha256: string;
|
||||
user_id: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface ValidationReport {
|
||||
project: string;
|
||||
timestamp: string;
|
||||
findings: Finding[];
|
||||
summary: Record<string, number>;
|
||||
coverage: Record<string, string[]>;
|
||||
review_errors?: Record<string, string>;
|
||||
not_reviewed?: { designator: string; reason: string }[];
|
||||
comments?: Record<string, FindingComment[]>;
|
||||
review_states?: Record<string, FindingReview>;
|
||||
release?: ReportRelease;
|
||||
}
|
||||
|
||||
export type NetType = "power" | "ground" | "signal" | "unknown";
|
||||
export type ComponentType =
|
||||
| "resistor"
|
||||
| "capacitor"
|
||||
| "inductor"
|
||||
| "ic"
|
||||
| "connector"
|
||||
| "crystal"
|
||||
| "discrete"
|
||||
| "transformer"
|
||||
| "fuse"
|
||||
| "switch"
|
||||
| "test_point"
|
||||
| "fiducial"
|
||||
| "mechanical"
|
||||
| "unknown";
|
||||
|
||||
export interface ComponentSpecs {
|
||||
specs_type: string;
|
||||
value_formatted?: string;
|
||||
values?: Record<string, string | number | null>;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface Component {
|
||||
reference: string;
|
||||
value: string;
|
||||
footprint: string;
|
||||
component_type: ComponentType;
|
||||
component_subtype: string | null;
|
||||
mpn: string | null;
|
||||
pins: Record<string, string>;
|
||||
specs: ComponentSpecs | null;
|
||||
}
|
||||
|
||||
export interface PinConnection {
|
||||
component_ref: string;
|
||||
pin_number: string;
|
||||
pin_name: string | null;
|
||||
}
|
||||
|
||||
export interface Net {
|
||||
name: string;
|
||||
net_type: NetType;
|
||||
voltage: number | null;
|
||||
pins: PinConnection[];
|
||||
}
|
||||
|
||||
export interface DesignGraph {
|
||||
components: Record<string, Component>;
|
||||
nets: Record<string, Net>;
|
||||
bom_fields?: Record<string, { mpn?: string | null; value?: string; dnp?: boolean; variant?: string | null }>;
|
||||
schematic_fields?: Record<string, { mpn?: string | null; value?: string; cad_uuid?: string; cad_sheet?: string }>;
|
||||
cad_index?: Record<string, { uuid?: string; sheet?: string }>;
|
||||
}
|
||||
|
||||
export interface BomSummaryRow {
|
||||
mpn: string | null;
|
||||
designators: string[];
|
||||
value: string;
|
||||
category: string | null;
|
||||
specs: Record<string, string | number> | null;
|
||||
description: string | null;
|
||||
hasDatasheet: boolean;
|
||||
}
|
||||
|
||||
export type ProjectStatus =
|
||||
| "draft"
|
||||
| "queued"
|
||||
| "running"
|
||||
| "complete"
|
||||
| "error"
|
||||
| "cancelled"
|
||||
| "paused_insufficient_credits"
|
||||
| "paused_by_user";
|
||||
|
||||
export interface PauseCheckpoint {
|
||||
paused_at?: string | null;
|
||||
paused_stage?: string | null;
|
||||
last_completed_label?: string | null;
|
||||
completed_review_refs?: string[];
|
||||
pending_review_refs?: string[];
|
||||
}
|
||||
|
||||
export interface SkippedComponent {
|
||||
identifier: string;
|
||||
stage: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface LcscPayload {
|
||||
mpn?: string | null;
|
||||
manufacturer?: string | null;
|
||||
package?: string | null;
|
||||
description?: string | null;
|
||||
category?: string | null;
|
||||
subcategory?: string | null;
|
||||
}
|
||||
|
||||
export interface ComponentMpnBuckets {
|
||||
ic: string[];
|
||||
passive: string[];
|
||||
simple: string[];
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
created: string;
|
||||
status: ProjectStatus;
|
||||
summary?: Record<string, number>;
|
||||
hasNetlist: boolean;
|
||||
hasBom: boolean;
|
||||
hasPcb?: boolean;
|
||||
datasheetCount: number;
|
||||
skippedComponents?: SkippedComponent[];
|
||||
completedReviewRefs?: string[];
|
||||
userId?: string;
|
||||
collaborators?: string[];
|
||||
creditsSpent?: number;
|
||||
totalCostUsd?: number | null;
|
||||
pauseCheckpoint?: PauseCheckpoint | null;
|
||||
pauseReason?: string | null;
|
||||
bomColumns?: { reference: string; mpn: string } | null;
|
||||
pipelineError?: string | null;
|
||||
lcscToMpn?: Record<string, string> | null;
|
||||
lcscPayloads?: Record<string, LcscPayload> | null;
|
||||
componentMpns?: ComponentMpnBuckets | null;
|
||||
periscopeVersion?: string | null;
|
||||
netlistFormat?: "pads" | "edif" | "kicad_xml" | "kicad_sexp" | "kicad_sch" | null;
|
||||
netlistSubdesigns?: string[] | null;
|
||||
placementStatus?: "draft" | "queued" | "running" | "complete" | "error" | "cancelled";
|
||||
placementState?: Record<string, unknown> | null;
|
||||
pcbStatus?: "draft" | "queued" | "running" | "complete" | "error" | "cancelled";
|
||||
pcbState?: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
export type RoleHint =
|
||||
| "decoupling"
|
||||
| "bulk"
|
||||
| "load_cap"
|
||||
| "filter"
|
||||
| "pullup"
|
||||
| "series"
|
||||
| "divider"
|
||||
| "bridge"
|
||||
| "crystal"
|
||||
| "other";
|
||||
|
||||
export interface PlacementSatellite {
|
||||
ref: string;
|
||||
component_type?: string;
|
||||
component_subtype?: string | null;
|
||||
nets?: string[];
|
||||
hop?: number;
|
||||
role_hint?: RoleHint;
|
||||
}
|
||||
|
||||
export interface PlacementIcGroup {
|
||||
ref: string;
|
||||
mpn?: string | null;
|
||||
component_subtype?: string | null;
|
||||
rank?: number;
|
||||
nets?: string[];
|
||||
satellites: PlacementSatellite[];
|
||||
layout_rules?: unknown[];
|
||||
assemble_order?: string[];
|
||||
}
|
||||
|
||||
export interface PlacementDomain {
|
||||
domain_id: string;
|
||||
power_nets: string[];
|
||||
ic_refs: string[];
|
||||
assemble_order: string[];
|
||||
}
|
||||
|
||||
export interface PlacementPlan {
|
||||
objective?: string;
|
||||
domains: PlacementDomain[];
|
||||
groups: PlacementIcGroup[];
|
||||
}
|
||||
|
||||
export interface PlacementProposal {
|
||||
ref: string;
|
||||
anchor_ref: string;
|
||||
rule_kind: string;
|
||||
max_distance_mm: number;
|
||||
proposed_x: number;
|
||||
proposed_y: number;
|
||||
layer?: string;
|
||||
basis?: string;
|
||||
}
|
||||
|
||||
export interface PlacementPack {
|
||||
objective?: string;
|
||||
status: "packed" | "skipped";
|
||||
skip_reason?: string | null;
|
||||
placements: PlacementProposal[];
|
||||
}
|
||||
|
||||
export interface EdifSubDesign {
|
||||
id: string | null;
|
||||
instance_count: number;
|
||||
designators: string[];
|
||||
}
|
||||
|
||||
export interface Collaborator {
|
||||
user_id: string;
|
||||
name: string | null;
|
||||
email: string | null;
|
||||
image_url: string | null;
|
||||
role: "owner" | "collaborator";
|
||||
}
|
||||
|
||||
export interface ApiLogEntry {
|
||||
timestamp: string;
|
||||
stage: string;
|
||||
identifier: string;
|
||||
model: string;
|
||||
input_tokens: number;
|
||||
output_tokens: number;
|
||||
duration_ms: number;
|
||||
stop_reason: string;
|
||||
skill_id?: string | null;
|
||||
turns?: number | null;
|
||||
error?: string | null;
|
||||
cost_usd?: number | null;
|
||||
}
|
||||
|
||||
export interface DeratingRow {
|
||||
designator: string;
|
||||
mpn: string | null;
|
||||
value_formatted: string | null;
|
||||
rated_voltage_v: number | null;
|
||||
operating_voltage_v: number | null;
|
||||
operating_voltage_source: string | null;
|
||||
net_plus: string | null;
|
||||
net_minus: string | null;
|
||||
dielectric_category: "ceramic" | "tantalum" | "electrolytic" | null;
|
||||
dielectric?: string | null;
|
||||
c_nominal_f?: number | null;
|
||||
dc_bias_factor?: number | null;
|
||||
c_eff_f?: number | null;
|
||||
c_eff_formatted?: string | null;
|
||||
dc_bias_model?: "stima" | null;
|
||||
stress?: "PASS" | "MARGIN" | "RISK" | "UNKNOWN";
|
||||
}
|
||||
|
||||
export interface DeratingSettings {
|
||||
ceramic: number;
|
||||
tantalum: number;
|
||||
electrolytic: number;
|
||||
}
|
||||
|
||||
export type ImpedanceKind = "microstrip" | "stripline" | "cpw" | "diff";
|
||||
|
||||
export interface ImpedanceTraceResult {
|
||||
kind: ImpedanceKind;
|
||||
z0?: number | null;
|
||||
zodd?: number | null;
|
||||
zeven?: number | null;
|
||||
zdiff?: number | null;
|
||||
w_mm?: number | null;
|
||||
s_mm?: number | null;
|
||||
}
|
||||
|
||||
export interface ImpedanceTarget {
|
||||
kind: string;
|
||||
w_mm: number | null;
|
||||
s_mm: number | null;
|
||||
z0: number | null;
|
||||
zdiff: number | null;
|
||||
formula: string;
|
||||
}
|
||||
|
||||
export interface ImpedanceStackupResult {
|
||||
targets: Record<string, ImpedanceTarget>;
|
||||
kicad_dru: string;
|
||||
}
|
||||
|
||||
export interface ImpedanceNetRow {
|
||||
net_name: string;
|
||||
length_mm?: number;
|
||||
branch_count?: number;
|
||||
is_differential?: boolean;
|
||||
partner_net_name?: string | null;
|
||||
topologies?: string[];
|
||||
z0_min_ohms?: number | null;
|
||||
z0_max_ohms?: number | null;
|
||||
z0_avg_ohms?: number | null;
|
||||
flags?: string[];
|
||||
sample_count?: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface ImpedanceNetsReport {
|
||||
pitch_mm: number;
|
||||
nets: ImpedanceNetRow[];
|
||||
skipped: string | null;
|
||||
}
|
||||
|
||||
export interface AntennaVerifyRow {
|
||||
ic_ref: string;
|
||||
pin: string;
|
||||
net: string;
|
||||
topology: string;
|
||||
parts: string[];
|
||||
target_z_ohm: number;
|
||||
status: "ok" | "warning" | "info";
|
||||
detail: string;
|
||||
feed_z0?: number | null;
|
||||
feed_length_mm?: number | null;
|
||||
marker_ref?: string | null;
|
||||
}
|
||||
|
||||
export interface AntennaGeometry {
|
||||
template: "ifa" | "meander" | "stub";
|
||||
fit: "ok" | "scaled" | "overflow" | "need_f0";
|
||||
segments: { points: number[][]; width_mm: number }[];
|
||||
total_length_mm?: number | null;
|
||||
length_ideal_mm?: number | null;
|
||||
scale?: number;
|
||||
svg?: string | null;
|
||||
kicad_mod?: string | null;
|
||||
footprint_name?: string | null;
|
||||
note?: string;
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
export interface AntennaDesignRecipe {
|
||||
status: "ready" | "need_pcb" | "need_stackup" | "need_marker";
|
||||
feed_point?: Record<string, unknown> | null;
|
||||
feed_line?: {
|
||||
kind: string;
|
||||
target_z_ohm: number;
|
||||
w_mm?: number | null;
|
||||
h_mm?: number | null;
|
||||
er?: number | null;
|
||||
t_mm?: number | null;
|
||||
} | null;
|
||||
radiator?: {
|
||||
length_mm_suggest?: number | null;
|
||||
f0_mhz?: number | null;
|
||||
note?: string;
|
||||
} | null;
|
||||
geometry?: AntennaGeometry | null;
|
||||
zone?: {
|
||||
net: string;
|
||||
layer: string;
|
||||
bbox_mm?: number[] | null;
|
||||
area_mm2?: number | null;
|
||||
} | null;
|
||||
keepout_checklist: string[];
|
||||
detail: string;
|
||||
}
|
||||
|
||||
export interface AntennaReport {
|
||||
verify: AntennaVerifyRow[];
|
||||
design: AntennaDesignRecipe | null;
|
||||
marker_help: string;
|
||||
}
|
||||
|
||||
export interface NetlistPreviewDesignator {
|
||||
ref: string;
|
||||
pins: { number: string; net_name: string }[];
|
||||
}
|
||||
|
||||
export interface PipelineSubstep {
|
||||
key: string;
|
||||
label: string;
|
||||
status: "pending" | "running" | "complete";
|
||||
cached?: boolean;
|
||||
}
|
||||
|
||||
export interface PipelineStep {
|
||||
title: string;
|
||||
description: string;
|
||||
substeps: PipelineSubstep[];
|
||||
status: "pending" | "running" | "complete";
|
||||
totalNew?: number;
|
||||
}
|
||||
|
||||
export interface CreditSnapshot {
|
||||
user_id: string;
|
||||
balance: number;
|
||||
plan: string;
|
||||
last_entry_ts?: string | null;
|
||||
next_expiry?: string | null;
|
||||
}
|
||||
|
||||
export interface CreditGrant {
|
||||
grant_id: string;
|
||||
user_id: string;
|
||||
amount_usd: number;
|
||||
remaining: number;
|
||||
granted_at: string;
|
||||
expires_at: string | null;
|
||||
source:
|
||||
| "top_up"
|
||||
| "trial_grant"
|
||||
| "admin_adjust"
|
||||
| "refund_system_error"
|
||||
| "pre_migration";
|
||||
stripe_event_id?: string | null;
|
||||
expired?: boolean;
|
||||
note?: string | null;
|
||||
}
|
||||
|
||||
export interface AutoTopupConfig {
|
||||
enabled: boolean;
|
||||
threshold_credits: number;
|
||||
amount_usd: number;
|
||||
has_payment_method: boolean;
|
||||
last_attempt_ts?: string | null;
|
||||
last_attempt_status?: "ok" | "failed" | "pending" | "";
|
||||
last_failure_reason?: string | null;
|
||||
}
|
||||
|
||||
export interface CreditLedgerEntry {
|
||||
user_id: string;
|
||||
timestamp: string;
|
||||
delta: number;
|
||||
balance_after: number;
|
||||
reason: string;
|
||||
run_id?: string | null;
|
||||
unit_id?: string | null;
|
||||
stripe_event_id?: string | null;
|
||||
note?: string | null;
|
||||
}
|
||||
|
||||
export interface CostItem {
|
||||
identifier: string;
|
||||
kind: string;
|
||||
api_cost_usd: number;
|
||||
source: "cache_hit" | "api_call" | "api_call_estimated";
|
||||
note?: string | null;
|
||||
}
|
||||
|
||||
export interface CostEstimate {
|
||||
api_cost_low: number;
|
||||
api_cost_high: number;
|
||||
api_cost_mid: number;
|
||||
credits_low: number;
|
||||
credits_high: number;
|
||||
credits_mid: number;
|
||||
breakdown: CostItem[];
|
||||
ic_count: number;
|
||||
simple_count: number;
|
||||
passive_count: number;
|
||||
cached_ic_count: number;
|
||||
cached_simple_count: number;
|
||||
cached_passive_count: number;
|
||||
review_ic_count: number;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import type { Finding, FindingClass, FindingStatus } from "./types";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function groupBy<T>(items: T[], key: (item: T) => string): Record<string, T[]> {
|
||||
const out: Record<string, T[]> = {};
|
||||
for (const item of items) {
|
||||
const k = key(item);
|
||||
(out[k] ??= []).push(item);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const BY_STATUS: Record<FindingStatus, number> = { ERROR: 0, WARNING: 1, INFO: 2 };
|
||||
const BY_CLASS: Record<FindingClass, number> = { RULE: 0, RISK: 1, REVIEW: 2, INFO: 3 };
|
||||
|
||||
export function sortFindings(findings: Finding[]): Finding[] {
|
||||
return [...findings].sort((a, b) => {
|
||||
const status = BY_STATUS[a.status] - BY_STATUS[b.status];
|
||||
if (status) return status;
|
||||
const ac = BY_CLASS[a.finding_class ?? "INFO"] ?? 9;
|
||||
const bc = BY_CLASS[b.finding_class ?? "INFO"] ?? 9;
|
||||
if (ac !== bc) return ac - bc;
|
||||
return (
|
||||
(a.designator || "").localeCompare(b.designator || "") ||
|
||||
(a.rule_id || "").localeCompare(b.rule_id || "") ||
|
||||
(a.finding || "").localeCompare(b.finding || "")
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function getFindingKey(finding: Finding, index: number): string {
|
||||
return finding.finding_id ?? `${finding.designator}-idx-${index}`;
|
||||
}
|
||||
|
||||
export function subtypeLabel(subtype: string | null): string {
|
||||
if (!subtype) return "";
|
||||
const last = subtype.split(".").pop() ?? subtype;
|
||||
return last.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
"""Native frontend types and API client live under periscope/src."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SRC = ROOT / "periscope" / "src" / "frontend" / "src" / "lib"
|
||||
DEP = ROOT / "periscope" / "dependency" / "frontend" / "src" / "lib"
|
||||
|
||||
|
||||
def _exports(path: Path) -> set[str]:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
names = set(re.findall(r"^export (?:async )?function (\w+)", text, re.M))
|
||||
names |= set(re.findall(r"^export (?:interface|type|class|const) (\w+)", text, re.M))
|
||||
return names
|
||||
|
||||
|
||||
def test_frontend_lib_is_src():
|
||||
for name in ("api.ts", "types.ts", "utils.ts"):
|
||||
path = SRC / name
|
||||
assert path.is_file(), name
|
||||
head = path.read_text(encoding="utf-8")[:400]
|
||||
assert "Native Periscope overlay" not in head
|
||||
|
||||
|
||||
def test_api_keeps_page_exports():
|
||||
src = _exports(SRC / "api.ts")
|
||||
dep = _exports(DEP / "api.ts")
|
||||
missing = dep - src
|
||||
assert not missing, sorted(missing)
|
||||
|
||||
|
||||
def test_types_keep_page_exports():
|
||||
src = _exports(SRC / "types.ts")
|
||||
dep = _exports(DEP / "types.ts")
|
||||
missing = dep - src
|
||||
assert not missing, sorted(missing)
|
||||
Reference in New Issue
Block a user