
    HE^jy                     ~	   U d Z ddlZddlZddlZddlZddlZddlmZm	Z	m
Z
mZmZ ddlmZ ddlmZ ddlmZmZ ddlmZ dZe	eef   Zd	ee   d
efdZde	eef   d
efdZde
e   d
e	eef   fdZded
ee	eef      fdZdeded
ee	eef      fdZdkdee
e      d
e	eef   fdZ e	eef   Z!e	eef   Z"g dZ#g dZ$e
eeef      e%d<   g dZ&e
eeef      e%d<   h dZ'ddddddd d!dd"dd#d#d#d$d%Z(d&ed'ed(ed)e	ee
e!   f   d*e	ee
e!   f   d
dfd+Z) ejT                  d,ejV                        d-f ejT                  d.ejV                        d/f ejT                  d0ejV                        d1f ejT                  d2ejV                        d3f ejT                  d4ejV                        d5f ejT                  d6ejV                        d7f ejT                  d8ejV                        d9f ejT                  d:ejV                        d;f ejT                  d<ejV                        d=f ejT                  d>ejV                        d?f ejT                  d@ejV                        dAfgZ,e
eejZ                  ef      e%dB<    ejT                  dCejV                        dDf ejT                  dEejV                        dFf ejT                  dGejV                        dHf ejT                  dIejV                        dJf ejT                  dKejV                        dLf ejT                  dMejV                        dNf ejT                  dOejV                        dPf ejT                  dQejV                        dRfgZ.e
eejZ                  ef      e%dS<   dTedUed
efdVZ/d&ed'ed(ed)e	ee
e!   f   d
df
dWZ0dXe	eef   de	eef   d)e	ee
e!   f   d*e	ee
e!   f   d
df
dYZ1de	eef   d)e	ee
e!   f   d
dfdZZ2dXe	eef   de	eef   d)e	ee
e!   f   d
dfd[Z3d	ed
efd\Z4h d]Z5de	eef   d^ee   d
ee	eef      fd_Z6d`ee   d^ee   d
ee   fdaZ7de	eef   d
e
e!   fdbZ8d	ed
ee9   fdcZ:d	ed
ee   fddZ;de	eef   dXe	eef   d
ee	eef      fdeZ<de	eef   d
e
e!   fdfZ=dge	ee
e!   f   d
e
e"   fdhZ>dXe	eef   diee	eef      d
ee
e"   e
e"   ee   f   fdjZ?y)luy  PSREF (psref.lenovo.com) spec-sheet enrichment.

Fills gaps our own outlet scrape never reports (chassis/security/ports/certs) by
resolving each catalog product's Machine Type (first 4 chars of `product_code`)
against Lenovo's undocumented PSREF JSON API and caching the flattened spec
sheet, keyed by MT (a spec sheet covers a whole product line, not one SKU).

Field-by-field research (which PSREF fields are flat-safe, which are genuinely
ambiguous, the two deterministic joins) lives in the `project_psref_feasibility`
memory — this module builds against that directly. Response shape below was
confirmed against the real API this session (not guessed from the memory's
prose description), e.g.:

  Suggest:      {"data": [{"ProductKey": "...", "MachineType": "21JR",
                            "info": {"page": "https://psref.lenovo.com/l/..."}}]}
  LoadSpecData: {"data": {"GeneralSpecData": [{"GeneralSpecJson": "<json string>"}]}}
                GeneralSpecJson parses to {"data": {"SpecData": [
                    {"L1": "Performance", "L2": [{"L2": "Chipset", "Features": [
                        {"FName": "Chipset", "Optional": "0", "FVs": [
                            {"FVGs": [{"FVGItem": [{"Att": "DefaultDescription",
                                                     "AttV": "AMD SoC..."}]}]}
                        ]}
                    ]}]}
                ]}}

ToS: Lenovo's Terms of Use bans "high volume" automated harvesting. This is 2
calls per unique MT, once, cached forever (~120 calls total as of this
writing) — flagged to the user before this runs against the live site for
real, same as the memory's own caveat.
    N)AnyDictListOptionalTuple)settings)db_connection)_logcurrent_time)_requestg?sreturnc                    | syt        j                  |       } t        j                  dd| t        j                  t        j
                  z        } t        j                  dd|       } t        j                  dd|       j                         } | S )u^  Some PSREF description fields (e.g. Docking) embed a live <a> tag pointing
    at their own accessories page — converted to `[[text|url]]` (not markdown's
    `[text](url)`: some of these real URLs contain literal parentheses, e.g.
    the BIOS Simulator link's "...(21Q6,21Q7)" segment, which breaks naive
    markdown-link parsing) so the frontend (lib/specValue.tsx) can render a
    real clickable link, instead of dead "please visit here" text with the
    href silently discarded. Any other/nested tags (a `<u>` almost always
    wraps the anchor's own text) are stripped as plain formatting noise. z'<a\s+[^>]*href="([^"]*)"[^>]*>(.*?)</a>z	[[\2|\1]])flagsz<[^>]+>\s+ )htmlunescaperesubISstripr   s    (/home/container/backend/scraper/psref.py_clean_textr   6   sr     aA
9<RTRVRVY[Y]Y]R]^A
z2q!A
vsA$$&AH    attrsc                     d| v r| d   S | j                         D cg c]  \  }}|dk7  s| }}}dj                  |      S c c}}w )u  Render one FV group's attrs as display text. Single-attribute features use
    the generic "DefaultDescription" key; composite ones (Weight, Dimensions) carry
    a "Models" qualifier alongside the real value — skip it here, it's metadata,
    not part of the visible value.DefaultDescriptionModelsz, )itemsjoin)r   kvpartss       r   _fv_textr(   H   sP    
 u$)** ;;=:=41aAMQ=E:99U ;s
   AA	spec_datac                 `   i }| D ]%  }|j                  dd      }|j                  dg       D ]  }|j                  dd      }|j                  dg       D ]  }|j                  d      }|sg }|j                  dg       D ]|  }	|	j                  dg       D ]e  }
i }|
j                  dg       D ]8  }|j                  d	      }t        |j                  d
            }|s1|s4|||<   : |sU|j                  |       g ~ |s||t        |      |j                  d      dk(  |d||<     ( |S )u{  Flatten PSREF's L1 -> L2 -> Feature -> FVs -> FVGs -> FVGItem nesting into
    {FName: {l1, l2, num_fvs, optional, values: [attrs_dict, ...]}}. Keyed by the
    Feature name (FName) directly, not nested under category — resolve_specs_for_laptop
    looks fields up by name; which display category each ends up in is decided there,
    independent of PSREF's own L1/L2 taxonomy.L1r   L2FeaturesFNameFVsFVGsFVGItemAttAttVr   1)l1l2num_fvsoptionalvalues)getr   appendlen)r)   flatr5   l1_namer6   l2_namefeaturefnamer9   fvfvgr   itemattattvs                  r   _flatten_spec_datarG   W   s4    "$D&&r"&&r"BffT2&G66*b1G,/1!++eR0B!vvfb102$'GGIr$:D"&((5/C#.txx/?#@D"t-1c
	 %;
 !"MM%0  2 1 !!"6{ 'J 73 >$U# 2 # 8 Kr   mtc                 P    t         j                   dt        j                  j	                          d}	 t        d|t         j                  d      }|j                  dk7  rt        d|j                   d         y	|j                         j                  d
      xs g }t         fd|D        d	      }|xs	 |r|d   nd	}|r|j                  d      sy	|d   |j                  d      xs i j                  d      dS # t        $ r}t        d  d|        Y d	}~y	d	}~ww xY w)u  Resolve a Machine Type to a PSREF `ProductKey` + spec-sheet page URL via
    the `Suggest` search endpoint. 100% hit rate across all 119 MTs ever seen in
    the catalog (per the feasibility sweep) — a miss here means a genuinely new
    MT PSREF hasn't indexed yet, not a bug.z1/api/search/DefinitionFilterAndSearch/Suggest?kw=z0&limit=6&IsPreviewProduct=true&SearchType=NormalGET   headerstimeout   z[PSREF] Suggest HTTP  for MT Ndatac              3   L   K   | ]  }|j                  d       k(  s|  yw)MachineTypeN)r:   ).0rrH   s     r   	<genexpr>z'resolve_machine_type.<locals>.<genexpr>   s"     GAAEE-,@B,Fas   $$r   
ProductKeyinfopage)product_keypage_urlz[PSREF] Suggest error for MT : )r   PSREF_BASE_URLurllibparsequoter   PSREF_HEADERSstatus_coder
   jsonr:   next	Exception)rH   urlresponseresultsexactrD   es   `      r   resolve_machine_typerk   }   s,    ""
# $||!!"%&&V	X E30F0FPRS3&()=)=(>hrdKL--/%%f-3GGN9wD488L1-&)/R44V<
 	
  ,RD1#67s%   AD >AD )D 	D%
D  D%rZ   c                    t         j                   dt        j                  j	                  |        }	 t        d|t         j                  d      }|j                  dk7  rt        d|j                   d|        y|j                         j                  d	      xs i j                  d
      xs g }|st        d| d|  d       y|d   j                  d      }|syt        j                  |      j                  d	      xs i j                  d      xs g }t        |      }|st        d| d|  d       y|S # t        $ r}t        d| d|        Y d}~yd}~ww xY w)zFetch and flatten the canonical per-product-family spec sheet (the same data
    that powers PSREF's own Spec tab and Compare feature).z-/api/product/Compare/LoadSpecData?ProductKey=rJ   rK   rL   rO   z[PSREF] LoadSpecData HTTP rP   NrQ   GeneralSpecDataz0[PSREF] LoadSpecData: no GeneralSpecData for MT  ()r   GeneralSpecJsonSpecDataz,[PSREF] LoadSpecData: empty SpecData for MT z"[PSREF] LoadSpecData error for MT r\   )r   r]   r^   r_   r`   r   ra   rb   r
   rc   r:   loadsrG   re   )	rZ   rH   rf   rg   generalraw_json	spec_listr=   rj   s	            r   fetch_load_spec_datarv      se    $$%%RSYS_S_SeSefqSrRs
tCE30F0FPRS3&-h.B.B-C8B4PQMMO''/52::;LMTRTCB4r+VWXY1:>>"34jj*..v6<"AA*MTRT	!),?t2k]RSTU 1"Rs;<s2   AD; <A
D; D; AD; 9D; ;	EEEmachine_typesc                 \   t               5 }|j                         }| rt        t        j	                  |             }n7|j                  d       |j                         D cg c]  }|d   s	|d    }}|st        d       dddcddd       S t        dt        |       d       d}d}t        t        dd	      }t        |      D ]  \  }}	||kD  r%t        |      |z
  }
t        d
| d| d|
 d        n	 t        |	      }t        j                  t               |s|dz  }t        d|	 d       mt!        |d   |	      }t        j                  t               ||dz  }|j                  d|	|d   |d   t#        j$                  |d      t'               f       |j)                          |dz  }t        d|	 dt        |       d       	 t        d| d|        ||dcddd       S c c}w # t*        $ r!}|dz  }t        d|	 d|        Y d}~Sd}~ww xY w# 1 sw Y   yxY w)uS  One-off backfill, same shape as enrichment.py's enrich_specs(): resolve +
    fetch + cache per Machine Type not already in psref_specs (or an explicit
    list). Spec sheets don't change post-announce, so this never re-fetches an
    MT that's already cached — call with explicit `machine_types` to force a
    refresh of specific ones.z
                SELECT DISTINCT substr(product_code, 1, 4) as mt
                FROM products
                WHERE substr(product_code, 1, 4) NOT IN (SELECT machine_type FROM psref_specs)
            r   z)[PSREF] No machine types need enrichment.)enrichedfailedNz[PSREF] Backfilling z machine type(s)...PSREF_MAX_FAILED   z[PSREF] z% failures exceeded PSREF_MAX_FAILED (u,   ) — PSREF may be down. Aborting this run, z? machine type(s) left uncached (picked up again next backfill).   z[PSREF] Could not resolve MT z via Suggest.rZ   z
                    INSERT OR REPLACE INTO psref_specs
                        (machine_type, product_key, page_url, spec_data, updated_at)
                    VALUES (?, ?, ?, ?, ?)
                r[   F)ensure_asciiz [PSREF] Cached spec data for MT rn   z	 fields).z[PSREF] Error enriching MT r\   z![PSREF] Backfill done. Enriched: z | Failed: )r	   cursorlistdictfromkeysexecutefetchallr
   r<   getattrr   	enumeraterk   timesleepPSREF_REQUEST_DELAYrv   rc   dumpsr   commitre   )rw   connr   targetsrowry   rz   
max_failedirH   	remainingresolvedr)   rj   s                 r   backfill_psref_specsr      sY    
D4==78GNN  
 *0):E):#c!fs1v):GE<= !Q/ 
  	#CL>1DEFX'91=
w'EAr
"L1,	vh&KJ< X==FK H@@
 >/3

./aKF8MJK0-1H"M	

./$aKF  
 ]+Z(JJyu= N	
 A7t2c)n=MYWXI (R 	0
+fXNO$7} 
 F^  >!22$b<==>s 
sn   AH" 
G0+G02H"A)H"9:G53H"4/G5#H"$A*G5H"0H"5	H>HH"HH""H+)	ProcessorMemoryStorageGraphicsDisplayConnectivityPhysicalPlatformPortsSecurityDurability & Certifications))Chipsetr   )z
Audio Chipr   )Speakersr   )
Microphoner   )Card Readerr   )Dockingr   )z
Max Memoryr   )zMax Storage Supportr   )zStorage Slotr   )zSecurity Chipr   )zPhysical Locksr   )ThinkShieldr   )zScreen-to-Body Ratior   )zMonitor Supportr   )zCase Materialr   )zDimensions (WxDxH)r   )zMil-Spec Testr   _FLAT_FIELDS))zStandard Portsr   )zOptional Portsr   )zBIOS Securityr   )zOther Securityr   )Green Certificationsr   )Other Certificationsr   _ENUM_FIELDS>   SIM Card
Case ColorMemory TypeBattery LifeStorage TypeBase WarrantyPower AdapterOperating SystemProcessor FamilyWLAN + BluetoothSystem ManagementFingerprint ReaderKeyboard BacklightBundled AccessoriesWWANCameraWeightBatteryr   r   Keyboardr   TouchscreenMemory SlotsSmart Card Readerr   r   r   r   r   r   r   r   r   )r   r   zRemovable StoragezMulti-MediazInput Device
MechanicalNetworkr   r   r   zOperating EnvironmentSustainabilityr   r   r   fieldcategorylabelextra	referencec                 Z   d | d   D        D cg c]  }|s|	 }}|syt        |      dk(  r(|j                  |g       j                  ||d   d       yt        t        j                  |            }|j                  |g       j                  |dj                  |      d       yc c}w )u   Universal rule for every singular field: check the live num_fvs for this
    specific product. ==1 -> confirmed, flat row. >1 -> all documented variants,
    joined (never a bare em dash — see feedback_no_em_dashes memory).c              3   2   K   | ]  }t        |        y wNr(   rT   r&   s     r   rV   z!_emit_singular.<locals>.<genexpr>y       >o!o   r9   Nr}   r   r   valuez or )r<   
setdefaultr;   r   r   r   r$   )r   r   r   r   r   ttextsuniqs           r   _emit_singularr   o  s     ?eHo>D>1!Q>ED
5zQ2&--a.QRDMM%()Xr*11EFKKX\L]2^_ Es
   B(B(thunderboltzThunderbolt / USB-Czusb-?c|usb4zUSB-Czusb-?a\bzUSB-AhdmiHDMIzdisplayport|\bdp\bDisplayPortzethernet|rj-?45Ethernetz*headphone|microphone|audio jack|combo jackz
Audio Jackz
smart cardr   zcard reader|sd cardr   znano-?sim|sim cardzSIM Slotz\bvga\bVGA_PORT_LABEL_PATTERNSzenergy starzENERGY STARepeatEPEATz\berp\bErProhsRoHSz\btco\bzTCO CertifiedeyesafeEyesafeu   t[üu]vu   TÜV Rheinlandzmil-stdzMIL-STD_CERT_LABEL_PATTERNS
field_nametextc                     d| j                         v rt        nt        }|D ]  \  }}|j                  |      s|c S  | S )Nport)lowerr   r   search)r   r   patternspatternr   s        r   _enum_item_labelr     sB    '-1A1A1C'C#I]H">>$L # r   c                     d | d   D        D cg c]  }|s|	 }}|D ]1  }|j                  |g       j                  t        ||      ||d       3 yc c}w )uw  One row per item (Product Details/Compare's "separated" convention), each
    tagged with the source PSREF field name via "field" — Product Details ignores
    it, but Compare's fixed-row-per-laptop table can't show a variable-length
    per-item list per column, so it groups back by "field" into one compact
    joined row instead (see lib/compare.ts's psrefFieldJoined).c              3   2   K   | ]  }t        |        y wr   r   r   s     r   rV   z_emit_enum.<locals>.<genexpr>  r   r   r9   )r   r   r   N)r   r;   r   )r   r   r   r   r   r   r   s          r   
_emit_enumr     sa     ?eHo>D>1!Q>ED2&--&ud3dUS	
  Es
   AAlaptopc                 &   | j                  d      xs d}|j                  d      }t        j                  d|t        j                        }|r|rdj	                  d |d   D              }t        j                  d|t        j                        }|r9|j                  d	       d
|j                  d	       d|j                  d       d}	n|j                         }	|j                  dg       j                  d|	d       y|rt        |dd||       yy)a  Always emits a row. Never guesses stick count/size. If outlet's Memory
    string already states a soldered amount, pair it with PSREF's slot-count +
    DDR generation (not outlet's own socketed-capacity figure, which would
    falsely imply the exact populated stick size).memoryr   r   z(\d+\s*GB)\s*Solderedr   c              3   2   K   | ]  }t        |        y wr   r   r   s     r   rV   z%_emit_memory_slots.<locals>.<genexpr>  s     C?ahqk?r   r9   z6\b(one|two|three|four|\d+)\s+(DDR\d)\s+SO-?DIMM\s+slotr}   z Soldered +    z SO-DIMM slotr   r   N)
r:   r   r   r   r$   groupr   r   r;   r   )
r   r)   r   r   memr   soldered
psref_textslotr   s
             r   _emit_memory_slotsr     s     **X

$"CMM.)Eyy13=HEXXC5?CC
yyRT^`b`d`de~~a()djjm_AdjjQRm_TabEIIKE2&--QV.WXuhyI r   c                    | j                  d      }|sy|d   dk(  rCt        |d   d         j                         j                         }|j	                  d      rdnd	}nd}|j                  d
g       j                  d|d       y)u   Decisive Yes/No, never a hedge — a buyer can't retrofit one either way, so
    an occasionally-wrong "No" beats a technically-safer "may vary".r   Nr7   r}   r9   r   noNoYesr   r   )r:   r(   r   r   
startswithr   r;   )r)   r   r   r   r   s        r   _emit_smart_card_readerr     s     MM-.EY1h*+11399;-5	Z$++6ITY,Z[r   c                 N   | j                  d      xs i }|j                  d      rd}nX|j                  d      }|r)dj                  d |d   D              j                         nd}t        j                  d	|      rd
nd}|j                  dg       j                  d|d       y)u   Three real states. Outlet's `SIM` key present is itself a fully confirmed
    signal (same reliability as Fingerprint Reader's presence/absence) — its
    absence means "Built-in" is ruled out, not "unknown".
full_specsSIMzBuilt-inr   r   c              3   2   K   | ]  }t        |        y wr   r   r   s     r   rV   z_emit_wwan.<locals>.<genexpr>  r   r   r9   r   zslot|antenna|upgradablezHas a WWAN slotr   r   r   N)r:   r$   r   r   r   r   r;   )r   r)   r   r  r   r   r   s          r   
_emit_wwanr    s     L)/RJ~~ef%JO>eHo>>DDFUW%'YY/I5%Q!W[	^R(//&50QRr   c                     t        j                  | xs d      } t        j                  dd|       } t        j                  dd|       } t        j                  dd|       j	                         j                         } | S )Nr   u	   [®™©]z	\([^)]*\)r   r   )r   r   r   r   r   r   r   s    r   _normalize_cpur    sc    ag2A
|R#A 	|R#A
vsA$$&,,.AHr   >   Max FrequencyBase FrequencyCacheCoresThreadsProcessor NameProcessor Graphicsoutlet_processorc                     | j                  d      }|r|syt        |      }|d   D ]'  }|j                  d      }|st        |      |v s%|c S  y)u   Match outlet's known Processor string against PSREF's per-SKU Processor
    Name to unlock Cache/Cores-Threads/NPU/iGPU-TOPS — none of which outlet
    reports. Deterministic: the CPU SKU token is exact, not fuzzy.r   Nr9   r  )r:   r  )r)   r  r   outlet_normr   names         r   _match_processorr    s\     MM+&E( !12Kxyy)*N4(K7L ! r   psref_spec_data_jsonc                     | r|sy	 t        j                  |       }t	        ||      }|sy|j                  d      }|rd| S dS # t        t        f$ r Y yw xY w)u  Resolve a laptop's integrated GPU name from PSREF's per-SKU Processor
    Graphics attr (the same join _match_processor already does for Cache/
    Cores-Threads/NPU-TOPS), so it works for any CPU SKU PSREF has indexed —
    not a fixed table. Confirmed against a live call for MT 21JK (ThinkPad E14
    Gen 5 Intel): Core i3-1315U -> 'Intel® UHD Graphics', Core i5-1335U/
    i7-1355U/i7-1365U -> 'Intel® Iris® Xe Graphics' — exact match to what
    outlet itself reports for every sibling SKU in the same catalog, so this
    is read as ground truth, not guessed. See the module-level comment above
    for why this is safe to call for *any* CPU tier despite that not being
    true of the single-field dGPU-vs-iGPU question.Nr  zIntegrated )rc   rr   	TypeError
ValueErrorr  r:   )r  r  r)   r   gfxs        r   infer_igpu_from_psrefr  '  sr      '7*.**5I*J	 Y(89E
))(
)C"%[/4/ z" s   A AAc                    g }| j                  d      r|j                  d| d   d       | j                  d      | j                  d      }}|r|r|j                  d| d| d       | j                         D ]H  \  }}|t        v rt	        j
                  d|t        j                        s5|j                  ||d       J |S )Nr
  r   r  r  zCores / Threadsz / znpu|tops)r:   r;   r#   _PROCESSOR_JOIN_SKIP_ATTRSr   r   r   )r   rowscoresthreadskeyvals         r   _processor_join_rowsr!  ?  s    Dyygg?@YYw'9)=7E/UG3wi:PQRKKMS,,99[#rtt,KK#45	 "
 Kr   c                 r    t        j                  d| xs d      }|rt        |j                  d            S d S )Nz(\d+(?:\.\d+)?)r   r}   )r   r   floatr   r   ms     r   _parse_inchesr&  N  s1    
		$ag2.A !5+t+r   c                     t        j                  d| xs dt         j                        }|r4t        j                  dd|j	                  d            j                         S d S )Nz(\d{3,4}\s*x\s*\d{3,4})r   r   r}   )r   r   r   r   r   r   r$  s     r   _extract_resolutionr(  S  sH    
		,ag2rtt<A56266&"aggaj)//1@D@r   c                    | j                  d      }|sy|j                  d      }t        |j                  d      xs d      }||syt        |j                  d            }|j                  d      xs dj                         j	                         xs d}|d   D ]  }t        |j                  d	d            }t        |j                  d
d            }	||	t        ||z
        dkD  s|	|k7  rT|j                  d      xs dj	                         }
t        |
      xr d|
v}||k7  r|j                  d      xs dj                         j	                         }|r|r||k7  r|c S  y)u   Match on Size+Resolution+Touch+(Type when known) — the combination we
    already know live, high-confidence against false matches within one MT's
    product line — to unlock Contrast Ratio/Viewing Angle/panel Certifications.r   Nzscreen-size
resolutionr   ztouch-screenz
panel-typer9   Size
Resolutiong?Touchz	non-touchType)r:   r(  boolr   r   r&  abs)r)   r   r   target_size
target_restarget_touchtarget_typer   sizeres
touch_texttouch
psref_types                r   _match_displayr:  X  s\    MM)$E**]+K$VZZ%=%CDJ*

>23L::l+1r88:@@BJdKxUYYvr23!%))L""=><3;#d[.@*AD*HCS]L]ii(.B557
Z B[
%BL ii'-2446<<>
:**C ! r   c                     g }| j                  d      r|j                  d| d   d       | j                  d      }|r|j                  d|d       | j                  d      }|r|j                  d|d       |S )NzContrast Ratior   zViewing Angle (L/R/U/D)zViewing AnglezKey Features	Certified)r:   r;   )r   r  viewing_anglekey_featuress       r   _display_join_rowsr?  v  sv    Dyy!".?O9PQRII78MoFG99^,LkLABKr   groupsc                     t         D cg c]	  }|| v s| }}|t        d | D              z  }|D cg c]  }| |   s	|| |   d c}S c c}w c c}w )Nc              3   2   K   | ]  }|t         vs|  y wr   )_CATEGORY_ORDER)rT   cs     r   rV   z_finalize.<locals>.<genexpr>  s     DA1O+Cas   )r   r  )rC  sorted)r@  rD  ordereds      r   	_finalizerG    s]    )9/QQ&[q/G9vDDDDG8?M16!9F1I.MM :Ms   	A
A

A
A	psref_rowc                    |r|j                  d      sg g dfS 	 t        j                  |d         }i }i }t        t              }t        D ]9  \  }}|j                  |      }|j                  |       |s+t        |||||       ; t        D ]8  \  }}|j                  |      }|j                  |       |s+t        ||||       : t        | |||       t        ||       t        | ||       t        || j                  d            }	|	r1t!        |	      D ]#  }
|j#                  dg       j%                  |
       % t'        ||       }|r1t)        |      D ]#  }
|j#                  dg       j%                  |
       % |j+                         D ]>  \  }}||v rt,        j                  |j                  d      d      }t        |||||       @ t/        |      t/        |      |j                  d      fS # t        t        f$ r g g dfcY S w xY w)	zRun at read time (not baked into the backfill), so it always reflects live
    outlet fields. Returns (specs_extra, reference_specs, reference_specs_url).r)   N	processorr   r   r6   r   r[   )r:   rc   rr   r  r  set_EXCLUDE_FNAMESr   addr   r   r   r   r   r  r  r!  r   r;   r:  r?  r#   _L2_FALLBACK_CATEGORYrG  )r   rH  r)   r   r   handledrA   r   r   processor_matchr   display_matchs               r   resolve_specs_for_laptoprR    s   
 IMM+62t|*.**Y{5K*L	 ')E*,I/"G'xe$E5(E5)D	 ( (xe$Euhu5	 ( vy%;Iu-vy%(&y&**[2IJO'8C["-44S9 9 #9f5M%m4CY+2237 5
 ")uG(,,UYYt_jIuhui@	 * UYy19==3LLLS z" 2t|s   G3 3H
	H
r   )@__doc__r   rc   r   r   urllib.parser^   typingr   r   r   r   r   backend.configr   backend.db.connectionr	   backend.logging_configr
   r   backend.scraper.apir   r   str	SpecFieldr   r(   r   rG   rk   rv   intr   SpecRow	SpecGrouprC  r   __annotations__r   rL  rN  r   compiler   r   Patternr   r   r   r   r   r  r  r  r  r  r!  r#  r&  r(  r:  r?  rG  rR   r   r   <module>rc     s  >   	   3 3 # / 5 (  cN	8C= S $DcN s #$t* #c9n1E #LS Xd38n%= 8c s xS)^@T7U >D8c(; D8tCQTH~ D8Z sCx.cN	"'d5c?# .'d5c?# 4 #'399 &``` ` T']"#	`
 Cg&'` 
`8 RZZ%'<=RZZ%w/RZZRTT"G,RZZ'RZZ%rtt,m<RZZ"BDD):6RZZ=rttDlSRZZrtt$&9:RZZ&-}=RZZ%rtt,j9RZZ
BDD!5)6 d5S12  RZZ%}5RZZ"$$)RZZ
BDD!5)RZZ'RZZ
BDD!?3RZZ
BDD!9-RZZ
BDD!#34RZZ
BDD!9-	6 d5S12 	 C C 

i 

3 

s 

4TRY]HZC[ 

`d 

JcNJCN#J T']"#J Cg&'	J
 
J4\tCN'; \DdSZmI[D\ \ae \StCH~ S$sI~2F StTWY]^eYfTfOg Slp S c c  S)^ 4 QT ZbcghkmphpcqZr B0 0QYZ]Q^ 0cklocp 00S#X 4= ,S ,Xe_ ,
A3 A8C= A
d3	>2 DcN xX\]`be]eXfOg <
d38n 
g 
Nd3W-. N4	? N2McN2M'/S#X'?2M
4	?DOXc]:;2Mr   