o
    hÀcj�D ã                   @   s  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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 ddlm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mZ ddlmZm Z m!Z!m"Z"m#Z#m$Z$m%Z%m&Z&m'Z'm(Z(m)Z)m*Z*m+Z+m,Z,m-Z- e .e/¡Z0ed	e/ƒZ1e&e(e)e'e%ej2j3d
œZ4h d£Z5h d£Z6ddhZ7h d£Z8da9e :¡ Z;e<ƒ Z=e :¡ Z>e<ƒ Z?i Z@dZAdZBdZCdd„ ZDdd„ ZEdd„ ZFdd„ ZGdd„ ZHdd„ ZId d!„ ZJ�d”d"d#„ZKd$d%„ ZLd&ZMd'd(„ ZNd)d*„ ZO�d•d+d,„ZPd-d.„ ZQ�d•d/d0„ZRd1d2„ ZSd3d4„ ZTd5d6„ ZUd7d8„ ZVd9d:„ ZWd;d<„ ZXd=d>„ ZYd?d@„ ZZdAdB„ Z[�d–dDdE„Z\dFdG„ Z]dHdI„ Z^dJZ_e`dKdLhƒZae`dMhƒZbdNZcdOZde`h dP£ƒZedQdRdSdTdUdVdWœZfdXdY„ ZgdZd[„ Zh�d—d]d^„Zid_d`„ Zjdadb„ Zkdcdd„ Zl�d˜dedf„Zmdgdh„ Zndidj„ Zodkdl„ Zpdmdn„ Zqdodp„ Zrdqdr„ Zsdsdt„ Ztdudv„ Zudwdx„ Zvdydz„ Zwd{d|„ Zx�d™d~d„Zyd€d�„ Zz�d—d‚dƒ„Z{d„d…„ Z|d†d‡„ Z}�d˜dˆd‰„Z~�dšd‹dŒ„Z�d˜d�dŽ„Z€d�d�„ Z�d‘d’„ Z‚�d™d“d”„Zƒ�d™d•d–„Z„d—d˜„ Z…�d›d™dš„Z†deBfd›dœ„Z‡�dœd�dž„ZˆdŸd „ Z‰d¡ZŠd¢d£„ Z‹d¤d¥„ ZŒd¦d§„ Z�d¨d©„ ZŽdªd«„ Z�d¬d­„ Z�d®d¯„ Z‘d°d±„ Z’d²d³„ Z“d´dµ„ Z”d¶d·„ Z•d¸d¹„ Z–dºd»„ Z—d¼d½„ Z˜d¾d¿„ Z™dÀdÁ„ ZšdÂdÃ„ Z›�d�dÄdÅ„ZœdÆdÇ„ Z��d–dÈdÉ„Zž�d�dÊdË„ZŸdÌdÍ„ Z dÎdÏ„ Z¡�d™dÐdÑ„Z¢dÒdÓ„ Z£eAfdÔdÕ„Z¤dÖd×„ Z¥dØdÙ„ Z¦dÚdÛ„ Z§�dœdÜdÝ„Z¨dÞdß„ Z©dàdá„ Zªdâdã„ Z«dädå„ Z¬dædç„ Z­dèdé„ Z®dêdë„ Z¯�d˜dìdí„Z°dîdï„ Z±�d˜dðdñ„Z²dòdó„ Z³dôdõ„ Z´död÷„ Zµdødù„ Z¶dúdû„ Z·düdý„ Z¸�d�dþdÿ„Z¹�d �d„ Zº�d�d„ Z»�d�d„ Z¼e1j½�d�d„ ƒZ¾e1 ¿�d¡�d	�d
„ ƒZÀe1j¿�d�dg�d��d�d„ ƒZÁe1j¿�d�dg�d��d�d„ ƒZÂe1j¿�d�dg�d��d�d„ ƒZÃe1j¿�d�dg�d��d�d„ ƒZÄe1j¿�d�dg�d��d�d„ ƒZÅe1j¿�d�dg�d��d�d„ ƒZÆe1j¿�d�dg�d��d �d!„ ƒZÇe1j¿�d"�dg�d��d#�d$„ ƒZÈe1j¿�d%�dg�d��d&�d'„ ƒZÉe1j¿�d(�dg�d��d)�d*„ ƒZÊe1j¿�d"�dg�d��d+�d,„ ƒZËe1j¿�d(�dg�d��d-�d.„ ƒZÌe1j¿�d/�dg�d��d0�d1„ ƒZÍe1j¿�d(�dg�d��d2�d3„ ƒZÎe1j¿�d4�dg�d��d5�d6„ ƒZÏe1j¿�d4�dg�d��d7�d8„ ƒZÐe1j¿�d9�dg�d��d:�d;„ ƒZÑe1j¿�d9�dg�d��d<�d=„ ƒZÒe1 ¿�d>¡�d?�d@„ ƒZÓe1 ¿�dA¡�dB�dC„ ƒZÔe1j¿�dD�dg�d��dE�dF„ ƒZÕe1j¿�dD�dg�d��dG�dH„ ƒZÖe1j¿�dI�dg�d��dJ�dK„ ƒZ×e1j¿�dL�dg�d��dM�dN„ ƒZØe1j¿�dO�dg�d��dP�dQ„ ƒZÙe1j¿�dR�dg�d��dS�dT„ ƒZÚe1j¿�dR�dg�d��dU�dV„ ƒZÛe1j¿�dW�dX�dg�d��dY�dZ„ ƒZÜe1j¿�dI�dg�d��d[�d\„ ƒZÝe1j¿�dI�dg�d��d]�d^„ ƒZÞe1j¿�d_�dXg�d��d`�da„ ƒZße1j¿�db�dg�d��dc�dd„ ƒZàe1j¿�db�dg�d��de�df„ ƒZáe1j¿�dg�dg�d��dh�di„ ƒZâe1j¿�dg�dg�d��dj�dk„ ƒZãe1j¿�dl�dg�d��dm�dn„ ƒZäe1j¿�dl�dg�d��do�dp„ ƒZåe1j¿�dq�dg�d��dr�ds„ ƒZæe1j¿�dq�dg�d��dt�du„ ƒZçe1j¿�dv�dg�d��dw�dx„ ƒZèe1j¿�dv�dg�d��dy�dz„ ƒZée1j¿�d{�dg�d��d|�d}„ ƒZêe1 ¿�d~¡�d�d€„ ƒZëe1 ¿�d�¡�d‚�dƒ„ ƒZìe1 ¿�d„¡�d…�d†„ ƒZíe1 ¿�d‡¡�dˆ�d‰„ ƒZîe1 ¿�dŠ¡�d‹�dŒ„ ƒZïe1j¿�d��dŽ�d�i�d��e1 ¿�d‘¡�d’�d“„ ƒƒZðdS (ž  zFGarden Tracker: plants, seeds, schedules, hydro tanks, planting guide.é    N)ÚdateÚdatetimeÚ	timedelta)ÚDecimal)Ú	BlueprintÚResponseÚjsonifyÚrequestÚsend_from_directory)Úsecure_filename)Úensure_alert_state_tableÚsend_sms_alert_detailed)ÚGARDEN_CLIENT_DISTÚ
GARDEN_LATÚ
GARDEN_LONÚGARDEN_LOCATION_LABELÚGARDEN_REMINDER_CHECK_MINUTESÚ
GARDEN_ZIPÚMYSQL_DATABASEÚ
MYSQL_HOSTÚMYSQL_PASSWORDÚ
MYSQL_PORTÚ
MYSQL_USERÚPARTSINV_IMAGE_SEARCH_API_KEYÚPARTSINV_IMAGE_SEARCH_CXÚWEATHER_LATÚWEATHER_LONÚgardentracker)ÚhostÚportÚuserÚpasswordÚdatabaseÚcursorclass>   ÚpruneÚwaterÚcustomÚ	fertilizeÚ	test_tankÚharvest_check>   ÚdiedÚactiveÚarchivedÚ	harvestedÚseedÚstore_bought>   r*   Únoter&   ÚprunedÚseededÚwateredr-   Ú
fertilizedÚ
germinatedÚ
seed_startÚtrue_leavesÚtransplantedFé   é   Úgardentracker_seed_uploadsc                   C   s   t jdi t¤ŽS )N© )ÚpymysqlÚconnectÚ	DB_CONFIGr<   r<   r<   ú0/var/www/html/Server/blueprints/gardentracker.pyÚget_dbQ   s   rA   c                 C   sR   t | tƒr	|  ¡ S t | tƒr|  ¡ S t | tƒrt| ƒS t | tƒr'| jddd�S | S )Núutf-8Úreplace©Úerrors)Ú
isinstancer   Ú	isoformatr   r   ÚfloatÚbytesÚdecode©Úvaluer<   r<   r@   Ú
_serializeU   s   



rM   c                 C   s   | s| S dd„ |   ¡ D ƒS )Nc                 S   s   i | ]	\}}|t |ƒ“qS r<   )rM   )Ú.0ÚkÚvr<   r<   r@   Ú
<dictcomp>d   ó    z"_serialize_row.<locals>.<dictcomp>)Úitems)Úrowr<   r<   r@   Ú_serialize_rowa   s   rU   c                 C   s   dd„ | D ƒS )Nc                 S   s   g | ]}t |ƒ‘qS r<   )rU   ©rN   Úrr<   r<   r@   Ú
<listcomp>h   ó    z#_serialize_rows.<locals>.<listcomp>r<   )Úrowsr<   r<   r@   Ú_serialize_rowsg   s   r[   c                 C   sT   | d u s| dkr
d S t | tƒr| S zt t| ƒ dd¡¡ ¡ W S  ty)   Y d S w ©NÚ ÚZz+00:00)rF   r   r   ÚfromisoformatÚstrrC   Ú
ValueErrorrK   r<   r<   r@   Ú_parse_datek   s   
ÿrb   c                 C   sP   | d u s| dkr
d S t | tƒr| S zt t| ƒ dd¡¡W S  ty'   Y d S w r\   )rF   r   r_   r`   rC   ra   rK   r<   r<   r@   Ú_parse_datetimev   s   
ÿrc   c              
   C   sÒ  |   ¡ �Ï}| d¡ | d¡ dD ]}z| |¡ W q tjy$   Y qw z| d¡ W n
 tjy6   Y nw | d¡ dD ]}z| |¡ W q> tjyQ   Y q>w | d¡ | d¡ | d	¡ | d
¡ | d¡ | d¡ | d¡ dD ]}z| |¡ W qw tjyŠ   Y qww | d¡ | d¡ t| ¡ p›i  d¡p dƒdkrºttƒD ]\}\}}}| d||||f¡ q¨| d¡ | ¡ sÌ| dt	t
tf¡ W d   ƒ n1 sÖw   Y  |  ¡  t| ƒ t| ƒ d S )Naý  
            CREATE TABLE IF NOT EXISTS GardenSettings (
                id INT PRIMARY KEY DEFAULT 1,
                zip_code VARCHAR(10) NOT NULL DEFAULT '39429',
                lat DECIMAL(10, 6) DEFAULT NULL,
                lng DECIMAL(10, 6) DEFAULT NULL,
                notifications_enabled TINYINT(1) NOT NULL DEFAULT 1,
                low_seed_threshold INT NOT NULL DEFAULT 5,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            )
            a+  
            CREATE TABLE IF NOT EXISTS GardenPlant (
                id INT AUTO_INCREMENT PRIMARY KEY,
                name VARCHAR(120) NOT NULL,
                variety VARCHAR(120) DEFAULT NULL,
                location VARCHAR(120) DEFAULT NULL,
                status VARCHAR(20) NOT NULL DEFAULT 'active',
                planted_date DATE DEFAULT NULL,
                moisture_device_id VARCHAR(16) DEFAULT NULL,
                seed_id INT DEFAULT NULL,
                source_type VARCHAR(20) NOT NULL DEFAULT 'store_bought',
                label_id VARCHAR(16) DEFAULT NULL,
                notes TEXT DEFAULT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            )
            )z;ALTER TABLE GardenPlant ADD COLUMN seed_id INT DEFAULT NULLzZALTER TABLE GardenPlant ADD COLUMN source_type VARCHAR(20) NOT NULL DEFAULT 'store_bought'zDALTER TABLE GardenPlant ADD COLUMN label_id VARCHAR(16) DEFAULT NULLzGCREATE UNIQUE INDEX idx_garden_plant_label_id ON GardenPlant (label_id)aS  
            CREATE TABLE IF NOT EXISTS GardenPlantEvent (
                id INT AUTO_INCREMENT PRIMARY KEY,
                plant_id INT NOT NULL,
                event_type VARCHAR(32) NOT NULL,
                event_date DATE NOT NULL,
                notes TEXT DEFAULT NULL,
                qty DECIMAL(10, 2) DEFAULT NULL,
                unit VARCHAR(32) DEFAULT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_plant (plant_id),
                FOREIGN KEY (plant_id) REFERENCES GardenPlant(id) ON DELETE CASCADE
            )
            )zIALTER TABLE GardenPlantEvent ADD COLUMN days_since_start INT DEFAULT NULLzRALTER TABLE GardenPlantEvent ADD COLUMN days_since_anchor VARCHAR(16) DEFAULT NULLzJALTER TABLE GardenPlantEvent ADD COLUMN location VARCHAR(120) DEFAULT NULLzJALTER TABLE GardenPlantEvent ADD COLUMN height DECIMAL(10, 2) DEFAULT NULLaF  
            CREATE TABLE IF NOT EXISTS GardenSchedule (
                id INT AUTO_INCREMENT PRIMARY KEY,
                plant_id INT DEFAULT NULL,
                tank_id INT DEFAULT NULL,
                task_type VARCHAR(32) NOT NULL,
                title VARCHAR(120) NOT NULL,
                interval_days INT NOT NULL DEFAULT 14,
                next_due DATETIME NOT NULL,
                enabled TINYINT(1) NOT NULL DEFAULT 1,
                notes TEXT DEFAULT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                INDEX idx_next_due (next_due),
                INDEX idx_plant (plant_id),
                FOREIGN KEY (plant_id) REFERENCES GardenPlant(id) ON DELETE CASCADE
            )
            aµ  
            CREATE TABLE IF NOT EXISTS GardenTaskCompletion (
                id INT AUTO_INCREMENT PRIMARY KEY,
                schedule_id INT NOT NULL,
                completed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                notes TEXT DEFAULT NULL,
                INDEX idx_schedule (schedule_id),
                FOREIGN KEY (schedule_id) REFERENCES GardenSchedule(id) ON DELETE CASCADE
            )
            a!  
            CREATE TABLE IF NOT EXISTS GardenSeedInventory (
                id INT AUTO_INCREMENT PRIMARY KEY,
                crop_name VARCHAR(120) NOT NULL,
                variety VARCHAR(120) DEFAULT NULL,
                qty DECIMAL(10, 2) NOT NULL DEFAULT 0,
                unit VARCHAR(32) NOT NULL DEFAULT 'seeds',
                source VARCHAR(120) DEFAULT NULL,
                purchase_date DATE DEFAULT NULL,
                expiry_date DATE DEFAULT NULL,
                location VARCHAR(120) DEFAULT NULL,
                low_stock_threshold INT DEFAULT NULL,
                notes TEXT DEFAULT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            )
            a  
            CREATE TABLE IF NOT EXISTS GardenTank (
                id INT AUTO_INCREMENT PRIMARY KEY,
                name VARCHAR(120) NOT NULL,
                volume_gal DECIMAL(10, 2) DEFAULT NULL,
                solution_type VARCHAR(120) DEFAULT NULL,
                target_ph_min DECIMAL(4, 2) DEFAULT NULL,
                target_ph_max DECIMAL(4, 2) DEFAULT NULL,
                target_ec_min DECIMAL(6, 2) DEFAULT NULL,
                target_ec_max DECIMAL(6, 2) DEFAULT NULL,
                test_interval_days INT NOT NULL DEFAULT 7,
                notes TEXT DEFAULT NULL,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            )
            af  
            CREATE TABLE IF NOT EXISTS GardenTankReading (
                id INT AUTO_INCREMENT PRIMARY KEY,
                tank_id INT NOT NULL,
                ph DECIMAL(4, 2) DEFAULT NULL,
                ec DECIMAL(6, 2) DEFAULT NULL,
                temp_f DECIMAL(5, 2) DEFAULT NULL,
                volume_gal DECIMAL(10, 2) DEFAULT NULL,
                notes TEXT DEFAULT NULL,
                recorded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                INDEX idx_tank (tank_id),
                FOREIGN KEY (tank_id) REFERENCES GardenTank(id) ON DELETE CASCADE
            )
            a!  
            CREATE TABLE IF NOT EXISTS GardenExternalCache (
                cache_key VARCHAR(128) PRIMARY KEY,
                payload JSON NOT NULL,
                fetched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                expires_at TIMESTAMP NOT NULL
            )
            aÆ  
            CREATE TABLE IF NOT EXISTS GardenSeedTypeInfo (
                cache_key VARCHAR(256) PRIMARY KEY,
                crop_name VARCHAR(120) NOT NULL,
                variety VARCHAR(120) DEFAULT NULL,
                summary TEXT DEFAULT NULL,
                wikipedia_title VARCHAR(256) DEFAULT NULL,
                wikipedia_url VARCHAR(512) DEFAULT NULL,
                info_source VARCHAR(32) DEFAULT NULL,
                source_url VARCHAR(512) DEFAULT NULL,
                source_name VARCHAR(120) DEFAULT NULL,
                images JSON DEFAULT NULL,
                planting JSON DEFAULT NULL,
                fetched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
            )zNALTER TABLE GardenSeedTypeInfo ADD COLUMN info_source VARCHAR(32) DEFAULT NULLzNALTER TABLE GardenSeedTypeInfo ADD COLUMN source_url VARCHAR(512) DEFAULT NULLzOALTER TABLE GardenSeedTypeInfo ADD COLUMN source_name VARCHAR(120) DEFAULT NULLz[ALTER TABLE GardenSeedTypeInfo ADD COLUMN images_user_managed TINYINT(1) NOT NULL DEFAULT 0a)  
            CREATE TABLE IF NOT EXISTS GardenSeedCompany (
                id INT AUTO_INCREMENT PRIMARY KEY,
                name VARCHAR(120) NOT NULL,
                base_url VARCHAR(512) NOT NULL,
                search_url_template VARCHAR(512) DEFAULT NULL,
                enabled TINYINT(1) NOT NULL DEFAULT 1,
                sort_order INT NOT NULL DEFAULT 0,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
            )
            z-SELECT COUNT(*) AS cnt FROM GardenSeedCompanyÚcntr   z±
                    INSERT INTO GardenSeedCompany (name, base_url, search_url_template, enabled, sort_order)
                    VALUES (%s, %s, %s, 1, %s)
                    z*SELECT id FROM GardenSettings WHERE id = 1z|
                INSERT INTO GardenSettings (id, zip_code, lat, lng)
                VALUES (1, %s, %s, %s)
                )ÚcursorÚexecuter=   ÚOperationalErrorÚintÚfetchoneÚgetÚ	enumerateÚDEFAULT_SEED_COMPANIESr   r   r   Úcommitr   Ú_backfill_plant_label_ids)ÚconnÚcurÚcol_sqlÚidxÚnameÚbase_urlÚtemplater<   r<   r@   Úensure_garden_tables�   sš   
ÿÿÿÿÿÿÿÿÿÿÿÿ
ÿÿÿ

û
û€ �¢ erv   c                 C   s>   |   d¡ |  ¡ }t|d pdƒd }dd„ t||| ƒD ƒS )Nz†
        SELECT MAX(CAST(SUBSTRING(label_id, 3) AS UNSIGNED)) AS n
        FROM GardenPlant
        WHERE label_id LIKE 'P-%'
        Únr   r:   c                 S   s   g | ]}d |d›�‘qS )zP-Ú05dr<   )rN   Úir<   r<   r@   rX   u  ó    z-_allocate_plant_label_ids.<locals>.<listcomp>)rf   ri   rh   Úrange)rp   ÚcountrT   Ústartr<   r<   r@   Ú_allocate_plant_label_idsk  s   ÿr~   c                 C   s’   |   ¡ �7}| d¡ | ¡ }|s	 W d   ƒ d S t|t|ƒƒ}t||ƒD ]\}}| d||d f¡ q%W d   ƒ n1 s>w   Y  |  ¡  d S )NzNSELECT id FROM GardenPlant WHERE label_id IS NULL OR label_id = '' ORDER BY idz2UPDATE GardenPlant SET label_id = %s WHERE id = %sÚid)re   rf   Úfetchallr~   ÚlenÚziprm   )ro   rp   rZ   ÚlabelsrT   Úlabelr<   r<   r@   rn   x  s   
ÿúÿø
rn   a¸  
    SELECT p.*,
        s.crop_name AS seed_crop_name,
        s.variety AS seed_variety,
        s.location AS seed_bin,
        s.source AS seed_source,
        EXISTS (
            SELECT 1
            FROM GardenPlantEvent dead_event
            WHERE dead_event.plant_id = p.id
              AND dead_event.event_type = 'died'
        ) AS has_died_event
    FROM GardenPlant p
    LEFT JOIN GardenSeedInventory s ON s.id = p.seed_id
c                 C   sP   | sdS |   d¡p|   d¡pd ¡ }|   d¡p|   d¡pd ¡ p!d }|p%d |fS )N©NNÚseed_crop_namers   r]   Úseed_varietyÚvariety©rj   Ústrip)rT   Úcroprˆ   r<   r<   r@   Ú_plant_type_crop_variety—  s
    rŒ   c                 C   s.   t | ||ƒ}|p	i  d¡pg }|r|d S d S )NÚimagesr   )Ú_get_seed_type_inforj   )ro   Ú	crop_namerˆ   Ú	type_infor�   r<   r<   r@   Ú_get_primary_type_imageŸ  s   r‘   c                 C   sd   | s| S t | ƒ}| d¡dkr| d¡rt| ƒ|d< |r0|d ur0t| ƒ\}}|r0t|||ƒ|d< |S )NÚsource_typer.   Úseed_idÚ
seed_labelÚprimary_image)rU   rj   Ú_seed_display_labelrŒ   r‘   )rT   ro   Úinclude_primary_imageÚoutr�   rˆ   r<   r<   r@   Ú_serialize_plant¥  s   r™   c                 C   sx   g }|   d¡r| d| d › �¡ |   d¡p|   d¡}|r"| |¡ |   d¡p+|   d¡}|r3| |¡ |r:d |¡S d S )NÚseed_binzbin #r†   r�   r‡   rˆ   u    Â· )rj   ÚappendÚjoin)rT   Úpartsr‹   rˆ   r<   r<   r@   r–   ²  s   


r–   c                    s   ‡ ‡fdd„| D ƒS )Nc                    s   g | ]	}t |ˆ ˆd �‘qS )©ro   r—   )r™   rV   rž   r<   r@   rX   À  rR   z%_serialize_plants.<locals>.<listcomp>r<   )rZ   ro   r—   r<   rž   r@   Ú_serialize_plants¿  s   rŸ   c                 C   s4   | d u rd S t | tƒr|  ¡ S t | tƒr| S t| ƒS ©N)rF   r   r   rb   rK   r<   r<   r@   Ú_as_dateÃ  s   

r¡   c           
      C   s¼   t |ƒ}|sdS g }t |r| d¡ndƒ}|r ||kr | |¡ |  d||f¡ |  ¡ D ]}t | d¡ƒ}|r<| |¡ q,|r\t|ƒ}|| j}|pKi  d¡dkrSdnd	}	td
|ƒ|	fS dS )z/Days from seed start or planting to event_date.r…   Úplanted_dateNz×
        SELECT event_date FROM GardenPlantEvent
        WHERE plant_id = %s AND event_type IN ('seed_start', 'seeded', 'transplanted')
          AND event_date <= %s
        ORDER BY event_date ASC, id ASC
        Ú
event_dater’   r.   r6   Úplantingr   )r¡   rj   r›   rf   r€   ÚminÚdaysÚmax)
rp   Úplant_idÚplantr£   ÚanchorsÚplantedrT   Úanchorr¦   Úanchor_typer<   r<   r@   Ú_compute_days_since_startÍ  s,   
ù	
€
r®   c                 C   s„   |s|S t |ƒ}| d¡d ur(| d¡s&t| ||| d¡ƒ\}}|r&||d< |S t| ||| d¡ƒ\}}|d ur@||d< ||d< |S )NÚdays_since_startÚdays_since_anchorr£   )rU   rj   r®   )rp   r¨   r©   Úeventr˜   Ú_r¬   r¦   r<   r<   r@   Ú_enrich_eventì  s   
r³   c                    s   ‡ ‡‡fdd„|D ƒS )Nc                    s   g | ]	}t ˆ ˆˆ|ƒ‘qS r<   )r³   )rN   Úev©rp   r©   r¨   r<   r@   rX   þ  rR   z"_enrich_events.<locals>.<listcomp>r<   )rp   r¨   r©   Úeventsr<   rµ   r@   Ú_enrich_eventsý  ó   r·   c                 C   s    | d u rd S t | ƒ ¡ }|pd S r    )r`   rŠ   )rL   Úlocr<   r<   r@   Ú_normalize_location  s   rº   c              	   C   s8   | d u s| dkr
d S zt | ƒW S  ttfy   Y d S w ©Nr]   )rH   Ú	TypeErrorra   rK   r<   r<   r@   Ú_optional_float  s   
ÿr½   c                 C   s$   |dkrd S |   dt|ƒ|f¡ d S )Nr8   z2UPDATE GardenPlant SET location = %s WHERE id = %s)rf   rº   )rp   r¨   Ú
event_typeÚlocationr<   r<   r@   Ú_sync_transplant_location  s   
þrÀ   c                 C   s>   |   ¡ �}| d¡ | ¡ W  d   ƒ S 1 sw   Y  d S )Nz)SELECT * FROM GardenSettings WHERE id = 1)re   rf   ri   ©ro   rp   r<   r<   r@   Ú_get_settings  s   

$þrÂ   c                    sŒ  t  ¡ }| d¡ | dt ¡  ¡ › d�¡ | d¡ ˆ  ¡ �–}| d¡ dd„ | ¡ D ƒ}|D ]}}| d|› d	�¡ | 	¡ }|rF| 
d
¡nd }|sKq0| d|› d�¡ | d|› d�¡ | |› d�¡ | d|› d	�¡ | ¡ }|suq0t|d  ¡ ƒ}d dd„ |D ƒ¡}	|D ]‰d ‡ ‡fdd„|D ƒ¡}
| d|› d|	› d|
› d�¡ q‰| d¡ q0W d   ƒ n1 s¸w   Y  | d¡ | ¡ S )Nz-- Garden Tracker backup
z-- Generated at z

zSET FOREIGN_KEY_CHECKS = 0;

zú
            SELECT TABLE_NAME
            FROM information_schema.TABLES
            WHERE TABLE_SCHEMA = DATABASE()
              AND TABLE_TYPE = 'BASE TABLE'
              AND TABLE_NAME LIKE 'Garden%'
            ORDER BY TABLE_NAME
            c                 S   s   g | ]}|d  ‘qS )Ú
TABLE_NAMEr<   )rN   rT   r<   r<   r@   rX   1  rY   z+_sql_dump_garden_tables.<locals>.<listcomp>zSHOW CREATE TABLE `ú`zCreate Tablez
-- Table: Ú
zDROP TABLE IF EXISTS `z`;
z;

zSELECT * FROM `r   z, c                 S   s   g | ]}d |› d �‘qS )rÄ   r<   ©rN   Úcr<   r<   r@   rX   B  rz   c                    s   g | ]
}ˆ   ˆ |¡¡‘qS r<   )Úliteralrj   rÆ   ©ro   rT   r<   r@   rX   D  ó    zINSERT INTO `z` (z
) VALUES (z);
zSET FOREIGN_KEY_CHECKS = 1;
)ÚioÚStringIOÚwriter   ÚnowrG   re   rf   r€   ri   rj   ÚlistÚkeysrœ   Úgetvalue)ro   r˜   rp   Útable_namesÚ
table_nameÚ
create_rowÚ
create_sqlrZ   Úcolumnsrq   Úvalsr<   rÉ   r@   Ú_sql_dump_garden_tables   s@   


ÿ
 íó
"rØ   é   c                 C   sZ   t jj| ddid�}t jj||d��}t | ¡  ¡ ¡W  d   ƒ S 1 s&w   Y  d S )Nú
User-AgentzGardenTracker/1.0©Úheaders©Útimeout)Úurllibr	   ÚRequestÚurlopenÚjsonÚloadsÚreadrJ   ©ÚurlrÞ   ÚreqÚrespr<   r<   r@   Ú_fetch_json_urlL  s   $ÿré   c                 C   s¾   |   ¡ �Q}| d|f¡ | ¡ }|s	 W d   ƒ d S |d rE|d t ¡ krM|d }t|tƒr<t |¡W  d   ƒ S |W  d   ƒ S W d   ƒ d S W d   ƒ d S 1 sXw   Y  d S )NzHSELECT payload, expires_at FROM GardenExternalCache WHERE cache_key = %sÚ
expires_atÚpayload)	re   rf   ri   r   ÚutcnowrF   r`   râ   rã   )ro   Úkeyrp   rT   rë   r<   r<   r@   Ú
_cache_getR  s0   
þù
õô
øû
øórî   c                 C   s^   t  ¡ t|d� }|  ¡ �}| d|t |¡|f¡ W d   ƒ n1 s$w   Y  |  ¡  d S )N)Úhoursa	  
            INSERT INTO GardenExternalCache (cache_key, payload, expires_at)
            VALUES (%s, %s, %s)
            ON DUPLICATE KEY UPDATE payload = VALUES(payload), fetched_at = CURRENT_TIMESTAMP,
                expires_at = VALUES(expires_at)
            )r   rì   r   re   rf   râ   Údumpsrm   )ro   rí   rë   Ú	ttl_hoursÚexpiresrp   r<   r<   r@   Ú
_cache_setc  s   
ùÿ
ró   zhttps://en.wikipedia.orgzupload.wikimedia.orgzcommons.wikimedia.orgúcdn.commercev3.net)ÚlogoÚfaviconÚ
mobilemenuÚ
mobilecartÚ
empty_cartz
green-lushÚheaderÚfooterÚ
newsletterzsearch-iconz	group.pngz
ngb-footerÚfontawesomeÚ
brands_secÚ	solid_secÚregular_sec))zJohnny's Selected Seedszhttps://www.johnnyseeds.com/ú{base_url}/search/?q={query})zTotally Tomatoeszhttps://www.totallytomato.com/z{base_url}/search?q={query}>   únon gmoúnon-gmoúopen pollinatedúopen-pollinatedÚf1Úf2ÚopÚgmoÚmixÚrawÚblendÚmixedÚcommonÚhybridÚgenericÚorganicÚregularÚtreatedÚunknownrˆ   ÚheirloomÚimprovedÚpelletedÚstandardÚ	untreatedÚtraditional)ÚerucaÚrocketÚroquette)Úmaizezzea maysÚzea)Ú	auberginezsolanum melongena)Ú	corianderÚ
coriandrum)zgreen onionzallium fistulosum)Ú	courgetteÚ	cucurbita)ÚarugulaÚcornÚeggplantÚcilantroÚscallionÚzucchinic                 C   sD   | pd  ¡ }|s
dS | d¡rd| S | d¡r d|dd …  S |S )Nr]   z//zhttps:úhttp://úhttps://é   )rŠ   Ú
startswith)ræ   Úur<   r<   r@   Ú_normalize_image_urlŽ  s   

r0  c                 C   s”   t | ƒ} | sdS tj | ¡j}d|v r1| dd¡d }| d¡}t|ƒdkr1d |dd… ¡ ¡ S dD ]}||v rE| |d¡d  ¡   S q3| ¡ S )	zKCanonical identity for deduping the same file at different thumbnail sizes.r]   z/thumb/r:   ú/é   Néÿÿÿÿ)z/wikipedia/commons/z/wikipedia/en/z	/commons/)	r0  rß   ÚparseÚurlparseÚpathÚsplitr�   rœ   Úlower)ræ   r6  Úrestr�   Úmarkerr<   r<   r@   Ú_wikimedia_image_identity™  s   
ÿr;  é   c                 C   sr   |rt | ƒ|kr
d S t| d¡ƒ}|sd S t|ƒp|}||v r!d S | |¡ |  || d¡| d¡p3ddœ¡ d S )Nræ   ÚtitleÚsourceÚ	wikipedia©ræ   r=  r>  )r�   r0  rj   r;  Úaddr›   )r�   Úseen_identitiesÚimgÚlimitræ   Úidentr<   r<   r@   Ú_append_unique_imageª  s   
ýrF  c                  C   s*   t j t j t j t¡¡¡} t j | t¡S r    )Úosr6  ÚdirnameÚabspathÚ__file__rœ   ÚSEED_IMAGE_UPLOAD_DIR)Úbaser<   r<   r@   Ú_seed_upload_root»  s   rM  c                 C   s"   t  dd| pd¡d d… }|pdS )Nz	[^\w\-|]+r²   r  éP   )ÚreÚsub)Ú	cache_keyÚsafer<   r<   r@   Ú_seed_upload_subdirÀ  s   rS  c                 C   s   | sd S dt jj| dd�› �S )Nz$/gardentracker/api/seed-type-upload/r1  ©rR  )rß   r4  Úquote©Ú	file_pathr<   r<   r@   Ú_seed_upload_display_urlÅ  s   rX  c                 C   s2   |rt |ƒS t| ƒ}|sd S dtjj|dd�› �S )Nz"/gardentracker/api/seed-image?url=r]   rT  )rX  r0  rß   r4  rU  )ræ   rW  Ú
normalizedr<   r<   r@   Ú_seed_image_display_urlË  s   rZ  c                 C   s¶   | d u rg S t | ttfƒr| jddd�} t | tƒr-zt | ¡} W n tjy,   g  Y S w t | tƒs4g S g }| D ] }t |tƒrD|ddœ}t |t	ƒrX| 
d¡sS| 
d¡rX| |¡ q8|S )NrB   rC   rD   r?  ©ræ   r>  ræ   rW  )rF   rI   Ú	bytearrayrJ   r`   râ   rã   ÚJSONDecodeErrorrÏ   Údictrj   r›   )r�   r˜   rC  r<   r<   r@   Ú_parse_images_rawÔ  s(   
ÿ



€r_  c                 C   sH   t | tƒsd S |  d¡rd| d › �S t|  d¡ƒ}|sd S t|ƒp#|S )NrW  zupload:ræ   )rF   r^  rj   r0  r;  )rC  ræ   r<   r<   r@   Ú_image_list_identityé  s   

r`  c                  C   s0   t jddd�pi } t j d¡p|  d¡pd ¡ S )NT©ÚforceÚsilentræ   r]   )r	   Úget_jsonÚargsrj   rŠ   )Údatar<   r<   r@   Ú_requested_image_urlô  s   rg  c               	   C   s^   t jddd�pi } t j d|  d¡¡}|d u s|dkrd S zt|ƒW S  ttfy.   Y d S w )NTra  Úindexr]   )r	   rd  re  rj   rh   r¼   ra   )rf  r  r<   r<   r@   Ú_requested_image_indexù  s   
ÿri  c                 C   sx   t |ƒ}|rt| tƒsdS t |  d¡ƒ}|r||krdS |  d¡p"d ¡ }|r2|d|› �|fv r2dS t| ƒtd|iƒkS )NFræ   TrW  r]   úseed-upload://)r0  rF   r^  rj   rŠ   r`  )Ú
stored_imgÚrequested_urlrç   Ústored_normrW  r<   r<   r@   Ú_image_matches_request  s   rn  c                 C   s\   t | ƒsdS |  ¡ �}| d|f¡ | ¡ }W d   ƒ n1 s w   Y  t|o,| d¡ƒS )NFzGSELECT images_user_managed FROM GardenSeedTypeInfo WHERE cache_key = %sÚimages_user_managed)Ú_has_images_user_managed_columnre   rf   ri   Úboolrj   ©ro   rQ  rp   rT   r<   r<   r@   Ú_type_info_images_user_managed  s   
þ
ûrs  c                 C   sF  | d u rg S t | ttfƒr| jddd�} t | tƒr-zt | ¡} W n tjy,   g  Y S w t | tƒs4g S g }t	ƒ }| D ]e}t |tƒrG|ddœ}t |t
ƒsMq;| d¡pSd ¡ }t| d¡ƒ}|sb|sbq;t|ƒ}|rl||v rmq;| |¡ |pxd	|› �| d
¡| d¡p‚dt||p‡d d�dœ}|r’||d< | |¡ t|ƒtkr  |S q;|S )NrB   rC   rD   r?  r[  rW  r]   ræ   rj  r=  r>  rV  )ræ   r=  r>  Údisplay_url)rF   rI   r\  rJ   r`   râ   rã   r]  rÏ   Úsetr^  rj   rŠ   r0  r`  rA  rZ  r›   r�   ÚMAX_SEED_TYPE_IMAGES)r�   r˜   ÚseenrC  rW  ræ   rE  Úentryr<   r<   r@   Ú_normalize_seed_images  sP   
ÿ




ü
þry  c                 C   s.   | pd  ¡  ¡ }|pd  ¡  ¡ }|› d|› �S )Nr]   ú|)rŠ   r8  )r�   rˆ   r‹   Úvarr<   r<   r@   Ú_seed_type_cache_keyF  s   r|  c                 C   sR   |   d¡|   d¡|   d¡|   d¡|   d¡|   d¡|   d¡pg |   d¡|   d	¡d
œ	S )NÚslugÚ
commonNameÚscientificNameÚcategoryÚseasonÚdaysToHarvestÚwindowsÚnotesÚ	zoneRange)	r}  Úcommon_nameÚscientific_namer€  r�  Údays_to_harvestrƒ  r„  Ú
zone_range©rj   )Ú	crop_datar<   r<   r@   Ú_map_cropgraph_guideL  s   ÷rŒ  c                 C   s  |pd  ¡ }|s
d S | ¡  dd¡}d|› �}t| |ƒ}|r |S z	td|› �ƒ}W nQ tjjtjjt	j
tfyz   z*tdtj |¡› �ƒ}| d¡pKg }|sRW Y d S |d  d	¡pZ|}td|› �ƒ}W n tjjtjjt	j
tfyw   Y Y d S w Y nw | d
¡p�|}	t|	ƒ}
t| ||
dƒ |
S )Nr]   ú ú-zcrop_detail:z#https://api.cropgraph.com/api/crop/ú'https://api.cropgraph.com/api/search?q=Úresultsr   r}  r‹   é   )rŠ   r8  rC   rî   ré   rß   ÚerrorÚURLErrorÚ	HTTPErrorrâ   r]  ÚOSErrorr4  rU  rj   rŒ  ró   )ro   r�   r‹   r}  rQ  Úcachedrf  Úsearchr�  r‹  Úguider<   r<   r@   Ú_fetch_cropgraph_plantingZ  s:   

ÿÿÿ÷r™  c                 C   s`   t jj| pd dd¡dd�}|sd S z
tt› d|› �ƒW S  t jjt jjt	j
tfy/   Y d S w )Nr]   r�  r²   rT  z/api/rest_v1/page/summary/)rß   r4  rU  rC   ré   ÚWIKIPEDIA_APIr’  r“  r”  râ   r]  r•  )r=  Úencodedr<   r<   r@   Ú_wiki_page_summaryw  s   ÿrœ  é   c                 C   sx   | pd  ¡ }|s
g S ztt› dtj |¡› d|› �ƒ}dd„ | d¡p$g D ƒW S  tjjtjj	t
jtfy;   g  Y S w )Nr]   z/w/rest.php/v1/search/title?q=z&limit=c                 S   ó    g | ]}|  d ¡r|  d ¡‘qS ©r=  rŠ  ©rN   Úpr<   r<   r@   rX   ‰  ó     z'_wiki_search_titles.<locals>.<listcomp>Úpages)rŠ   ré   rš  rß   r4  rU  rj   r’  r“  r”  râ   r]  r•  )ÚqueryrD  Úqrf  r<   r<   r@   Ú_wiki_search_titles�  s   ÿÿr¦  c                 C   sV   | sd S |   d¡p
i }|  d¡p|   d¡pi   d¡}t|ƒ}|s"d S ||   d¡ddœS )NÚ	thumbnailr>  Úoriginalimager=  r?  r@  )rj   r0  )ÚsummaryÚthumbræ   r<   r<   r@   Ú_image_from_wiki_summaryŽ  s   ýr«  c                 C   sÞ   | pd  ¡ } | s
g S tj d| dddddœ¡}ztt› d|› �ƒ}W n tjjtjjt	j
tfy6   g  Y S w g }| d¡p?i  d	i ¡ ¡ D ]"}| d
¡rNqFt| d¡pUi  d¡ƒ}|rh| || d¡ddœ¡ qF|d |… S )Nr]   r¤  Ú
pageimagesrâ   éô  zthumbnail|name)ÚactionÚtitlesÚpropÚformatÚpithumbsizeÚpipropú/w/api.php?r£  Úmissingr§  r>  r=  r?  r@  )rŠ   rß   r4  Ú	urlencoderé   rš  r’  r“  r”  râ   r]  r•  rj   Úvaluesr0  r›   )r=  rD  Úparamsrf  r�   Úpagerª  r<   r<   r@   Ú_wiki_page_images�  s<   úÿ
ÿ
ý€rº  c                 C   s,   | pd  ¡  ¡ }|rt|ƒdkrdS |tv S )Nr]   r<  T)rŠ   r8  r�   ÚGENERIC_VARIETY_TERMS)rˆ   rP   r<   r<   r@   Ú_is_generic_variety½  s   r¼  c                 C   sl   | pd  ¡  ¡ }tƒ }|r4| |¡ | ¡ D ]}t|ƒdkr#| |¡ qt |d¡D ]	}| | ¡ ¡ q*|S )Nr]   r2  r<   )rŠ   r8  ru  rA  r7  r�   ÚCROP_WIKI_ALIASESrj   )r�   r‹   ÚtermsÚwordÚaliasr<   r<   r@   Ú_crop_match_termsÄ  s   

€rÁ  c                 C   s\   | pd  ¡ }|s
dS t|ƒD ]	}||v r dS q|pd ¡   ¡ }|r,t|ƒs,||v r,dS dS )Nr]   FT)r8  rÁ  rŠ   r¼  )r=  r�   rˆ   Útitle_lÚtermr<   r<   r@   Ú_wiki_title_matches_cropÑ  s   ÿrÄ  é   c                 C   s¢   | pd  ¡ } | s
g S tƒ }g }t| |d�D ]}|r+| ¡ |vr+| | ¡ ¡ | |¡ qt|ƒdk rOt| |d�D ]}|rN| ¡ |vrN| | ¡ ¡ | |¡ q8|S )Nr]   ©rD  r2  )rŠ   ru  r¦  r8  rA  r›   r�   Ú_wiki_fulltext_titles)r�   rD  rw  r˜   r=  r<   r<   r@   Ú_wiki_crop_title_candidatesÞ  s"   
€
€rÈ  c                    sH   ‡‡fdd„| p	g D ƒ}|r|S dd„ t ˆƒD ƒ‰ ‡ fdd„| p!g D ƒS )Nc                    s   g | ]
}t |ˆ ˆƒr|‘qS r<   )rÄ  ©rN   Út©r�   rˆ   r<   r@   rX   ñ  rÊ   z+_filter_titles_for_crop.<locals>.<listcomp>c                 S   s   h | ]}|  ¡ ’qS r<   ©r8  rÉ  r<   r<   r@   Ú	<setcomp>ô  rY   z*_filter_titles_for_crop.<locals>.<setcomp>c                    s    g | ]}|pd   ¡ ˆ v r|‘qS )r]   rÌ  rÉ  )Ú	allowlistr<   r@   rX   õ  r¢  )rÈ  )r¯  r�   rˆ   Úfilteredr<   )rÎ  r�   rˆ   r@   Ú_filter_titles_for_cropð  s
   rÐ  c                 C   s   | pd  ¡ } t| ƒrdS | S r»   ©rŠ   r¼  ©rˆ   r<   r<   r@   Ú_variety_for_wiki_searchø  s   rÓ  c                 C   s’   | pd  ¡ } t|ƒ}g }|r"| |› d| › �¡ | | › d|› �¡ | r)| | ¡ tƒ }g }|D ]}| ¡ }|rF||vrF| |¡ | |¡ q0|S ©Nr]   r�  )rŠ   rÓ  r›   ru  r8  rA  )r�   rˆ   Úqueriesrw  r˜   r¥  rí   r<   r<   r@   Ú_wiki_search_queriesÿ  s"   


€rÖ  c                 C   s\   |}t | |ƒD ]}t||d�}t|| |ƒ}|r||f  S qt| |d�}|r*|| fS g d fS )NrÆ  )rÖ  r¦  rÐ  rÈ  )r�   rˆ   rD  Úvariety_for_matchr¤  r¯  ÚrelevantÚcrop_titlesr<   r<   r@   Ú_wiki_resolve_titles  s   ÿrÚ  c                 C   sŒ   | pd  ¡ }|s
g S tj dd||ddœ¡}ztt› d|› �ƒ}W n tjjtjjt	j
tfy5   g  Y S w dd„ | d¡p?i  dg ¡D ƒS )	Nr]   r¤  r—  râ   )r®  rÏ   ÚsrsearchÚsrlimitr±  r´  c                 S   rž  rŸ  rŠ  )rN   Úhitr<   r<   r@   rX   0  r¢  z)_wiki_fulltext_titles.<locals>.<listcomp>)rŠ   rß   r4  r¶  ré   rš  r’  r“  r”  râ   r]  r•  rj   )r¤  rD  r¥  r¸  rf  r<   r<   r@   rÇ    s"   ûÿ	ÿ rÇ  c                    sP   | pd  ¡ ‰ ˆ  d¡sdS t‡ fdd„dD ƒƒsdS d}t‡ fdd„|D ƒƒ S )	Nr]   zfile:Fc                 3   ó   � | ]}|ˆ v V  qd S r    r<   )rN   Úext©rs   r<   r@   Ú	<genexpr>7  ó   € z&_wiki_file_is_photo.<locals>.<genexpr>)ú.jpgú.jpegú.pngú.webp)zcommons-logoz
edit-clearÚsymbolzicon-ÚflagÚmaprõ   Ú	pictogramÚamboxÚ	wikimediaÚquestion_bookÚcrystalc                 3   rÞ  r    r<   ©rN   Útokenrà  r<   r@   rá  =  râ  )r8  r.  Úany)Ú
file_titleÚskip_tokensr<   rà  r@   Ú_wiki_file_is_photo3  s   
rô  c                 C   s  | pd  ¡ } | r|dkrg S t|pg ƒ}tj d| ddddœ¡}ztt› d|› �ƒ}W n tjjtjj	t
jtfy?   g  Y S w g }| d¡pHi  d	i ¡ ¡ D ]}| d¡pWg D ]}| d
¡}t|ƒrh| |¡ qXqO|sng S g }	d}
tdtt|ƒdƒ|
ƒD ]Š}||||
 … }tj dd |¡dddddœ¡}ztt› d|› �ƒ}W n tjjtjj	t
jtfy´   Y q}w | d¡p»i  d	i ¡ ¡ D ]D}| d¡pËi gd }t| d¡pØ| d¡ƒ}|sÝqÂt|ƒpâ|}||v rèqÂ| |¡ |	 || d
¡ddœ¡ t|	ƒ|k�r|	    S qÂq}|	S )Nr]   r   r¤  r�   é   râ   )r®  r¯  r°  Úimlimitr±  r´  r£  r=  rÅ  r‘  rz  Ú	imageinforæ   r­  )r®  r¯  r°  ÚiipropÚ
iiurlwidthr±  Úthumburlr?  r@  )rŠ   ru  rß   r4  r¶  ré   rš  r’  r“  r”  râ   r]  r•  rj   r·  rô  r›   r{   r¥   r�   rœ   r0  r;  rA  )r=  Úskip_identitiesrD  r¸  rf  Úfile_titlesr¹  Úitemrò  r�   Ú
batch_sizer}   ÚbatchÚinfo_paramsÚ	info_dataÚinforæ   rE  r<   r<   r@   Ú_wiki_extra_page_images@  s€   ûÿ	ÿ

€ýúÿ
ÿ
ýÿòr  c           
      C   sÈ   g }t ƒ }t| ||ƒD ]R}t|ƒ|kr nIt|dd�}|r't|||d |ƒ nt|ƒ}t||t|ƒ|ƒ t|ƒ|kr< n"|dkr]t|||t|ƒ d�D ]}	t|||	|ƒ t|ƒ|kr\ nqKq|d |… S )Nr:   rÆ  r   )rû  rD  )ru  rÐ  r�   rº  rF  rœ  r«  r  )
r¯  r�   rˆ   rD  r�   rB  r=  Ú	page_imgsÚprimaryÚextrar<   r<   r@   Ú_collect_wiki_images€  s0   

ýÿ€r  c                 C   sV   |   ¡ �}d}|r|d7 }|d7 }| |¡ | ¡ W  d   ƒ S 1 s$w   Y  d S )NzSELECT * FROM GardenSeedCompanyz WHERE enabled = 1z ORDER BY sort_order, name)re   rf   r€   )ro   Úenabled_onlyrp   Úsqlr<   r<   r@   Ú_get_seed_companiesš  s   

$úr
  c                 C   s   t j | ¡jpd ¡ S r»   )rß   r4  r5  Úhostnamer8  )rt   r<   r<   r@   Ú_company_host¤  r¸   r  é2   c                 C   sp   t j |¡}|jp
d ¡ }|r|jdvrdS t| dd�D ]}t| d¡ƒ}|r5||ks2| 	d| ¡r5 dS qdS )Nr]   )ÚhttpÚhttpsFT©r  rt   Ú.)
rß   r4  r5  r  r8  Úschemer
  r  rj   Úendswith)ro   ræ   Úparsedr   ÚcompanyÚcompany_hostr<   r<   r@   Ú_is_allowed_product_url«  s   €r  c                 C   s>   t |ƒ}t| dd�D ]}t | d¡ƒ|kr| d¡  S q
d S )NTr  rt   rs   )r  r
  rj   )ro   ræ   r   r  r<   r<   r@   Ú_source_name_for_url·  s   ÿr  c                 C   sB   t tƒt tƒB }t| dd�D ]}t| d¡ƒ}|r| |¡ q|S )NTr  rt   )ru  ÚWIKIMEDIA_IMAGE_HOSTSÚSEED_COMPANY_CDN_HOSTSr
  r  rj   rA  )ro   Úhostsr  r   r<   r<   r@   Ú_allowed_image_hosts¿  s   
€r  c                 C   sJ   | pd  ¡ } | s
dS | |v rdS |D ]}| |ks|  d| ¡r" dS qdS )Nr]   FTr  )r8  r  )r   Úallowed_hostsÚallowedr<   r<   r@   Ú_image_host_allowedÈ  s   ÿr  c                 C   s.   | pd  ¡ } t|ƒ}|r|› d| › �  ¡ S | S rÔ  )rŠ   rÓ  rË  r<   r<   r@   Ú_build_seed_search_queryÔ  s
   r   c                 C   s‚   t  dd| pd ¡  ¡ ¡ d¡}|s|S | d¡r|S | d¡r4t|ƒdkr4|d dvr4|d d	… d
 S | d¡r=|d S |d S )Nú
[^a-z0-9]+rŽ  r]   ÚsÚyr:   éþÿÿÿÚaeiour3  Úies)ÚchÚshÚxÚzÚes)rO  rP  rŠ   r8  r  r�   )r�   r}  r<   r<   r@   Ú_crop_plural_slugÜ  s    
"
r,  c                 C   sð  | pd  ¡ } g }t|ƒ}|rÑ| dd¡ dd¡  ¡ }|r5| ¡ | ¡ kr5| |› d| › �  ¡ ¡ | |¡ t d|tj¡rÀt dd|¡}| |› d| › �  ¡ ¡ | dd¡ dd¡  ¡ }|rr| ¡ | ¡ krr| |› d| › �  ¡ ¡ tjdd|tjd	�  ¡ }|rÀ| ¡ | ¡ krÀ| |› d| › �  ¡ ¡ | |¡ | dd¡ dd¡  ¡ }|rÀ| ¡ | ¡ krÀ| |› d| › �  ¡ ¡ | |¡ | |› d| › �  ¡ ¡ | |¡ | rØ| | ¡ t	ƒ }	g }
|D ]}| ¡ }|rõ||	vrõ|	 
|¡ |
 |¡ qß|
S )
Nr]   ú'u   â€™r�  ú\bog\bz\bOG\bÚOrganicz	\s+OG\s*$)Úflags)rŠ   Ú_effective_varietyrC   r8  r›   rO  r—  ÚIrP  ru  rA  )r�   rˆ   rÕ  ÚeffÚapostrophe_freeÚorganic_varÚorganic_freeÚstrippedÚstripped_freerw  r˜   r¤  rí   r<   r<   r@   Ú_seed_company_search_queriesé  sF   






€r9  c                    s^   ˆ pd  ¡ ‰ t‡ fdd„dD ƒƒrdS dˆ v rtt dˆ ¡ƒS dˆ v s+d	ˆ v s+d
ˆ v r-dS dS )Nr]   c                 3   rÞ  r    r<   ©rN   Úskip©r6  r<   r@   rá    ó
   € ÿ
ÿz+_is_company_product_path.<locals>.<genexpr>)z/gift-certificatez'+produrl+'Úprodurlz	/viewcartz/create_reviewFz	/product/z/product/[^/]+(?:/\d+)?/?$z.htmlz/herbs/ú/vegetables/T)r8  rñ  rq  rO  r—  r<  r<   r<  r@   Ú_is_company_product_path  s   þr@  c                    s^   | pd  ¡ ‰ ˆ s
dS t‡ fdd„tD ƒƒrdS dˆ v sdˆ v r!dS dˆ v r-t d	ˆ ¡r-dS dS )
Nr]   Fc                 3   rÞ  r    r<   r:  ©r/  r<   r@   rá  "  râ  z-_is_seed_company_image_url.<locals>.<genexpr>z/images/popup/z/images/products/Trô   z//images/[^/]*\d{3,}[^/]*\.(?:jpg|jpeg|png|webp))r8  rñ  ÚSEED_COMPANY_IMAGE_SKIP_TOKENSrO  r—  ©ræ   r<   rA  r@   Ú_is_seed_company_image_url  s   þrD  c           
      C   s  t |  d¡ƒ}|dkrg S |  d¡pd d¡}g }|D ]^}zLtj dd|dœ¡ ¡ }tjj|› d�|d	d
d|› d�dœdd�}tjj	|dd��}| 
¡ jddd�}	W d   ƒ n1 s[w   Y  | t|	| ƒ¡ W q tjjtjjtfyy   Y qw tt |¡ƒS )Nrt   úwww.totallytomato.comr]   r1  ÚSearchÚprodcat)r®  Úsearch_typeÚkeywordz
/index.phpú<Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36z!application/x-www-form-urlencodedú/text/html,application/xhtml+xml;q=0.9,*/*;q=0.8)rÚ   úContent-TypeÚAcceptÚRefererÚPOST)rf  rÜ   Úmethodé   rÝ   rB   rC   rD   )r  rj   Úrstriprß   r4  r¶  Úencoder	   rà   rá   rä   rJ   ÚextendÚ_extract_company_product_linksr’  r“  r”  r•  rÏ   r^  Úfromkeys)
r  Úsearch_queriesr   rt   Úlinksr¤  rë   rç   rè   Úsearch_htmlr<   r<   r@   Ú_company_post_search_links.  s@   ýüù	ôÿÿrZ  c                    s²   g ‰ t ƒ ‰‡ ‡fdd„}|D ]%}z	tt| |ƒƒ}W n tjjtjjtfy+   d}Y nw |t|| ƒƒ q|t	| |ƒƒ |t
| |ƒƒ t|  d¡ƒ}|rW|D ]	}|t||ƒƒ qMˆ S )Nc                    s2   | pg D ]}|r|ˆvrˆ  |¡ ˆ  |¡ qd S r    )rA  r›   )Ú	new_linksÚlink©rX  rw  r<   r@   rA  U  s   

€ýz+_collect_company_product_links.<locals>.addr]   rt   )ru  Ú_fetch_html_urlÚ_company_search_urlrß   r’  r“  r”  r•  rU  Ú_company_browse_linksrZ  r  rj   Ú_google_site_search_links)r  r�   rˆ   rW  rA  Úsearch_queryrY  Údomainr<   r]  r@   Ú_collect_company_product_linksQ  s"   ÿrd  c           	      C   sî   t |  d¡ƒ}|  d¡pd d¡}|pd ¡ }|r|r|sg S g }|dkr5t|ƒ}|r4| d|› d�¡ n|dkrHt|ƒ}|rH| d|› d�¡ g }|D ]#}zt|| ƒ}W n tjj	tjj
tfyf   Y qLw | t|| ƒ¡ qLtt |¡ƒS )	Nrt   r]   r1  zwww.johnnyseeds.comr?  rE  z/vegetables/vegetable-seeds/z-seeds)r  rj   rR  rŠ   r,  r›   r^  rß   r’  r“  r”  r•  rT  rU  rÏ   r^  rV  )	r  r�   r   rt   ÚpathsÚpluralrX  r6  Úbrowse_htmlr<   r<   r@   r`  l  s0   €ÿr`  c                 C   s„   t | ƒ}|g}t d|¡r| t dd|¡¡ | dd¡}|r$| |¡ t d|¡}|r;| | d¡› | d¡› �¡ tt	 
|¡ƒS )Nr.  r  r�  r]   ú^([a-z])\s+(\d+)\br:   r<  )Ú_normalize_match_textrO  r—  r›   rP  rC   ÚmatchÚgrouprÏ   r^  rV  )rˆ   Únorm_varÚformsÚcompactÚ
code_matchr<   r<   r@   Ú_variety_match_forms…  s   
rp  c                 C   s    | pd  ¡ } | rt| ƒrdS | S r»   rÑ  rÒ  r<   r<   r@   r1  “  s   r1  c                 C   s<   | pd  ¡ } |  dd¡ dd¡} t dd| ¡} d |  ¡ ¡S )Nr]   rŽ  r�  r²   r!  )r8  rC   rO  rP  rœ   r7  )Útextr<   r<   r@   ri  š  s   ri  c                    s\  t | ƒ} | sdS td |¡ƒ}|sdS | dd¡}t| ƒD ]}||v r& dS | dd¡}t|ƒdkr9||v r9 dS qt| ƒ}g }| ¡ D ]}t|ƒdkrR| |¡ qD|dv r[| |¡ qDt 	d	|¡}	|	rs| |	 
d
¡› |	 
d¡› �¡ |s�| dd¡|v rdS dS |‰ d|v r‹ˆ d7 ‰ t‡ fdd„|D ƒƒ}
|
t|ƒ }|dkr¢dS |dkr¨dS |
r¬dS dS )Néd   r�  r   r]   é   éb   r2  )Úogr  r  r  rh  r:   r<  ru  z organicc                 3   ó   � | ]	}|ˆ v rd V  qdS ©r:   Nr<   rï  ©Úexpandedr<   r@   rá  ¾  ó   € z'_variety_match_score.<locals>.<genexpr>g      ð?é_   g      à?éA   é#   )r1  ri  rœ   rC   rp  r�   r7  r›   rO  rj  rk  Úsum)rˆ   ÚtextsÚcombinedÚcompact_textÚformÚcompact_formrl  Ú
var_tokensrð  ro  ÚmatchedÚratior<   rx  r@   Ú_variety_match_score¡  sN   €
€r‡  c                    s–   | pd  ¡ } | s
dS t| ƒ}td |¡ƒ‰ ˆ sdS |ˆ v rdS dd„ | ¡ D ƒ}|s6|ˆ  dd¡v r4dS dS t‡ fdd„|D ƒƒ}td| t|ƒ ƒS )	Nr]   rr  r�  r   c                 S   s   g | ]
}t |ƒd kr|‘qS )r2  )r�   rÉ  r<   r<   r@   rX   Ó  rÊ   z%_crop_match_score.<locals>.<listcomp>c                 3   rv  rw  r<   rï  ©r€  r<   r@   rá  Ö  rz  z$_crop_match_score.<locals>.<genexpr>)rŠ   ri  rœ   r7  rC   r~  rh   r�   )r�   r  Ú	norm_cropÚcrop_tokensr…  r<   rˆ  r@   Ú_crop_match_scoreÉ  s   r‹  c           	      C   s�   | pd|pd|p	dg}t |g|¢R Ž }|dk rdS t|g|¢R Ž }t|ƒr+|dk r+dS t|ƒr8|d |d  }nt|ƒ}dt| ƒv rF|d7 }|S )	Nr]   é(   r   r  g      è?g      Ð?r.   r�  )r‹  r‡  r1  rH   ri  )	ræ   r�   rˆ   r=  r©  r  Ú
crop_scoreÚvariety_scoreÚscorer<   r<   r@   Ú_score_seed_candidateÚ  s   r�  c                 C   s>   |   d¡pd d¡}|   d¡pd}tj |¡}|j|||d�S )Nrt   r]   r1  Úsearch_url_templater  )rt   r¤  Úencoded_query)rj   rR  rß   r4  rU  r±  )r  r¤  rt   ru   r›  r<   r<   r@   r_  ë  s   
þr_  c                 C   s^   t jj| ddddœd�}t jj||d��}| ¡ jddd	�W  d   ƒ S 1 s(w   Y  d S )
NrJ  rK  zen-US,en;q=0.9)rÚ   rM  zAccept-LanguagerÛ   rÝ   rB   rC   rD   )rß   r	   rà   rá   rä   rJ   rå   r<   r<   r@   r^  õ  s   úþ$ÿr^  c                 C   s\   |r	d|› d�}nd|› d�}t  t  |¡› d�| t jt jB ¡}|s$d S t | d¡ ¡ ¡S )Nz
property="ú"zname="z\s+content=(['\"])(.*?)\1r<  )	rO  r—  Úescaper2  ÚSÚhtmlÚunescaperk  rŠ   )Ú	page_htmlrs   r°  Úattrrj  r<   r<   r@   Ú_html_meta_content  s   
ýrš  c                 C   s   t | ||ƒS r    )r�  )ræ   r�   rˆ   r<   r<   r@   Ú_score_seed_link  ó   r›  c                    sà   |  d¡pd d¡}t|ƒ}g }t d| pd¡D ]P}| dd¡}| d¡r)|| }| d¡s/qtj 	|¡}|r@|j
r@||j
vr@q|jpDd ¡ ‰ t‡ fdd	„d
D ƒƒrSqtˆ ƒrh| |jd |j |j d¡ ¡ qtt |¡ƒS )Nrt   r]   r1  zhref="([^"]+)"z&amp;ú&r  c                 3   rÞ  r    r<   r:  r<  r<   r@   rá  &  r=  z1_extract_company_product_links.<locals>.<genexpr>)	z/searchz
/customer-z/growers-libraryz/shop-by-colorz/cartz/accountz/loginz	/shippingz/privacyz://)rj   rR  r  rO  ÚfindallrC   r.  rß   r4  r5  r  r6  r8  rñ  r@  r›   r  ÚnetlocrÏ   r^  rV  )r–  r  rt   Ú	base_hostrX  Úhrefr  r<   r<  r@   rU    s*   

þ"€rU  c                 C   sÄ   t rtsg S tj t td| › d|› �t|dƒdœ¡}ztd|› �dd�}W n tjjtjj	t
jtfy9   g  Y S w g }| d¡pBg D ]}| d	¡}|rV| | d
¡d ¡ qCtt |¡ƒd |… S )Nzsite:r�  é
   )rí   Úcxr¥  Únumz+https://www.googleapis.com/customsearch/v1?r9   rÝ   rS   r\  ú?r   )r   r   rß   r4  r¶  r¥   ré   r’  r“  r”  râ   r]  r•  rj   r›   r7  rÏ   r^  rV  )rc  r¤  rD  r¸  rf  rX  rý  r\  r<   r<   r@   ra  3  s0   üÿ
þÿ
€ra  c              
      s  zt | ƒ‰W n tjjtjjtfy   Y d S w ‡fdd„}‡fdd„}|dƒp+|dƒ‰ˆsKt dˆtj¡}|rKt	 
| d¡ ¡ ¡‰t d	d
ˆ¡ ¡ ‰ˆsnt dˆtjtjB ¡}|rnt	 
t dd
| d¡¡¡‰d ˆ ¡ ¡‰|dƒpu|dƒ}|s™t dˆtjtjB ¡}|r™t	 
t dd
| d¡¡¡}d | ¡ ¡}g ‰ tƒ ‰‡ ‡‡fdd„}t dˆtj¡D ]}||ƒ q®t dˆtj¡D ]}||ƒ q½t|dƒƒ}	|	rÐ||	ƒ t dˆtj¡D ]o}
z
t |
 d¡¡}W n
 tjyî   Y qØw t|tƒrö|n|g}|D ]K}t|tƒ�r| d¡dk�rqû|�s| d¡}ˆ�s| d¡‰| d¡}t|tƒ�r0|�r.|d nd }t|tƒ�r;| d¡}t|ƒ}|�rF||ƒ qûqØ|�sPˆ�rPˆ}|�sXˆ �sXd S ˆ�rgt	 
ˆ¡‰d ˆ ¡ ¡‰|�rvt	 
|¡}d | ¡ ¡}ˆ|ˆ d d… | dœS )Nc                    ó   t ˆ | d�S )N©r°  ©rš  r§  ©r˜  r<   r@   Úmeta_propertyS  rœ  z1_extract_product_page_info.<locals>.meta_propertyc                    r¦  )Nrà  r¨  rà  r©  r<   r@   Ú	meta_nameV  rœ  z-_extract_product_page_info.<locals>.meta_namezog:titler=  z<title>([^<]+)</title>r:   z\s*\|.*$r]   z<h1[^>]*>(.*?)</h1>z<[^>]+>r�  zog:descriptionÚdescriptionzC<p[^>]*>\s*((?:A [^<]{40,600}|(?:A blend of|This)[^<]{20,600}))</p>c                    sP   t | ƒ}|r
t|ƒsd S t|ƒp|}|ˆv rd S ˆ |¡ ˆ  |ˆddœ¡ d S )NÚseed_companyr@  )r0  rD  r;  rA  r›   )ræ   rY  rE  )r�   rw  r=  r<   r@   Ú	add_imageq  s   
ýz-_extract_product_page_info.<locals>.add_imagez>https://[^"\']+/images/products/[^"\']+\.(?:jpg|jpeg|png|webp)z;https://cdn\.commercev3\.net/[^"\']+\.(?:jpg|jpeg|png|webp)zog:imagez2<script type="application/ld\+json">(.*?)</script>z@typeÚProductrs   Úimager   ræ   r<  )r=  r©  r�   Ú
source_url)r^  rß   r’  r“  r”  r•  rO  r—  r2  r–  r—  rk  rŠ   rP  r•  rœ   r7  ru  rž  r0  Úfinditerrâ   rã   r]  rF   rÏ   r^  rj   )Úpage_urlrª  r«  Útitle_matchÚh1_matchr©  Ú
lead_matchr®  ræ   Úog_imagerj  rë   rS   rý  r°  r<   )r�   r˜  rw  r=  r@   Ú_extract_product_page_infoM  s²   ÿ
ýý
ý
ýÿ



€ò


ür¸  c                 C   sx   t | pg ƒ}dd„ |D ƒ}| d ¡ |pg D ]}t|ƒ}|r"||v r#q| |¡ | |¡ t|ƒ|kr5 nq|d |… S )Nc                 S   s   h | ]}t |ƒ’qS r<   )r`  ©rN   rC  r<   r<   r@   rÍ  ¿  rY   z%_merge_seed_images.<locals>.<setcomp>)rÏ   Údiscardr`  rA  r›   r�   )ÚexistingÚ
new_imagesrD  Úmergedrw  rC  rE  r<   r<   r@   Ú_merge_seed_images½  s   


ÿr¾  c              
   C   sz   |   ¡ �+}| d|f¡ | ¡ r	 W d   ƒ d S | d|||p!d t g ¡f¡ W d   ƒ n1 s2w   Y  |  ¡  d S )Nz=SELECT cache_key FROM GardenSeedTypeInfo WHERE cache_key = %sz„
            INSERT INTO GardenSeedTypeInfo (cache_key, crop_name, variety, images)
            VALUES (%s, %s, %s, %s)
            )re   rf   ri   râ   rð   rm   )ro   rQ  r�   rˆ   rp   r<   r<   r@   Ú_ensure_seed_type_info_rowÌ  s   
ýûür¿  c                 C   sT   |   ¡ �}| d¡ t| ¡ pi  d¡pdƒdkW  d   ƒ S 1 s#w   Y  d S )Nzò
            SELECT COUNT(*) AS cnt
            FROM information_schema.COLUMNS
            WHERE TABLE_SCHEMA = DATABASE()
              AND TABLE_NAME = 'GardenSeedTypeInfo'
              AND COLUMN_NAME = 'images_user_managed'
            rd   r   )re   rf   rh   ri   rj   rÁ   r<   r<   r@   rp  Û  s   
ÿ	$örp  c                 C   sV   |   ¡ �}| d|f¡ | ¡ }W d   ƒ n1 sw   Y  t|r(| d¡ƒS d ƒS )Nz:SELECT images FROM GardenSeedTypeInfo WHERE cache_key = %sr�   )re   rf   ri   r_  rj   rr  r<   r<   r@   Ú_load_type_info_imagesé  s
   

þrÀ  c                 C   s‚   t | |||ƒ |  ¡ �(}t |d t… ¡}|r$t| ƒr$| d||f¡ n| d||f¡ W d   ƒ n1 s6w   Y  |  ¡  d S )Nz™
                UPDATE GardenSeedTypeInfo
                SET images = %s, images_user_managed = 1
                WHERE cache_key = %s
                z>UPDATE GardenSeedTypeInfo SET images = %s WHERE cache_key = %s)r¿  re   râ   rð   rv  rp  rf   rm   )ro   rQ  r�   rˆ   r�   Úuser_managedrp   rë   r<   r<   r@   Ú_save_type_info_imagesð  s   
ú	þ€ôrÂ  c                 C   sp   | rd| v s|   d¡rd S tƒ }tj || ¡}tj |¡r6zt |¡ W d S  ty5   t 	d| ¡ Y d S w d S )Nú..r1  zCould not delete seed upload %s)
r.  rM  rG  r6  rœ   ÚisfileÚremover•  ÚloggerÚwarning)rW  Úupload_rootÚfullr<   r<   r@   Ú_delete_seed_upload_file  s   ÿýrÊ  c                 C   sz  t | dd�}|sd g d d d dœS t||ƒ}|sd g d d d dœS d }d}t||ƒ}|D ]©}t||||ƒ}	g }
t |	¡D ]}t|||ƒ}|dkrGq:|
 ||f¡ q:|
jdd� |
d d… D ]c\}}t	|ƒ}|sfq[t|||| 
d¡| 
d	¡d
�}|dkrzq[|| 
d	¡r‚dnd t| 
d¡p‹g ƒ }||krª|}| 
d	¡| 
d¡pžg d| 
d¡| 
d¡dœ}|r¾| 
d	¡r¾t| 
d¡p¸g ƒdkr¾ nq[|rÓ| 
d	¡rÓt| 
d¡pÍg ƒdkrÓ nq*|�rµg }dd„ | 
d¡pâg D ƒ}|D ]¿}t| 
d¡pðg ƒdkrö n±z	tt||ƒƒ}W n tjjtjjtf�y   Y qçw t||ƒ}	t |	¡D ]Š}|| 
d¡k�r'�qt|||ƒ}|dk�r4�qt	|ƒ}|�s=�qt|||| 
d¡| 
d	¡d
�}|dk�rS�q| 
d¡�pZg D ]5}t| 
d¡ƒ�pi| 
d¡}|�r�||v�r�| |¡ | |¡ t| 
d¡�p„g ƒt|ƒ dk�r� n�q[t| 
d¡�p™g ƒt|ƒ dk�r¤ n�qqçt| 
d¡|dd�|d< |S d g d d d dœS )NTr  )r©  r�   Úinfo_sourcer±  Úsource_namer3  r   )Úreverseé   r=  r©  ©r=  r©  rs  r�   r­  r±  rs   r<  c                 S   s$   h | ]}t | d ¡ƒp| d ¡’qS rC  )r;  rj   r¹  r<   r<   r@   rÍ  R  s    ÿÿz+_fetch_seed_company_info.<locals>.<setcomp>ræ   rÆ  )r
  r   r9  rd  r^  rV  r�  r›   Úsortr¸  rj   r�   r^  r_  rß   r’  r“  r”  r•  rU  r;  rA  r¾  )ro   r�   rˆ   Ú	companiesr¤  ÚbestÚ
best_scorerW  r  rX  Ú
candidatesr\  Ú	url_scorer¹  Ú
full_scoreÚtotal_scoreÚextra_imagesrw  rY  rC  rE  r<   r<   r@   Ú_fetch_seed_company_info  sØ   û
û
û(û$€$€þÿ

û


"€"ÿ€ûrÙ  c           	      C   sÊ   | pd  ¡ } |p	d  ¡ }| s|sd d d g dœS t| |ƒ\}}|s(| r(t| ƒ}| }g }d }d }d }|rZt|d ƒ}|rR| d¡}| d¡}| d¡pJi  di ¡ d¡}t|| |td	�}||||d t… dœS )
Nr]   )r©  Úwikipedia_titleÚwikipedia_urlr�   r   Úextractr=  Úcontent_urlsÚdesktopr¹  rÆ  )rŠ   rÚ  rÇ  rœ  rj   r  ÚWIKI_IMAGE_LIMIT)	r�   rˆ   r¯  Úmatched_queryr�   Úsummary_textÚ
wiki_titleÚwiki_urlr  r<   r<   r@   Ú_fetch_wikipedia_seed_info…  s8   ü


ürä  c                 C   s–   | sd S t |  d¡ƒ}|  d¡}t|ttfƒr|jddd�}t|tƒr(t |¡}|  d¡|  d¡|  d¡|  d	¡|  d
¡|  d¡||t	|  d¡ƒdœ	S )Nr�   r¤   rB   rC   rD   r©  rÚ  rÛ  rË  r±  rÌ  Ú
fetched_at)	r©  rÚ  rÛ  rË  r±  rÌ  r�   r¤   rå  )
ry  rj   rF   rI   r\  rJ   r`   râ   rã   rM   )rT   r�   r¤   r<   r<   r@   Ú_serialize_seed_type_infoª  s$   


÷ræ  c                 C   sV   t ||ƒ}|  ¡ �}| d|f¡ | ¡ }W d   ƒ t|ƒS 1 s"w   Y  t|ƒS )Nú5SELECT * FROM GardenSeedTypeInfo WHERE cache_key = %s)r|  re   rf   ri   ræ  )ro   r�   rˆ   rQ  rp   rT   r<   r<   r@   rŽ   À  s   



þýrŽ   c                 C   sL   | sdS |   d¡p
d ¡ rdS |   d¡rdS |   d¡rdS |   d¡r$dS dS )NFr©  r]   Tr�   r¤   rÛ  r‰   )r�   r<   r<   r@   Ú_seed_type_info_has_contentÈ  s   


rè  c                 C   s.  t ||ƒ}t| |||ƒ |  ¡ �}| d|f¡ | ¡ pi }W d   ƒ n1 s(w   Y  t| d¡ƒ}t| |ƒr<|}n
t|| d¡pDg ƒ}| d¡}	| d¡pT| d¡}
t	| |	ƒ}|  ¡ �'}| d|||pgd |
| d¡| d¡d	|	|t
 |¡| d
¡f¡ W d   ƒ n1 sˆw   Y  |  ¡  t| ||ƒS )Nrç  r�   r±  r©  r=  a²  
            INSERT INTO GardenSeedTypeInfo
                (cache_key, crop_name, variety, summary, wikipedia_title, wikipedia_url,
                 info_source, source_url, source_name, images, planting)
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                crop_name = VALUES(crop_name),
                variety = VALUES(variety),
                summary = VALUES(summary),
                info_source = VALUES(info_source),
                source_url = VALUES(source_url),
                source_name = VALUES(source_name),
                images = VALUES(images),
                fetched_at = CURRENT_TIMESTAMP
            rÚ  rÛ  r­  r¤   )r|  r¿  re   rf   ri   ry  rj   rs  r¾  r  râ   rð   rm   rŽ   )ro   r�   rˆ   Ú	page_inforQ  rp   r»  Úexisting_imagesr�   r±  r©  rÌ  r<   r<   r@   Ú_apply_product_page_infoÖ  s@   

þ



õðÿrë  c                 C   s¼  t ||ƒ}|d urt|ƒ}nt| |ƒ}t| |ƒ}t| ||ƒ}| d¡}t| d¡p*g ƒ}	| d¡}
| d¡}| d¡}d }d }| }t|	ƒdk }|sL|r•t||ƒ}|rk| d¡rk| d¡}| d¡}| d¡}|
skd	}
|r}t|	| d¡pug d t	… t
d
�}	|
s•| d¡s‰| d¡r•d	}
| d¡}| d¡}t| |ƒr t| |ƒ}	nt||	t
d
�}	|  ¡ �%}| d|||p´d ||||
||t |	¡|rÅt |¡nd f¡ W d   ƒ n1 sÓw   Y  |  ¡  d S )Nr©  r�   rË  r±  rÌ  r<  rÚ  rÛ  r?  rÆ  aQ  
            INSERT INTO GardenSeedTypeInfo
                (cache_key, crop_name, variety, summary, wikipedia_title, wikipedia_url,
                 info_source, source_url, source_name, images, planting)
            VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
            ON DUPLICATE KEY UPDATE
                crop_name = VALUES(crop_name),
                variety = VALUES(variety),
                summary = VALUES(summary),
                wikipedia_title = VALUES(wikipedia_title),
                wikipedia_url = VALUES(wikipedia_url),
                info_source = VALUES(info_source),
                source_url = VALUES(source_url),
                source_name = VALUES(source_name),
                images = VALUES(images),
                planting = VALUES(planting),
                fetched_at = CURRENT_TIMESTAMP
            )r|  rÏ   rÀ  r™  rÙ  rj   r�   rä  r¾  rß  rv  rs  re   rf   râ   rð   rm   )ro   r�   rˆ   Úpreserved_imagesrQ  rê  r¤   r  r©  r�   rË  r±  rÌ  râ  rã  Úneed_summaryÚneed_imagesÚwikirp   r<   r<   r@   Ú_fetch_and_store_seed_type_info
  sl   











ý



õíÿ"rð  c                 C   s0   t � | tv W  d   ƒ S 1 sw   Y  d S r    )Ú_seed_type_fetch_lockÚ_seed_type_fetch_inflight)rQ  r<   r<   r@   Ú_is_seed_type_fetch_inflightZ  s   $ÿró  c                    s˜   t ˆˆƒ‰ t�" |d ur|tˆ < ˆ tv r	 W d   ƒ d S t ˆ ¡ W d   ƒ n1 s,w   Y  ‡ ‡‡fdd„}tj|ddˆ d d… › �d� ¡  d S )Nc                     sö   d } z`z5t � t ˆ d ¡} W d   ƒ n1 sw   Y  tƒ }zt|ƒ t|ˆˆ| d� W | ¡  n| ¡  w W n tyG   t 	dˆ ¡ Y nw W t � t
 ˆ ¡ W d   ƒ d S 1 s\w   Y  d S t � t
 ˆ ¡ W d   ƒ w 1 suw   Y  w )N©rì  z"Seed type enrichment failed for %s)rñ  Ú_seed_type_preserved_imagesÚpoprA   rv   rð  ÚcloseÚ	ExceptionrÆ  Ú	exceptionrò  rº  )Ú	preservedro   ©rQ  r�   rˆ   r<   r@   Úworkeri  s0   ÿüÿ€(ÿÿz&_queue_seed_type_fetch.<locals>.workerTz
seed-type-é    ©ÚtargetÚdaemonrs   )r|  rñ  rõ  rò  rA  Ú	threadingÚThreadr}   )r�   rˆ   rì  rü  r<   rû  r@   Ú_queue_seed_type_fetch_  s    
üûý
r  c                 C   sP   t | ƒpi }t| d¡pdƒ}t|ƒ}| d¡p|}t|d ƒt|ƒk|d< |S )NÚlow_seed_thresholdr�  Úlow_stock_thresholdÚqtyÚ	low_stock)rÂ   rh   rj   rU   rH   )ro   rT   ÚsettingsÚ	thresholdrý  rD  r<   r<   r@   Ú_serialize_seed_with_stock†  s   r
  c                 C   sH   t | ƒpi }| d¡pt}| d¡pt}| d¡pt}t|ƒt|ƒ|fS )NÚlatÚlngÚzip_code)rÂ   rj   r   r   r   rH   )ro   r  r  r  r  r<   r<   r@   Ú_get_coords�  s
   r  c                 C   s¢   | s| S t | ƒ ¡ }| d¡}t|ƒdkr/tdd„ |D ƒƒr/t|d ƒd›dt|d ƒd›�S t|ƒd	krOtd
d„ |D ƒƒrOt|d ƒd›dt|d ƒd›�S |S )z+Format MM-DD or date-like strings as MM/DD.rŽ  r<  c                 s   ó   � | ]}|  ¡ V  qd S r    ©Úisdigitr   r<   r<   r@   rá  �  râ  z_format_mmdd.<locals>.<genexpr>r   Ú02dr1  r:   r2  c                 s   r  r    r  r   r<   r<   r@   rá  Ÿ  râ  )r`   rŠ   r7  r�   Úallrh   )rL   r"  r�   r<   r<   r@   Ú_format_mmdd—  s   
""r  c                 C   s:  t | dƒ}|r	|S t| ƒpi }| d¡pt}t| ƒ\}}}z
td|› d�ƒ}W n tjjtjj	t
jtfy;   i }Y nw ztd|› d|› �ƒ}W n tjjtjj	t
jtfy\   i }Y nw | d¡pci }	| d¡pji }
||	 d¡pu| d¡| d	¡t|
 d
¡ƒt|
 d¡ƒ| d¡| d¡||dœdœ}t| d|dƒ |S )Nzzone_info:v2r  zhttps://phzmapi.org/z.jsonz'https://api.cropgraph.com/api/zone?lat=ú&lng=ÚzoneÚ
frostDatesÚtemperature_rangeÚ
lastSpringÚ	firstFallÚ
seasonDaysÚclimateType)r  r  )r‚   r  r  Úlast_spring_frostÚfirst_fall_frostÚseason_daysÚclimate_typeÚcoordinatesr‘  )rî   rÂ   rj   r   r  ré   rß   r’  r“  r”  râ   r]  r•  r  ró   )ro   r–  r  r  r  r  r²   ÚphzmÚ	cropgraphr  ÚfrostÚresultr<   r<   r@   Ú_get_zone_info¤  s>   
ÿÿÿø
r&  c                  C   s¢   t } t}d| › d|› d�}z0t|ƒ}| d¡pi }| d¡pi }| d¡| d¡| d¡p/d gd	 | d
¡p8d gd	 dœW S  tjjtjjtj	t
fyP   Y d S w )Nz0https://api.open-meteo.com/v1/forecast?latitude=z&longitude=z’&current=temperature_2m,relative_humidity_2m&daily=temperature_2m_max,temperature_2m_min&temperature_unit=fahrenheit&timezone=auto&forecast_days=2ÚcurrentÚdailyÚtemperature_2mÚrelative_humidity_2mÚtemperature_2m_maxr   Útemperature_2m_min)Útemp_fÚhumidityÚ	today_maxÚ	today_min)r   r   ré   rj   rß   r’  r“  r”  râ   r]  r•  )r  r  ræ   rf  r'  r(  r<   r<   r@   Ú_get_weatherÅ  s(   ÿÿÿüÿr1  c                 C   s$   t |  d¡pdƒ}t ¡ t|d� S )NÚinterval_daysé   ©r¦   )rh   rj   r   rì   r   )ÚscheduleÚintervalr<   r<   r@   Ú_advance_schedule_next_dueÜ  s   r7  c                 C   sB   |   d¡p
|   dd¡}|r|› d|› d�S |r|› d|› d�S |S )Nr=  Ú	task_typeÚTaskú (ú)rŠ  )r5  Ú
plant_nameÚ	tank_namer=  r<   r<   r@   Ú_schedule_labelá  s   r>  c               
   C   sf  t ƒ } �z©�zŠt| ƒ t| ƒpi }| dd¡sW W |  ¡  d S t| d¡p&dƒ}t ¡ }t t	 
¡ tj ¡ ¡}|  ¡ ��F}| d|f¡ | ¡ }|D ]S}t|d ƒ}| d|f¡ | ¡ }	|	rc|	d nd }
|
rl|
|krlqJ| d	|d |f¡ | ¡ r{qJt|| d
¡| d¡ƒ}d|› d�}t|ƒ\}}|r�| d||f¡ qJ| d|f¡ | ¡ D ]T}d|d › �}| d|f¡ | ¡ }	|	rÃ|	d nd }
|
rÌ|
|krÌq©d|d › | d¡rÞd|d  d nd› d|d › d|d › d�}t|ƒ\}}|rý| d||f¡ q©| d¡ | ¡ }|D ]q}| d|d f¡ | ¡ }t| d ¡�p d!ƒ}d"}|�s*d#}n|d$ t|d%� }||k}|�s<�q	t|d ƒ}| d&|f¡ | ¡ }	|	�rT|	d nd }
|
�r`|
|k�r`�q	d'|d( › d)�}t|ƒ\}}|�ry| d*||f¡ �q	W d   ƒ n	1 �s†w   Y  |  ¡  W n t�yŸ   t d+¡ Y nw W |  ¡  d S W |  ¡  d S |  ¡  w ),NÚnotifications_enabledr:   r  r�  a.  
                SELECT s.*, p.name AS plant_name, t.name AS tank_name
                FROM GardenSchedule s
                LEFT JOIN GardenPlant p ON p.id = s.plant_id
                LEFT JOIN GardenTank t ON t.id = s.tank_id
                WHERE s.enabled = 1 AND s.next_due <= %s
                r   z·
                    SELECT last_notified_at FROM alert_state
                    WHERE source_type = 'garden_schedule' AND source_id = %s AND metric = 'reminder'
                    Úlast_notified_atz«
                    SELECT id FROM GardenTaskCompletion
                    WHERE schedule_id = %s AND completed_at >= %s
                    LIMIT 1
                    r<  r=  zGarden Tracker: z is dueaF  
                        INSERT INTO alert_state (source_type, source_id, metric, last_state, last_notified_at)
                        VALUES ('garden_schedule', %s, 'reminder', 'due', %s)
                        ON DUPLICATE KEY UPDATE last_state = 'due', last_notified_at = VALUES(last_notified_at)
                        zŠ
                SELECT s.* FROM GardenSeedInventory s
                WHERE s.qty <= COALESCE(s.low_stock_threshold, %s)
                Úseed_z´
                    SELECT last_notified_at FROM alert_state
                    WHERE source_type = 'garden_seed' AND source_id = %s AND metric = 'low_stock'
                    u#   Garden Tracker: Low seed stock â€” r�   rˆ   r:  r;  r]   z: r  r�  Úunitz
 remainingaC  
                        INSERT INTO alert_state (source_type, source_id, metric, last_state, last_notified_at)
                        VALUES ('garden_seed', %s, 'low_stock', 'low', %s)
                        ON DUPLICATE KEY UPDATE last_state = 'low', last_notified_at = VALUES(last_notified_at)
                        zSELECT * FROM GardenTankúœ
                    SELECT recorded_at FROM GardenTankReading
                    WHERE tank_id = %s ORDER BY recorded_at DESC LIMIT 1
                    Útest_interval_daysr-  FTÚrecorded_atr4  z·
                    SELECT last_notified_at FROM alert_state
                    WHERE source_type = 'garden_tank' AND source_id = %s AND metric = 'test_overdue'
                    zGarden Tracker: Tank 'rs   z' needs testing (pH/EC)aF  
                        INSERT INTO alert_state (source_type, source_id, metric, last_state, last_notified_at)
                        VALUES ('garden_tank', %s, 'test_overdue', 'due', %s)
                        ON DUPLICATE KEY UPDATE last_state = 'due', last_notified_at = VALUES(last_notified_at)
                        zGarden reminder check failed)rA   rv   rÂ   rj   r÷  rh   r   rì   Úcombiner   Útodayr¥   Útimere   rf   r€   r`   ri   r>  r   r   rm   rø  rÆ  rù  )ro   r  Úlow_thresholdrÎ   Útoday_startrp   Údue_schedulesÚschedÚsidÚ	alert_rowÚlast_notifiedr„   ÚmsgÚokÚerrr.   r²   ÚtanksÚtankÚlast_readingr6  ÚoverdueÚdue_byÚtidr<   r<   r@   Ú_check_remindersê  sì     �õø
û
úÿú€	ûû
ÿþþÿú€
	ûûú€ß§ ÿÿþrY  c                  C   s,   t dtd ƒ} t d¡ 	 tƒ  t | ¡ q)Né<   r¢  )r§   r   rH  ÚsleeprY  )Úinterval_secsr<   r<   r@   Ú_reminder_loop~	  s   

þr]  c                  C   s\   t �" tr	 W d   ƒ d S tjtddd�} |  ¡  daW d   ƒ d S 1 s'w   Y  d S )NTzgarden-remindersrþ  )Ú_reminder_lockÚ_reminder_thread_startedr  r  r]  r}   )rÊ  r<   r<   r@   Ústart_reminder_thread†	  s   þ"ûr`  c                   C   s
   t ƒ  d S r    )r`  r<   r<   r<   r@   Ú_ensure_reminder_thread�	  s   
ra  z/api/healthc               
   C   s¾   zt ƒ } W n tjy# } ztdt|ƒdœƒdfW  Y d }~S d }~ww z5zt| ƒ tdddœƒW W |  ¡  S  tjyY } ztdt|ƒdœƒdfW  Y d }~W |  ¡  S d }~ww |  ¡  w )NF©rQ  r’  r­  Tr   )rQ  Úservice)rA   r=   ÚErrorr   r`   rv   r÷  )ro   Úexcr<   r<   r@   Úhealth˜	  s    
"€ÿ
ý"
€ý
rf  z/api/settingsÚGET)Úmethodsc                  C   s:   t ƒ } zt| ƒ tdtt| ƒƒdœƒW |  ¡  S |  ¡  w )NT©rQ  r  )rA   rv   r   rU   rÂ   r÷  ©ro   r<   r<   r@   Úget_settings§	  ó
   rk  ÚPUTc                  C   sê   t jddd�pi } tƒ }zct|ƒ | ¡ �"}| d|  d¡|  d¡|  d¡|  d¡|  d¡f¡ W d   ƒ n1 s:w   Y  | ¡  | ¡ �}| d	¡ W d   ƒ n1 sWw   Y  | ¡  tdt	t
|ƒƒd
œƒW | ¡  S | ¡  w )NTra  a„  
                UPDATE GardenSettings SET
                    zip_code = COALESCE(%s, zip_code),
                    lat = COALESCE(%s, lat),
                    lng = COALESCE(%s, lng),
                    notifications_enabled = COALESCE(%s, notifications_enabled),
                    low_seed_threshold = COALESCE(%s, low_seed_threshold)
                WHERE id = 1
                r  r  r  r?  r  zPDELETE FROM GardenExternalCache WHERE cache_key IN ('zone_info', 'zone_info:v2')ri  )r	   rd  rA   rv   re   rf   rj   rm   r   rU   rÂ   r÷  )rf  ro   rp   r<   r<   r@   Úupdate_settings±	  s0   

ûöÿ
ÿÿrn  z/api/settings/backup/fullc               
   C   s¤   t ƒ } zIz&t| ƒ t| ƒ}dt ¡  d¡› d�}t|ddd|› d�id�W W |  ¡  S  tyL } zt	d	t
|ƒd
œƒdfW  Y d }~W |  ¡  S d }~ww |  ¡  w )NÚgarden_tracker_backup_z%Y%m%d_%H%M%Sz.sqlzapplication/sqlzContent-Dispositionzattachment; filename="r“  ©ÚmimetyperÜ   Frb  r­  )rA   rv   rØ   r   rÎ   Ústrftimer   r÷  rø  r   r`   )ro   Údump_sqlÚfilenamere  r<   r<   r@   Úsettings_backup_fullÕ	  s"   ý
ý"
€ý
ru  z/api/seed-companiesc                  C   s>   t ƒ } zt| ƒ tt| ƒƒ}td|dœƒW |  ¡  S |  ¡  w )NT)rQ  rÑ  )rA   rv   r[   r
  r   r÷  )ro   rÑ  r<   r<   r@   Úlist_seed_companiesç	  s   rv  rO  c                  C   s(  t jddd�pi } |  d¡pd ¡ }|  d¡pd ¡  d¡}|r"|s+tddd	œƒd
fS | d¡s4d| }tƒ }zWt|ƒ | 	¡ �3}| 
d|||  d¡|  dd¡rRdndt|  d¡pZdƒf¡ |j}| 
d|f¡ | ¡ }W d   ƒ n1 svw   Y  | ¡  tdt|ƒdœƒdfW | ¡  S | ¡  w )NTra  rs   r]   rt   r1  Fzname and base_url requiredrb  é�  r  r,  zº
                INSERT INTO GardenSeedCompany
                    (name, base_url, search_url_template, enabled, sort_order)
                VALUES (%s, %s, %s, %s, %s)
                r‘  Úenabledr:   r   Ú
sort_orderú-SELECT * FROM GardenSeedCompany WHERE id = %s©rQ  r  éÉ   )r	   rd  rj   rŠ   rR  r   r.  rA   rv   re   rf   rh   Ú	lastrowidri   rm   rU   r÷  )rf  rs   rt   ro   rp   Ú
company_idr  r<   r<   r@   Úcreate_seed_companyò	  s6   

ûú
ïr  z$/api/seed-companies/<int:company_id>c                 C   s6  t jddd�pi }tƒ }z‰t|ƒ | ¡ �g}| d| f¡ | ¡ s6tdddœƒdfW  d   ƒ W | ¡  S | 	d¡}t
|tƒrR| ¡  d	¡}|rR| d
¡sRd| }| d| 	d¡|| 	d¡| 	d¡| 	d¡| f¡ | d| f¡ | ¡ }W d   ƒ n1 sw   Y  | ¡  tdt|ƒdœƒW | ¡  S | ¡  w )NTra  z.SELECT id FROM GardenSeedCompany WHERE id = %sFú	Not foundrb  é”  rt   r1  r  r,  a~  
                UPDATE GardenSeedCompany SET
                    name = COALESCE(%s, name),
                    base_url = COALESCE(%s, base_url),
                    search_url_template = COALESCE(%s, search_url_template),
                    enabled = COALESCE(%s, enabled),
                    sort_order = COALESCE(%s, sort_order)
                WHERE id = %s
                rs   r‘  rx  ry  rz  r{  )r	   rd  rA   rv   re   rf   ri   r   r÷  rj   rF   r`   rŠ   rR  r.  rm   rU   )r~  rf  ro   rp   rt   r  r<   r<   r@   Úupdate_seed_company
  s>   
ý
!
ã

úö
ãr‚  ÚDELETEc                 C   ó¢   t ƒ }zHt|ƒ | ¡ �)}| d| f¡ |jdkr.tdddœƒdfW  d   ƒ W | ¡  S W d   ƒ n1 s8w   Y  | ¡  tddiƒW | ¡  S | ¡  w )	Nz+DELETE FROM GardenSeedCompany WHERE id = %sr   Fr€  rb  r�  rQ  T©rA   rv   re   rf   Úrowcountr   r÷  rm   )r~  ro   rp   r<   r<   r@   Údelete_seed_company@
  ó   

ý
ûþr‡  z/api/plantsc                  C   s˜   t j d¡} tƒ }z=t|ƒ | ¡ �}| r| td | f¡ n| td ¡ | ¡ }W d   ƒ n1 s4w   Y  t	dt
|ƒdœƒW | ¡  S | ¡  w )NÚstatusz$ WHERE p.status = %s ORDER BY p.namez ORDER BY p.nameT)rQ  Úplants)r	   re  rj   rA   rv   re   rf   Ú_PLANT_SELECTr€   r   rŸ   r÷  )r‰  ro   rp   rZ   r<   r<   r@   Úlist_plantsR
  s   
þ
ø	rŒ  z/api/plants/by-label/<label_id>c                 C   sÞ   t ƒ }zft|ƒ | ¡ �D}| td |  ¡  ¡ f¡ | ¡ }|s5tdddœƒdfW  d   ƒ W | 	¡  S |d }| d|f¡ t
|||| ¡ ƒ}W d   ƒ n1 sSw   Y  tdt||dd	�|d
œƒW | 	¡  S | 	¡  w )Nz WHERE p.label_id = %sFr€  rb  r�  r   úTSELECT * FROM GardenPlantEvent WHERE plant_id = %s ORDER BY event_date DESC, id DESCTrž   )rQ  r©   r¶   )rA   rv   re   rf   r‹  rŠ   Úupperri   r   r÷  r·   r€   r™   )Úlabel_idro   rp   r©   r¨   r¶   r<   r<   r@   Úget_plant_by_labelf
  s&   
ü
øþör�  z/api/plants/<int:plant_id>c                 C   sî   t ƒ }znt|ƒ | ¡ �I}| td | f¡ | ¡ }|s1tdddœƒdfW  d   ƒ W | ¡  S | d| f¡ | ¡ }| d| f¡ | ¡ }t	|| ||ƒ}W d   ƒ n1 sXw   Y  tdt
||dd	�|t|ƒd
œƒW | ¡  S | ¡  w )Nú WHERE p.id = %sFr€  rb  r�  r�  zBSELECT * FROM GardenSchedule WHERE plant_id = %s ORDER BY next_dueTrž   )rQ  r©   r¶   Ú	schedules)rA   rv   re   rf   r‹  ri   r   r÷  r€   r·   r™   r[   )r¨   ro   rp   r©   r¶   r’  r<   r<   r@   Ú	get_plant{
  s<   
ü
ìþþñüÿ	r“  c                  C   s�  t jddd�pi } |  d¡pd ¡ }|tvrtdddœƒdfS |  d	¡p%d
}|tvr3tdddœƒdfS zt|  d¡p;dƒ}W n tt	fyR   tdddœƒdf Y S w |dk s[|dkrdtdddœƒdfS |  d¡}|  d¡pod ¡ }|  d¡pxd ¡ p|d }t
|  d¡ƒp‡t ¡ }tƒ }�z6t|ƒ | ¡ ��}	d }
|dkrê|s´tdddœƒdfW  d   ƒ W | ¡  S |	 d|f¡ |	 ¡ }
|
sÖtdddœƒdfW  d   ƒ W | ¡  S |sÜ|
d }|d u ré|
 d¡ré|
d }n|�stdddœƒdfW  d   ƒ W | ¡  S |dk�r	dnd}d }|dk�rt|
ƒ}n|  d¡�r"|  d¡}g }t|	|ƒ}|D ]7}|	 d ||||  d!¡|||  d"¡|  d¡|dk�rH|nd |f
¡ |	j}| |¡ |	 d#||||d$d%f¡ �q+|dk�rw|  d&d¡�rw|	 d'||f¡ g }|D ]}|	 td( |f¡ |	 ¡ }|�r”| t|ƒ¡ �q{W d   ƒ n	1 �s¡w   Y  | ¡  tdt|ƒ|�r¶|d$ nd |d)œƒd*fW | ¡  S | ¡  w )+NTra  r’   r/   Fzinvalid source_typerb  rw  r‰  r+   úinvalid statusÚquantityr:   zinvalid quantityr­  z"quantity must be between 1 and 500r“   rs   r]   rˆ   r¢   r.   z(seed_id required for seed-sourced plantsú/SELECT * FROM GardenSeedInventory WHERE id = %szSeed not foundr�  r�   z%name required for store-bought plantsr6   r8   r„  a  
                    INSERT INTO GardenPlant (label_id, name, variety, location, status, planted_date,
                        moisture_device_id, notes, seed_id, source_type)
                    VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                    r¿   Úmoisture_device_idz÷
                    INSERT INTO GardenPlantEvent (
                        plant_id, event_type, event_date, notes, days_since_start, days_since_anchor
                    )
                    VALUES (%s, %s, %s, %s, %s, %s)
                    r   r¤   Údecrement_seedzHUPDATE GardenSeedInventory SET qty = GREATEST(0, qty - %s) WHERE id = %sr‘  )rQ  r|   r©   rŠ  r|  )r	   rd  rj   rŠ   ÚPLANT_SOURCE_TYPESr   ÚPLANT_STATUSESrh   r¼   ra   rb   r   rG  rA   rv   re   r÷  rf   ri   r–   r~   r}  r›   r‹  r™   rm   r�   )rf  r’   r‰  r•  r“   rs   rˆ   r¢   ro   rp   r.   r¾   Úevent_notesÚcreated_idsÚ	label_idsr�  r¨   rŠ  rT   r<   r<   r@   Úcreate_plantœ
  sÂ   ÿ
ü
O¶ø
Oº€ò
OÁ



öú
ù
þ€üÀEüÿù	rž  c                 C   sv  t jddd�pi }tƒ }z©t|ƒ | ¡ �‡}| d| f¡ | ¡ s6tdddœƒdfW  d   ƒ W | ¡  S | 	d¡}|d urX|t
vrXtdd	dœƒd
fW  d   ƒ W | ¡  S | d| 	d¡| 	d¡| 	d¡|d|v rst| 	d¡ƒnd | 	d¡| 	d¡| 	d¡| 	d¡| f
¡ | td | f¡ | ¡ }W d   ƒ n1 sŸw   Y  | ¡  tdt|ƒdœƒW | ¡  S | ¡  w )NTra  z(SELECT id FROM GardenPlant WHERE id = %sFr€  rb  r�  r‰  r”  rw  aP  
                UPDATE GardenPlant SET
                    name = COALESCE(%s, name),
                    variety = COALESCE(%s, variety),
                    location = COALESCE(%s, location),
                    status = COALESCE(%s, status),
                    planted_date = COALESCE(%s, planted_date),
                    moisture_device_id = COALESCE(%s, moisture_device_id),
                    notes = COALESCE(%s, notes),
                    seed_id = COALESCE(%s, seed_id),
                    source_type = COALESCE(%s, source_type)
                WHERE id = %s
                rs   rˆ   r¿   r¢   r—  r„  r“   r’   r‘  ©rQ  r©   )r	   rd  rA   rv   re   rf   ri   r   r÷  rj   rš  rb   r‹  rm   r™   )r¨   rf  ro   rp   r‰  r©   r<   r<   r@   Úupdate_plant  sF   
ý
'
Ýú
'àöò
Ý$r   z$/api/plants/<int:plant_id>/duplicatec                 C   sÎ  t ƒ }zÞt|ƒ | ¡ �º}| td | f¡ | ¡ }|s1tdddœƒdfW  d   ƒ W | ¡  S t|dƒd }| 	d¡p>d	}|d
krEdnd}d }|d
kre| 	d¡re| d|d f¡ | ¡ }|ret
|ƒ}| 	d¡pmt ¡ }	t|	tƒrw|	 ¡ }	| d||d | 	d¡| 	d¡| 	d¡pŒd|	| 	d¡| 	d¡|d
krž| 	d¡nd |f
¡ |j}
| d|
||	|ddf¡ | td |
f¡ | ¡ }W d   ƒ n1 sÉw   Y  | ¡  tdt|ƒdœƒdfW | ¡  S | ¡  w )Nr‘  Fr€  rb  r�  r:   r   r’   r/   r.   r6   r8   r“   r–  r¢   zø
                INSERT INTO GardenPlant (label_id, name, variety, location, status, planted_date,
                    moisture_device_id, notes, seed_id, source_type)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                rs   rˆ   r¿   r‰  r+   r—  r„  zã
                INSERT INTO GardenPlantEvent (
                    plant_id, event_type, event_date, notes, days_since_start, days_since_anchor
                )
                VALUES (%s, %s, %s, %s, %s, %s)
                r¤   TrŸ  r|  )rA   rv   re   rf   r‹  ri   r   r÷  r~   rj   r–   r   rG  rF   r   r}  rm   r™   )r¨   ro   rp   r©   r�  r’   r¾   r›  r.   r¢   Únew_idÚ	new_plantr<   r<   r@   Úduplicate_plant4  s^   
ü
6Ð
öúù	
Î3r£  c                 C   r„  )	Nz%DELETE FROM GardenPlant WHERE id = %sr   Fr€  rb  r�  rQ  Tr…  )r¨   ro   rp   r<   r<   r@   Údelete_plantr  rˆ  r¤  z!/api/plants/<int:plant_id>/eventsc                 C   sÆ   t ƒ }zZt|ƒ | ¡ �>}| td | f¡ | ¡ }|s1tdddœƒdfW  d   ƒ W | ¡  S | d| f¡ | ¡ }t	|| ||ƒ}W d   ƒ n1 sMw   Y  td|dœƒW | ¡  S | ¡  w )	Nr‘  FúPlant not foundrb  r�  r�  T)rQ  r¶   )
rA   rv   re   rf   r‹  ri   r   r÷  r€   r·   )r¨   ro   rp   r©   rZ   r<   r<   r@   Úlist_plant_events�  s&   
ü
øþör¦  c                 C   s–  t jddd�pi }| d¡pd}|tvrtdddœƒdfS t| d	¡ƒp't ¡ }tƒ }zšt	|ƒ | 
¡ �x}| td
 | f¡ | ¡ }|sYtdddœƒdfW  d   ƒ W | ¡  S t| d¡ƒ}t| d¡ƒ}t|| ||ƒ\}	}
| d| ||| d¡| d¡| d¡|||	|
f
¡ t|| ||ƒ |j}| d|f¡ | ¡ }t|| ||ƒ}W d   ƒ n1 s¯w   Y  | ¡  td|dœƒdfW | ¡  S | ¡  w )NTra  r¾   r0   Fúinvalid event_typerb  rw  r£   r‘  r¥  r�  r¿   Úheighta$  
                INSERT INTO GardenPlantEvent (
                    plant_id, event_type, event_date, notes, qty, unit, location, height,
                    days_since_start, days_since_anchor
                )
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                r„  r  rB  ú,SELECT * FROM GardenPlantEvent WHERE id = %s©rQ  r±   r|  )r	   rd  rj   ÚEVENT_TYPESr   rb   r   rG  rA   rv   re   rf   r‹  ri   r÷  rº   r½   r®   rÀ   r}  r³   rm   )r¨   rf  r¾   r£   ro   rp   r©   r¿   r¨  Ú
days_sincer¬   Úevent_idr±   r<   r<   r@   Úcreate_plant_event–  sR   
ü
%àöøß"r®  z0/api/plants/<int:plant_id>/events/<int:event_id>c                 C   s*  t jddd�pi }tƒ }�z‚t|ƒ | ¡ ��`}| td | f¡ | ¡ }|s<tdddœƒdfW  d   ƒ W | 	¡  S | d|| f¡ | ¡ }|s_tdd	dœƒdfW  d   ƒ W | 	¡  S | 
d
|d
 ¡}|tvr€tdddœƒdfW  d   ƒ W | 	¡  S d|v r£t| 
d¡ƒ}|s¢tdddœƒdfW  d   ƒ W | 	¡  S nt| 
d¡ƒ}d|v r²|d n| 
d¡}	d|v r¿|d n| 
d¡}
d|v rÌ|d n| 
d¡}d|v �r| 
d¡d u�rztdt|d ƒƒ}W n! ttf�y
   tdddœƒdf Y W  d   ƒ W | 	¡  S w |�pi  
d¡dk�rdnd}n	t|| ||ƒ\}}td|v �r.|d n| 
d¡ƒ}d|v �rAt| 
d¡ƒ}n| 
d¡}| d|||	|
||||||| f¡ t|| ||ƒ | d|f¡ | ¡ }t|| ||ƒ}W d   ƒ n	1 �s{w   Y  | ¡  td|dœƒW | 	¡  S | 	¡  w )NTra  r‘  Fr¥  rb  r�  z>SELECT * FROM GardenPlantEvent WHERE id = %s AND plant_id = %sr€  r¾   r§  rw  r£   zinvalid event_dater„  r  rB  r¯   r   zinvalid days_since_startr’   r.   r6   r¤   r¿   r¨  a®  
                UPDATE GardenPlantEvent SET
                    event_type = %s,
                    event_date = %s,
                    notes = %s,
                    qty = %s,
                    unit = %s,
                    location = %s,
                    height = %s,
                    days_since_start = %s,
                    days_since_anchor = %s
                WHERE id = %s AND plant_id = %s
                r©  rª  )r	   rd  rA   rv   re   rf   r‹  ri   r   r÷  rj   r«  rb   r¡   r§   rh   r¼   ra   r®   rº   r½   rÀ   r³   rm   )r¨   r­  rf  ro   rp   r©   r±   r¾   r£   r„  r  rB  r¬  r¬   r¿   r¨  r<   r<   r@   Úupdate_plant_eventÈ  s–   ü
L¹þõ
LÁñ
LÄí
LÆâ
LÑ ÿ

õò¸Ir¯  c                 C   s¤   t ƒ }zIt|ƒ | ¡ �*}| d|| f¡ |jdkr/tdddœƒdfW  d   ƒ W | ¡  S W d   ƒ n1 s9w   Y  | ¡  tddiƒW | ¡  S | ¡  w )	Nz<DELETE FROM GardenPlantEvent WHERE id = %s AND plant_id = %sr   Fr€  rb  r�  rQ  Tr…  )r¨   r­  ro   rp   r<   r<   r@   Údelete_plant_event  s"   
þ
ú

ûûr°  z /api/seed-type-upload/<path:rel>c                 C   s`   | r	d|   d¡v rtdddœƒdfS tƒ }tj || ¡}tj |¡s+tdddœƒdfS t|| ƒS )	NrÃ  r1  Fzinvalid pathrb  rw  z	not foundr�  )r7  r   rM  rG  r6  rœ   rÄ  r
   )ÚrelrÈ  rÉ  r<   r<   r@   Úserve_seed_type_upload2  s   
r²  z/api/seed-imagec               
   C   s„  t j d¡pd ¡ } | stdddœƒdfS t| ƒ}tj |¡}|j	p#d 
¡ }tƒ }zt|ƒ t|ƒ}W | ¡  n| ¡  w t||ƒsKtdddœƒdfS |jdkrYtdd	dœƒdfS d
}|tvred|› d�}z8tj j|d|dœd�}tj j|dd��}| ¡ }	|j dd¡}
W d   ƒ n1 s�w   Y  t|	|
ddid�W S  tjjtjjtfyÁ } ztdt|ƒdœƒdfW  Y d }~S d }~ww )Nræ   r]   Fzurl requiredrb  rw  zhost not allowedr  zinvalid urlzhttps://en.wikipedia.org/r,  r1  zMozilla/5.0 GardenTracker/1.0)rÚ   rN  rÛ   rÙ   rÝ   rL  z
image/jpegzCache-Controlzpublic, max-age=86400rp  éö  )r	   re  rj   rŠ   r   r0  rß   r4  r5  r  r8  rA   rv   r  r÷  r  r  r  rà   rá   rä   rÜ   r   r’  r“  r”  r•  r`   )Úraw_urlræ   r  r   ro   r  Úrefererrç   rè   rf  Úcontent_typere  r<   r<   r@   Úseed_image_proxy=  sL   


þþþý"€ÿr·  z
/api/seedsc            	      C   sÐ   t ƒ } z_t| ƒ |  ¡ �}| d¡ | ¡ }W d   ƒ n1 s w   Y  t| ƒp*i }t| d¡p2dƒ}g }|D ]}t|ƒ}| d¡pD|}t	|d ƒt	|ƒk|d< | 
|¡ q8td|dœƒW |  ¡  S |  ¡  w )	Nz=SELECT * FROM GardenSeedInventory ORDER BY crop_name, varietyr  r�  r  r  r  T)rQ  Úseeds)rA   rv   re   rf   r€   rÂ   rh   rj   rU   rH   r›   r   r÷  )	ro   rp   rZ   r  r	  r˜   rT   rý  rD  r<   r<   r@   Ú
list_seedsf  s"   


þr¹  c                  C   s4  t jddd�pi } |  d¡pd ¡ }|stdddœƒdfS tƒ }ztt|ƒ | ¡ �H}| d	||  d
¡|  dd¡|  d¡p=d|  d¡t	|  d¡ƒt	|  d¡ƒ|  d¡|  d¡|  d¡f
¡ |j
}| d|f¡ | ¡ }W d   ƒ n1 stw   Y  | ¡  t||  d
¡ƒ tdt|ƒdœƒdfW | ¡  S | ¡  w )NTra  r�   r]   Fzcrop_name requiredrb  rw  a  
                INSERT INTO GardenSeedInventory
                    (crop_name, variety, qty, unit, source, purchase_date, expiry_date,
                     location, low_stock_threshold, notes)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                rˆ   r  r   rB  r¸  r>  Úpurchase_dateÚexpiry_dater¿   r  r„  r–  ©rQ  r.   r|  )r	   rd  rj   rŠ   r   rA   rv   re   rf   rb   r}  ri   rm   r  rU   r÷  )rf  r�   ro   rp   r“   r.   r<   r<   r@   Úcreate_seed{  s<   

öù
ér½  z/api/seeds/<int:seed_id>c                 C   sJ  t ƒ }zœt|ƒ | ¡ �}| d| f¡ | ¡ }W d   ƒ n1 s"w   Y  |s7tdddœƒdfW | ¡  S t||ƒ}t||d | 	d¡ƒ}t
|d | 	d¡ƒ}d}|sht|ƒ}|sgt|d | 	d¡ƒ d}n*| 	d	¡s’g s’t||ƒs’|tvrŽt |¡ t|ƒs�t|d | 	d¡ƒ d}nt|ƒ}td|||d
œƒW | ¡  S | ¡  w )Nr–  Fr€  rb  r�  r�   rˆ   Tr�   )rQ  r.   r�   Útype_info_loading)rA   rv   re   rf   ri   r   r÷  r
  rŽ   rj   r|  ró  r  rs  Ú_seed_type_image_retry_donerA  )r“   ro   rp   rT   r.   r�   rQ  r¾  r<   r<   r@   Úget_seed£  sJ   

þ

å€

€üÿ	rÀ  z$/api/seeds/<int:seed_id>/product-urlc                 C   sp  t jdd�pi }| d¡pd ¡ }|stdddœƒdfS | d	¡s*| d
¡s*d
| }| d¡d  d¡}tƒ }zût	|ƒ | 
¡ �}| d| f¡ | ¡ }W d   ƒ n1 sVw   Y  |sktdddœƒdfW | ¡  S t||ƒs~tdddœƒdfW | ¡  S t|ƒ}|s�tdddœƒW | ¡  S |d }| d¡}	t|||	| d¡| d¡d�}
|
tk}| d¡| d¡|t||ƒt| d¡pÀg ƒdœ}t|||	ƒ}t|ƒ}t| d¡ƒ}t| d¡ƒ}|�s|sýtdd|› �|	rîd|	› �nd |
||ddœƒW | ¡  S tdd|
||dœƒW | ¡  S |�s |�s tdd dœƒdfW | ¡  S t|||	|ƒ}td|d!œƒW | ¡  S | ¡  w )"NT)rc  ræ   r]   FzProduct URL is requiredrb  rw  r+  r,  r¥  r   r1  r–  r€  r�  z<URL must be a product page on a configured seed company sitez*Could not load product info from that pager�   rˆ   r=  r©  rÏ  r�   )r=  r©  r±  rÌ  Úimage_countÚapplyrb  z#This page does not appear to match u    â€” )rQ  r’  Úmatch_scoreÚpreviewÚhas_existingÚ	can_apply)rQ  ÚmatchesrÃ  rÄ  rÅ  zPage match not confirmed©rQ  r�   )r	   rd  rj   rŠ   r   r.  r7  rR  rA   rv   re   rf   ri   r÷  r  r¸  r�  ÚPRODUCT_URL_MIN_MATCH_SCOREr  r�   rŽ   rè  rq  rë  )r“   rf  ræ   Ú	clean_urlro   rp   rT   ré  r�   rˆ   rÃ  rÇ  rÄ  r»  rÅ  rÂ  rb  r�   r<   r<   r@   Úset_seed_product_urlË  s¬   

þ
B
¿þý
@Åþ
9Ì
ûûÿÿ÷
ïû
÷þý
ýrË  z*/api/seeds/<int:seed_id>/refresh-type-infoc                 C   s  t ƒ }z~t|ƒ | ¡ �}| d| f¡ | ¡ }W d   ƒ n1 s"w   Y  |s7tdddœƒdfW | ¡  S t|d | d¡ƒ}t	||ƒ}| ¡ �}| d|f¡ W d   ƒ n1 s\w   Y  | 
¡  t |¡ t|d | d¡|d	� td
d
dœƒW | ¡  S | ¡  w )Nr–  Fr€  rb  r�  r�   rˆ   z3DELETE FROM GardenSeedTypeInfo WHERE cache_key = %srô  T)rQ  r¾  )rA   rv   re   rf   ri   r   r÷  r|  rj   rÀ  rm   r¿  rº  r  )r“   ro   rp   rT   rQ  rì  r<   r<   r@   Úrefresh_seed_type_info!  s0   

þ
ó

ÿ
ýrÌ  z$/api/seeds/<int:seed_id>/type-imagesc                 C   sÜ  t j d¡}|stdddœƒdfS tƒ }zÔt|ƒ | ¡ �}| d| f¡ | ¡ }W d   ƒ n1 s3w   Y  |sHtdddœƒdfW | 	¡  S t
|d	 | d
¡ƒ}t||ƒ}t|ƒtkrotddt› d�dœƒdfW | 	¡  S t|jptdƒpwd}tj |¡d  ¡ }|dvrˆd}t d¡ ¡ › |› �}	t|ƒ› d|	› �}
tj tƒ |
¡}tjtj |¡dd� | |¡ | d|
› �|
|ddœ¡ t|||d	 | d
¡|dd� t||d	 | d
¡ƒ}td|dœƒdfW | 	¡  S | 	¡  w )Nr°  FzNo file uploadedrb  rw  r–  r€  r�  r�   rˆ   zMaximum z imagesz	image.jpgr:   )rã  rä  rå  ræ  z.gifrã  é   r1  T)Úexist_okrj  Úuploaded)ræ   rW  r=  r>  ©rÁ  rÈ  r|  )r	   Úfilesrj   r   rA   rv   re   rf   ri   r÷  r|  rÀ  r�   rv  r   rt  rG  r6  Úsplitextr8  ÚurandomÚhexrS  rœ   rM  ÚmakedirsrH  Úsaver›   rÂ  rŽ   )r“   r°  ro   rp   rT   rQ  r�   Ú	safe_namerß  rt  r±  rÉ  r�   r<   r<   r@   Úupload_seed_type_image;  sN   

þ
å

ê
üÿrØ  c                 C   sÆ  t ƒ }tƒ }|dkr|d u rtdddœƒdfS tƒ }zÃt|ƒ | ¡ �}| d| f¡ | ¡ }W d   ƒ n1 s9w   Y  |sNtdddœƒdfW | ¡  S t	|d	 | 
d
¡ƒ}t||ƒ}d }t|ƒ}	|d ur…|dk sq|t|ƒkrtdddœƒdfW | ¡  S |	 |¡}ng }	|D ]}
t|
|ƒr—|d u r—|
}q‰|	 |
¡ q‰|s­tdddœƒdfW | ¡  S | 
d¡r¸t|d ƒ t|||d	 | 
d
¡|	dd� t||d	 | 
d
¡ƒ}td|dœƒW | ¡  S | ¡  w )Nr]   Fúurl or index requiredrb  rw  r–  r€  r�  r�   rˆ   r   úImage not foundrW  TrÐ  rÈ  )rg  ri  r   rA   rv   re   rf   ri   r÷  r|  rj   rÀ  rÏ   r�   rö  rn  r›   rÊ  rÂ  rŽ   )r“   ræ   rh  ro   rp   rT   rQ  r�   ÚremovedÚkeptrC  r�   r<   r<   r@   Údelete_seed_type_imageg  sP   

þ
æ

í


øÿrÝ  z,/api/seeds/<int:seed_id>/type-images/primaryÚPATCHc           
         sø  t ƒ ‰ tƒ }ˆ dkr|d u rtdddœƒdfS tƒ }zÜt|ƒ | ¡ �}| d| f¡ | ¡ }W d   ƒ n1 s9w   Y  |sNtdddœƒdfW | ¡  S t	|d	 | 
d
¡ƒ}t||ƒ}|d ur||dk sk|t|ƒkrytdddœƒdfW | ¡  S |}n t‡ fdd„t|ƒD ƒd ƒ}|d u rœtdddœƒdfW | ¡  S |dkrÆt|||d	 | 
d
¡|dd� t||d	 | 
d
¡ƒ}td|dœƒW | ¡  S | |¡}	| d|	¡ t|||d	 | 
d
¡|dd� t||d	 | 
d
¡ƒ}td|dœƒW | ¡  S | ¡  w )Nr]   FrÙ  rb  rw  r–  r€  r�  r�   rˆ   r   rÚ  c                 3   s"   � | ]\}}t |ˆ ƒr|V  qd S r    )rn  )rN   ry   rC  rC  r<   r@   rá  ª  s   €  z/make_seed_type_image_primary.<locals>.<genexpr>TrÐ  rÈ  )rg  ri  r   rA   rv   re   rf   ri   r÷  r|  rj   rÀ  r�   Únextrk   rÂ  rŽ   rö  Úinsert)
r“   rh  ro   rp   rT   rQ  r�   rr   r�   Úchosenr<   rC  r@   Úmake_seed_type_image_primary“  sV   

þ
ä

éþ
ñÿ


øÿrâ  c                 C   sL  t jddd�pi }tƒ }z”t|ƒ | ¡ �r}| d| f¡ | ¡ s6tdddœƒdfW  d   ƒ W | ¡  S | d| 	d	¡| 	d
¡| 	d¡| 	d¡| 	d¡d|v rXt
| 	d¡ƒnd d|v rdt
| 	d¡ƒnd | 	d¡| 	d¡| 	d¡| f¡ | d| f¡ | ¡ }W d   ƒ n1 sŠw   Y  | ¡  tdt|ƒdœƒW | ¡  S | ¡  w )NTra  z0SELECT id FROM GardenSeedInventory WHERE id = %sFr€  rb  r�  a�  
                UPDATE GardenSeedInventory SET
                    crop_name = COALESCE(%s, crop_name),
                    variety = COALESCE(%s, variety),
                    qty = COALESCE(%s, qty),
                    unit = COALESCE(%s, unit),
                    source = COALESCE(%s, source),
                    purchase_date = COALESCE(%s, purchase_date),
                    expiry_date = COALESCE(%s, expiry_date),
                    location = COALESCE(%s, location),
                    low_stock_threshold = COALESCE(%s, low_stock_threshold),
                    notes = COALESCE(%s, notes)
                WHERE id = %s
                r�   rˆ   r  rB  r>  rº  r»  r¿   r  r„  r–  r¼  )r	   rd  rA   rv   re   rf   ri   r   r÷  rj   rb   rm   rU   )r“   rf  ro   rp   r.   r<   r<   r@   Úupdate_seedÁ  s>   
ý
&Þõñ
Þ#rã  c                 C   r„  )	Nz-DELETE FROM GardenSeedInventory WHERE id = %sr   Fr€  rb  r�  rQ  Tr…  )r“   ro   rp   r<   r<   r@   Údelete_seedð  rˆ  rä  z#/api/seeds/<int:seed_id>/adjust-qtyc                 C   s   t jddd�pi }| d¡}|d u rtdddœƒdfS tƒ }zlt|ƒ | ¡ �J}| d| f¡ | ¡ }|sJtdd	dœƒd
fW  d   ƒ W | 	¡  S t
dt|d ƒt|ƒ ƒ}| d|| f¡ | d| f¡ | ¡ }W d   ƒ n1 stw   Y  | ¡  tdt|ƒdœƒW | 	¡  S | 	¡  w )NTra  ÚdeltaFzdelta requiredrb  rw  r–  r€  r�  r   r  z5UPDATE GardenSeedInventory SET qty = %s WHERE id = %sr¼  )r	   rd  rj   r   rA   rv   re   rf   ri   r÷  r§   rH   rm   rU   )r“   rf  rå  ro   rp   r.   Únew_qtyr<   r<   r@   Úadjust_seed_qtyÿ  s2   

ü
öþ
õrç  z
/api/tanksc            
      C   s  t ƒ } z�t| ƒ |  ¡ �e}| d¡ | ¡ }g }t ¡ }|D ]K}t|ƒ}| d|d f¡ | ¡ }|r7t|ƒnd |d< t	| 
d¡pBdƒ}|r\|d t|d� }	|	|k|d	< t|	ƒ|d
< nd|d	< d |d
< | |¡ qW d   ƒ n1 stw   Y  td|dœƒW |  ¡  S |  ¡  w )Nú&SELECT * FROM GardenTank ORDER BY namez’
                    SELECT * FROM GardenTankReading WHERE tank_id = %s
                    ORDER BY recorded_at DESC LIMIT 1
                    r   Úlatest_readingrD  r-  rE  r4  Útest_overdueÚnext_test_dueT)rQ  rS  )rA   rv   re   rf   r€   r   rì   rU   ri   rh   rj   r   rM   r›   r   r÷  )
ro   rp   rS  r˜   rÎ   rT  rý  Úlatestr6  rW  r<   r<   r@   Ú
list_tanks  s8   

ûíûrí  c                  C   s  t jddd�pi } |  d¡pd ¡ }|stdddœƒdfS tƒ }zbt|ƒ | ¡ �>}| d	||  d
¡|  d¡|  d¡|  d¡|  d¡|  d¡|  dd¡|  d¡f	¡ |j	}| d|f¡ | 
¡ }W d   ƒ n1 sjw   Y  | ¡  tdt|ƒdœƒdfW | ¡  S | ¡  w )NTra  rs   r]   Fzname requiredrb  rw  a  
                INSERT INTO GardenTank
                    (name, volume_gal, solution_type, target_ph_min, target_ph_max,
                     target_ec_min, target_ec_max, test_interval_days, notes)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
                Ú
volume_galÚsolution_typeÚtarget_ph_minÚtarget_ph_maxÚtarget_ec_minÚtarget_ec_maxrD  r-  r„  ú&SELECT * FROM GardenTank WHERE id = %s©rQ  rT  r|  )r	   rd  rj   rŠ   r   rA   rv   re   rf   r}  ri   rm   rU   r÷  )rf  rs   ro   rp   Útank_idrT  r<   r<   r@   Úcreate_tank@  s8   

÷ù
êr÷  z/api/tanks/<int:tank_id>c                 C   s$  t jddd�pi }tƒ }z€t|ƒ | ¡ �^}| d| f¡ | ¡ s6tdddœƒdfW  d   ƒ W | ¡  S | d| 	d	¡| 	d
¡| 	d¡| 	d¡| 	d¡| 	d¡| 	d¡| 	d¡| 	d¡| f
¡ | d| f¡ | ¡ }W d   ƒ n1 svw   Y  | 
¡  tdt|ƒdœƒW | ¡  S | ¡  w )NTra  ú'SELECT id FROM GardenTank WHERE id = %sFr€  rb  r�  a  
                UPDATE GardenTank SET
                    name = COALESCE(%s, name),
                    volume_gal = COALESCE(%s, volume_gal),
                    solution_type = COALESCE(%s, solution_type),
                    target_ph_min = COALESCE(%s, target_ph_min),
                    target_ph_max = COALESCE(%s, target_ph_max),
                    target_ec_min = COALESCE(%s, target_ec_min),
                    target_ec_max = COALESCE(%s, target_ec_max),
                    test_interval_days = COALESCE(%s, test_interval_days),
                    notes = COALESCE(%s, notes)
                WHERE id = %s
                rs   rî  rï  rð  rñ  rò  ró  rD  r„  rô  rõ  )r	   rd  rA   rv   re   rf   ri   r   r÷  rj   rm   rU   )rö  rf  ro   rp   rT  r<   r<   r@   Úupdate_tankf  s<   
ý
$àöò
à!rù  c                 C   r„  )	Nz$DELETE FROM GardenTank WHERE id = %sr   Fr€  rb  r�  rQ  Tr…  )rö  ro   rp   r<   r<   r@   Údelete_tank“  rˆ  rú  z!/api/tanks/<int:tank_id>/readingsc                 C   st   t ƒ }z1t|ƒ | ¡ �}| d| f¡ | ¡ }W d   ƒ n1 s"w   Y  tdt|ƒdœƒW | ¡  S | ¡  w )Nzˆ
                SELECT * FROM GardenTankReading WHERE tank_id = %s
                ORDER BY recorded_at DESC LIMIT 100
                T)rQ  Úreadings©rA   rv   re   rf   r€   r   r[   r÷  )rö  ro   rp   rZ   r<   r<   r@   Úlist_tank_readings¢  s   
û
ø	rý  c                 C   s&  t jddd�pi }tƒ }z�t|ƒ | ¡ �]}| d| f¡ | ¡ s6tdddœƒdfW  d   ƒ W | ¡  S t	| 
d¡ƒp@t ¡ }| d	| | 
d
¡| 
d¡| 
d¡| 
d¡| 
d¡|f¡ |j}| d|f¡ | ¡ }W d   ƒ n1 suw   Y  | ¡  tdt|ƒdœƒdfW | ¡  S | ¡  w )NTra  rø  FzTank not foundrb  r�  rE  zÁ
                INSERT INTO GardenTankReading
                    (tank_id, ph, ec, temp_f, volume_gal, notes, recorded_at)
                VALUES (%s, %s, %s, %s, %s, %s, %s)
                ÚphÚecr-  rî  r„  z-SELECT * FROM GardenTankReading WHERE id = %s)rQ  Úreadingr|  )r	   rd  rA   rv   re   rf   ri   r   r÷  rc   rj   r   rì   r}  rm   rU   )rö  rf  ro   rp   rE  Ú
reading_idr   r<   r<   r@   Úcreate_tank_readingµ  s:   
ý
éùú
ér  z/api/schedulesc                  C   sp   t ƒ } z/t| ƒ |  ¡ �}| d¡ | ¡ }W d   ƒ n1 s w   Y  tdt|ƒdœƒW |  ¡  S |  ¡  w )Na  
                SELECT s.*, p.name AS plant_name, t.name AS tank_name
                FROM GardenSchedule s
                LEFT JOIN GardenPlant p ON p.id = s.plant_id
                LEFT JOIN GardenTank t ON t.id = s.tank_id
                ORDER BY s.next_due
                T)rQ  r’  rü  )ro   rp   rZ   r<   r<   r@   Úlist_schedulesÜ  s   
ÿ
	ör  c            	      C   sD  t jddd�pi } |  d¡pd}|tvrtdddœƒdfS |  d	¡p)| d
d¡ ¡  ¡ }t|  d¡p3dƒ}t	|  d¡ƒp?t
 ¡ }tƒ }zYt|ƒ | ¡ �5}| d|  d¡|  d¡|||||  dd¡rddnd|  d¡f¡ |j}| d|f¡ | ¡ }W d   ƒ n1 s„w   Y  | ¡  tdt|ƒdœƒdfW | ¡  S | ¡  w )NTra  r8  r&   Fúinvalid task_typerb  rw  r=  r²   r�  r2  r3  Únext_duez×
                INSERT INTO GardenSchedule
                    (plant_id, tank_id, task_type, title, interval_days, next_due, enabled, notes)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
                r¨   rö  rx  r:   r   r„  ú*SELECT * FROM GardenSchedule WHERE id = %s©rQ  r5  r|  )r	   rd  rj   Ú
TASK_TYPESr   rC   r=  rŠ   rh   rc   r   rì   rA   rv   re   rf   r}  ri   rm   rU   r÷  )	rf  r8  r=  r2  r  ro   rp   Úschedule_idr5  r<   r<   r@   Úcreate_scheduleñ  s<   
øú
ìr
  z /api/schedules/<int:schedule_id>c                 C   s†  t jddd�pi }tƒ }z±t|ƒ | ¡ ��}| d| f¡ | ¡ s6tdddœƒdfW  d   ƒ W | ¡  S | 	d¡}|d urX|t
vrXtdd	dœƒd
fW  d   ƒ W | ¡  S | 	d¡}| d| 	d¡| 	d¡|| 	d¡| 	d¡d|v r|t| 	d¡ƒnd |du rƒdn|du r‰dnd | 	d¡| f	¡ | d| f¡ | ¡ }W d   ƒ n1 s§w   Y  | ¡  tdt|ƒdœƒW | ¡  S | ¡  w )NTra  r  Fr€  rb  r�  r8  r  rw  rx  a  
                UPDATE GardenSchedule SET
                    plant_id = COALESCE(%s, plant_id),
                    tank_id = COALESCE(%s, tank_id),
                    task_type = COALESCE(%s, task_type),
                    title = COALESCE(%s, title),
                    interval_days = COALESCE(%s, interval_days),
                    next_due = COALESCE(%s, next_due),
                    enabled = COALESCE(%s, enabled),
                    notes = COALESCE(%s, notes)
                WHERE id = %s
                r¨   rö  r=  r2  r  r:   r   r„  r  )r	   rd  rA   rv   re   rf   ri   r   r÷  rj   r  rc   rm   rU   )r	  rf  ro   rp   r8  rx  r5  r<   r<   r@   Úupdate_schedule  sF   
ý
&
Þú
&
á÷ó
Þ#r  c                 C   r„  )	Nz(DELETE FROM GardenSchedule WHERE id = %sr   Fr€  rb  r�  rQ  Tr…  )r	  ro   rp   r<   r<   r@   Údelete_scheduleG  rˆ  r  z)/api/schedules/<int:schedule_id>/completec              	   C   sL  t jddd�pi }tƒ }z”t|ƒ | ¡ �r}| d| f¡ | ¡ }|s8tdddœƒdfW  d   ƒ W | ¡  S t	 
¡ }| d| || d	¡f¡ t|ƒ}| d
|| f¡ | d¡ru| d¡dkru| d|d t ¡ | d	¡pr| d¡f¡ | d| f¡ | ¡ }W d   ƒ n1 sŠw   Y  | ¡  tdt|ƒdœƒW | ¡  S | ¡  w )NTra  r  Fr€  rb  r�  z‰
                INSERT INTO GardenTaskCompletion (schedule_id, completed_at, notes)
                VALUES (%s, %s, %s)
                r„  z5UPDATE GardenSchedule SET next_due = %s WHERE id = %sr¨   r8  r'   z¦
                    INSERT INTO GardenPlantEvent (plant_id, event_type, event_date, notes)
                    VALUES (%s, 'fertilized', %s, %s)
                    r=  r  )r	   rd  rA   rv   re   rf   ri   r   r÷  r   rì   rj   r7  r   rG  rm   rU   )r	  rf  ro   rp   r5  rÎ   r  r<   r<   r@   Úcomplete_scheduleV  sF   
ü
#âûþýû
á r  z/api/tasks/duec                  C   sf  t tj d¡pdƒ} tƒ }z t|ƒ t ¡ t| d� }| 	¡ �o}| 
d|f¡ | ¡ }| 
d¡ | ¡ }t ¡ }g }|D ]J}| 
d|d f¡ | ¡ }	t | d¡pRdƒ}
|	 pa|	d	 t|
d� |k}|r†| d
|d |d d|d › �t|	r|	d	 t|
d� n|ƒddœ¡ q<W d   ƒ n1 s‘w   Y  t|ƒ}tƒ }tdt|ƒ|||dœƒW | ¡  S | ¡  w )Nr¦   r-  r4  aR  
                SELECT s.*, p.name AS plant_name, t.name AS tank_name
                FROM GardenSchedule s
                LEFT JOIN GardenPlant p ON p.id = s.plant_id
                LEFT JOIN GardenTank t ON t.id = s.tank_id
                WHERE s.enabled = 1 AND s.next_due <= %s
                ORDER BY s.next_due
                rè  rC  r   rD  rE  Ú	tank_testrs   zTest tank: T)Útyperö  r=  r=  r  rV  )rQ  ÚtasksÚ
tank_tasksr  Úweather)rh   r	   re  rj   rA   rv   r   rì   r   re   rf   r€   ri   r›   rM   r&  r1  r   r[   r÷  )r¦   ro   Úuntilrp   r’  rS  rÎ   r  rT  rì  r6  rV  r  r  r<   r<   r@   Ú	tasks_due…  sj   
÷
ûÿÿýöÿ€òï-ûÿ
r  z/api/zone-infoc                  C   s:   t ƒ } zt| ƒ tdt| ƒtƒ dœƒW |  ¡  S |  ¡  w )NT)rQ  r  r  )rA   rv   r   r&  r1  r÷  rj  r<   r<   r@   Ú	zone_infoË  rl  r  z/api/planting-nowc                  C   sJ  t ƒ } zœt| ƒ t| ƒpi }t| d¡ptƒ ¡ }|tkr3tddd d dt› dt› d�dœƒW |  	¡  S dt› �}t
| |ƒ}|rPtddttd	œ|d
œƒW |  	¡  S ztdt› dt› �ƒ}W n+ tjjtjjtjtfy‡ } ztdt|ƒdœƒdfW  Y d }~W |  	¡  S d }~ww t| ||dƒ tddttd	œ|d
œƒW |  	¡  S |  	¡  w )Nr  TFz Plant now is only available for r:  z).)rQ  Úshowr¿   rf  Úmessagezplanting_now:)Úcityr‚   )rQ  r  r¿   rf  z+https://api.cropgraph.com/api/planting?lat=r  rb  r³  rÎ  )rA   rv   rÂ   r`   rj   r   rŠ   r   r   r÷  rî   ré   r   r   rß   r’  r“  r”  râ   r]  r•  ró   )ro   r  r  rQ  r–  rf  re  r<   r<   r@   Úplanting_nowÕ  sX   ûÿ
$
å
üÿ
ðÿ "
€ôüÿ	r  z/api/crops/searchc                  C   s(  t j d¡pd ¡ } | stdg dœƒS tƒ }zxt|ƒ d|  ¡ › �}t||ƒ}|r5td|dœƒW | 	¡  S zt
dtj | ¡› �ƒ}W n+ tjjtjjtjtfym } ztdt|ƒdœƒd	fW  Y d }~W | 	¡  S d }~ww t|tƒrz| d
¡py|ng }t|||dƒ td|dœƒW | 	¡  S | 	¡  w )Nr¥  r]   T)rQ  r�  zcrop_search:r�  Frb  r³  r�  r‘  )r	   re  rj   rŠ   r   rA   rv   r8  rî   r÷  ré   rß   r4  rU  r’  r“  r”  râ   r]  r•  r`   rF   rÏ   ró   )r¥  ro   rQ  r–  rf  re  r�  r<   r<   r@   Úsearch_crops  s4   

öÿ "
€ú€ r  z/api/planting-guidec                  C   sŒ   t j d¡pd ¡ } | stdddœƒdfS tƒ }z(t|ƒ t|| ƒ}|s2tdddœƒdfW | ¡  S td	|t	|ƒd
œƒW | ¡  S | ¡  w )Nr‹   r]   Fzcrop query param requiredrb  rw  zCrop not foundr�  T)rQ  r˜  r  )
r	   re  rj   rŠ   r   rA   rv   r™  r÷  r&  )r‹   ro   r˜  r<   r<   r@   Úplanting_guide  s   

þr  r1  r6  r]   )Údefaultsz/<path:path>c                 C   sz   t }|  d¡rtddiƒdfS | r!tj tj || ¡¡r!t|| ƒS tj |d¡}tj |¡r3t|dƒS tdd|dœƒd	fS )
Nzapi/r’  r€  r�  z
index.htmlz!Garden Tracker frontend not builtz>Build the React client and place output in GARDEN_CLIENT_DIST.)r’  r  Údisti÷  )r   r.  r   rG  r6  Úexistsrœ   r
   )r6  Údist_dirÚ
index_pathr<   r<   r@   Úspa0  s    


ýÿúr!  )r:   )NF)rÙ   )r<  r    )r�  )rÅ  )Nr<  )Fr…   )ñÚ__doc__r–  rË   râ   ÚloggingrG  rO  r  rH  Úurllib.errorrß   Úurllib.parseÚurllib.requestr   r   r   Údecimalr   r=   Úflaskr   r   r   r	   r
   Úwerkzeug.utilsr   Úblueprints.alertsr   r   Úconfigr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   Ú	getLoggerÚ__name__rÆ  ÚbpÚcursorsÚ
DictCursorr?   r  rš  r™  r«  r_  ÚLockr^  ru  rò  rñ  r¿  rõ  rv  rß  rK  rA   rM   rU   r[   rb   rc   rv   r~   rn   r‹  rŒ   r‘   r™   r–   rŸ   r¡   r®   r³   r·   rº   r½   rÀ   rÂ   rØ   ré   rî   ró   rš  Ú	frozensetr  r  rB  rl   r»  r½  r0  r;  rF  rM  rS  rX  rZ  r_  r`  rg  ri  rn  rs  ry  r|  rŒ  r™  rœ  r¦  r«  rº  r¼  rÁ  rÄ  rÈ  rÐ  rÓ  rÖ  rÚ  rÇ  rô  r  r  r
  r  rÉ  r  r  r  r  r   r,  r9  r@  rD  rZ  rd  r`  rp  r1  ri  r‡  r‹  r�  r_  r^  rš  r›  rU  ra  r¸  r¾  r¿  rp  rÀ  rÂ  rÊ  rÙ  rä  ræ  rŽ   rè  rë  rð  ró  r  r
  r  r  r&  r1  r7  r>  rY  r]  r`  Úbefore_app_requestra  Úrouterf  rk  rn  ru  rv  r  r‚  r‡  rŒ  r�  r“  rž  r   r£  r¤  r¦  r®  r¯  r°  r²  r·  r¹  r½  rÀ  rË  rÌ  rØ  rÝ  râ  rã  rä  rç  rí  r÷  rù  rú  rý  r  r  r
  r  r  r  r  r  r  r  r  r!  r<   r<   r<   r@   Ú<module>   s   D

ú	 k
		,
ú
	)
 @
	%#(
pt%4P'	!	 

	#
#) g/=1T


(''U++-."%,&&.
.
E
	
.

